diff --git a/changes/29349-custom-scep-challenges b/changes/29349-custom-scep-challenges
new file mode 100644
index 0000000000..8769935c1c
--- /dev/null
+++ b/changes/29349-custom-scep-challenges
@@ -0,0 +1 @@
+- Updated custom SCEP proxy implementation to include one-time challenges.
diff --git a/cmd/fleet/cron.go b/cmd/fleet/cron.go
index 4131c43fc7..c113c435c1 100644
--- a/cmd/fleet/cron.go
+++ b/cmd/fleet/cron.go
@@ -890,6 +890,13 @@ func newCleanupsAndAggregationSchedule(
return ds.CleanupExpiredPasswordResetRequests(ctx)
},
),
+ schedule.WithJob(
+ "expired_challenges",
+ func(ctx context.Context) error {
+ _, err := ds.CleanupExpiredChallenges(ctx)
+ return err
+ },
+ ),
// Run aggregation jobs after cleanups.
schedule.WithJob(
"query_aggregated_stats",
diff --git a/docs/Contributing/product-groups/mdm/custom-scep-integration.md b/docs/Contributing/product-groups/mdm/custom-scep-integration.md
index 2b5a2b013d..eff4cf5229 100644
--- a/docs/Contributing/product-groups/mdm/custom-scep-integration.md
+++ b/docs/Contributing/product-groups/mdm/custom-scep-integration.md
@@ -102,7 +102,7 @@ sequenceDiagram
PayloadContent
Challenge
- $FLEET_VAR_CUSTOM_SCEP_CHALLENGE_Test_SCEP
+ $FLEET_VAR_CUSTOM_SCEP_CHALLENGE_CA_NAME
Key Type
RSA
Key Usage
@@ -114,7 +114,7 @@ sequenceDiagram
CN
- %SerialNumber% WIFI
+ %SerialNumber% WIFI $FLEET_VAR_SCEP_RENEWAL_ID
@@ -125,12 +125,12 @@ sequenceDiagram
URL
- ${FLEET_VAR_CUSTOM_SCEP_PROXY_URL_Test_SCEP}
+ $FLEET_VAR_CUSTOM_SCEP_PROXY_URL_CA_NAME
PayloadDisplayName
- SCEP #1
+ WIFI SCEP
PayloadIdentifier
- com.fleetdm.custom.scep
+ com.apple.security.scep.9DCC35A5-72F9-42B7-9A98-7AD9A9CCA3AC
PayloadType
com.apple.security.scep
PayloadUUID
@@ -142,7 +142,7 @@ sequenceDiagram
PayloadDisplayName
SCEP proxy cert
PayloadIdentifier
- Fleet.custom.SCEP
+ Fleet.WiFi
PayloadType
Configuration
PayloadUUID
diff --git a/ee/server/service/scep_proxy.go b/ee/server/service/scep_proxy.go
index b65a7e8b1f..f8bafefc69 100644
--- a/ee/server/service/scep_proxy.go
+++ b/ee/server/service/scep_proxy.go
@@ -23,8 +23,10 @@ import (
"golang.org/x/text/transform"
)
-var _ scepserver.ServiceWithIdentifier = (*scepProxyService)(nil)
-var challengeRegex = regexp.MustCompile(`(?i)The enrollment challenge password is: (?P\S*)`)
+var (
+ _ scepserver.ServiceWithIdentifier = (*scepProxyService)(nil)
+ challengeRegex = regexp.MustCompile(`(?i)The enrollment challenge password is: (?P\S*)`)
+)
const (
fullPasswordCache = "The password cache is full."
@@ -90,9 +92,11 @@ func (svc *scepProxyService) GetCACert(ctx context.Context, message string, iden
return res, num, nil
}
+// NOTE: Any changes to this method must ensure that the challenge portion of the identifer is
+// properly validated using the before proceeding with the PKIOperation.
func (svc *scepProxyService) PKIOperation(ctx context.Context, data []byte, identifier string) ([]byte, error) {
// We only check for expired NDES challenge during this (the last) SCEP request to account for previous requests having large network delays
- scepURL, err := svc.validateIdentifier(ctx, identifier, true)
+ scepURL, err := svc.validateIdentifier(ctx, identifier, true) // checkChallenge must be true to validate the challenge portion of the identifier
if err != nil {
return nil, err
}
@@ -110,7 +114,8 @@ func (svc *scepProxyService) PKIOperation(ctx context.Context, data []byte, iden
}
func (svc *scepProxyService) validateIdentifier(ctx context.Context, identifier string, checkChallenge bool) (string,
- error) {
+ error,
+) {
appConfig, err := svc.ds.AppConfig(ctx)
if err != nil {
return "", ctxerr.Wrap(ctx, err, "getting app config")
@@ -132,6 +137,10 @@ func (svc *scepProxyService) validateIdentifier(ctx context.Context, identifier
if len(parsedIDs) > 2 {
caName = parsedIDs[2]
}
+ var fleetChallenge string
+ if len(parsedIDs) > 3 {
+ fleetChallenge = parsedIDs[3]
+ }
if !strings.HasPrefix(profileUUID, fleet.MDMAppleProfileUUIDPrefix) {
return "", &scepserver.BadRequestError{Message: fmt.Sprintf("invalid profile UUID (only Apple config profiles are supported): %s",
profileUUID)}
@@ -175,6 +184,22 @@ func (svc *scepProxyService) validateIdentifier(ctx context.Context, identifier
if !appConfig.Integrations.CustomSCEPProxy.Valid {
return "", &scepserver.BadRequestError{Message: MessageSCEPProxyNotConfigured}
}
+ if checkChallenge {
+ if err := svc.handleFleetChallenge(ctx, fleetChallenge, hostUUID, profileUUID); err != nil {
+ // FIXME: The layered logging implementation of the scepProxyService not
+ // intuitive. Can we make it so that we return fleet.ErrWithInternal to
+ // better capture/log the context errors here?
+ svc.debugLogger.Log(
+ "msg", "custom scep proxy: failed to handle fleet challenge",
+ "host_uuid", hostUUID,
+ "profile_uuid", profileUUID,
+ "err", err.Error(),
+ )
+ return "", &scepserver.BadRequestError{
+ Message: "custom scep challenge failed",
+ }
+ }
+ }
for _, ca := range appConfig.Integrations.CustomSCEPProxy.Value {
if ca.Name == profile.CAName {
scepURL = ca.URL
@@ -193,6 +218,37 @@ func (svc *scepProxyService) GetNextCACert(_ context.Context) ([]byte, error) {
return nil, errors.New("GetNextCACert is not implemented for SCEP proxy")
}
+// handleFleetChallenge handles the validation of the fleet challenge for custom SCEP profiles as
+// well as resending the profile if the challenge cannot be validated. If it is valid, it returns
+// nil. If it cannot be validated or if any errors occur while validating or resending the profile,
+// it returns a concatenated error.
+//
+// TODO: Consider refactoring to differentiate between invalid challenge and other errors. As it
+// stands, we're resending the profile in both cases.
+func (svc *scepProxyService) handleFleetChallenge(ctx context.Context, fleetChallenge string, hostUUID string, profileUUID string) error {
+ var errs []error
+
+ if err := svc.ds.ConsumeChallenge(ctx, fleetChallenge); err != nil {
+ errs = append(errs, ctxerr.Wrap(ctx, err, "custom scep proxy: validating challenge"))
+ // FIXME: We really should have a more generic function to handle this, but our existing methods
+ // for "resending" profiles don't reevaluate the profile variables so they aren't useful for
+ // custom SCEP profiles where we need to regenerate the SCEP challenge. The main difference between
+ // the existing flow and the implementation below is that we need to blank the command uuid in order
+ // get the reconcile cron to reevaluate the command template to generate the challenge. Otherwise,
+ // it just sends the old bytes again. It feels like we some leaky abstrations somewhere that we need
+ // to clean up.
+ if err := svc.ds.ResendHostCustomSCEPProfile(ctx, hostUUID, profileUUID); err != nil {
+ errs = append(errs, ctxerr.Wrap(ctx, err, "custom scep proxy: resending host mdm profile"))
+ }
+ }
+
+ if len(errs) > 0 {
+ return ctxerr.Wrap(ctx, errors.Join(errs...), "custom scep proxy: failed to handle fleet challenge")
+ }
+
+ return nil
+}
+
type SCEPConfigService struct {
logger log.Logger
// Timeout is the timeout for SCEP requests.
diff --git a/ee/server/service/scep_proxy_test.go b/ee/server/service/scep_proxy_test.go
index bfed459870..2870b8a142 100644
--- a/ee/server/service/scep_proxy_test.go
+++ b/ee/server/service/scep_proxy_test.go
@@ -119,7 +119,6 @@ func TestValidateNDESSCEPURL(t *testing.T) {
proxy.URL = srv.URL + "/bozo"
err = svc.ValidateSCEPURL(context.Background(), proxy.URL)
assert.ErrorContains(t, err, "could not retrieve CA certificate")
-
}
// utf16FromString returns the UTF-16 encoding of the UTF-8 string s, with a terminating NUL added.
diff --git a/server/datastore/mysql/apple_mdm.go b/server/datastore/mysql/apple_mdm.go
index 239e6f3d44..a3ab243c95 100644
--- a/server/datastore/mysql/apple_mdm.go
+++ b/server/datastore/mysql/apple_mdm.go
@@ -744,6 +744,66 @@ func (ds *Datastore) RenewMDMManagedCertificates(ctx context.Context) error {
return err
}
+// ResendHostCustomSCEPProfile marks a custom SCEP profile to be resent to the host with the given UUID. It
+// also deactivates prior nano commands for the profile UUID and host UUID.
+//
+// FIXME: We really should have a more generic function to handle this, but our existing methods
+// for "resending" profiles don't reevaluate the profile variables so they aren't useful for
+// custom SCEP profiles where we need to regenerate the SCEP challenge. The main difference between
+// the existing flow and the implementation below is that we need to blank the command uuid in order
+// get the reconcile cron to reevaluate the command template to generate the challenge. Otherwise,
+// it just sends the old bytes again. It feels like we some leaky abstrations somewhere that we need
+// to clean up.
+func (ds *Datastore) ResendHostCustomSCEPProfile(ctx context.Context, hostUUID string, profUUID string) error {
+ deactivateNanoStmt := `
+UPDATE
+ nano_enrollment_queue
+ JOIN host_mdm_apple_profiles hmap
+ ON hmap.command_uuid = nano_enrollment_queue.command_uuid AND
+ hmap.host_uuid = nano_enrollment_queue.id
+SET
+ nano_enrollment_queue.active = 0
+WHERE
+ hmap.profile_uuid = ? AND
+ hmap.host_uuid = ?`
+
+ updateStmt := `
+UPDATE
+ host_mdm_apple_profiles
+SET
+ status = NULL,
+ command_uuid = '',
+ detail = '',
+ retries = 0,
+ variables_updated_at = NOW(6)
+WHERE
+ profile_uuid = ? AND
+ host_uuid = ? AND
+ operation_type = ?`
+
+ return ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
+ res, err := tx.ExecContext(ctx, deactivateNanoStmt, profUUID, hostUUID)
+ if err != nil {
+ return ctxerr.Wrap(ctx, err, "deactivating nano_enrollment_queue for commands that were pending send to host")
+ }
+ if rows, _ := res.RowsAffected(); rows == 0 {
+ // this should never happen, log for debugging
+ level.Error(ds.logger).Log("msg", "resend custom scep profile: nano not deactivated", "host_uuid", hostUUID, "profile_uuid", profUUID)
+ }
+
+ res, err = tx.ExecContext(ctx, updateStmt, profUUID, hostUUID, fleet.MDMOperationTypeInstall)
+ if err != nil {
+ return ctxerr.Wrap(ctx, err, "resending host MDM profile")
+ }
+ if rows, _ := res.RowsAffected(); rows == 0 {
+ // this should never happen, log for debugging
+ level.Error(ds.logger).Log("msg", "resend custom scep profile: host mdm apple profiles not updated", "host_uuid", hostUUID, "profile_uuid", profUUID)
+ }
+
+ return nil
+ })
+}
+
func (ds *Datastore) NewMDMAppleEnrollmentProfile(
ctx context.Context,
payload fleet.MDMAppleEnrollmentProfilePayload,
diff --git a/server/datastore/mysql/challenges.go b/server/datastore/mysql/challenges.go
new file mode 100644
index 0000000000..27aa95c4d4
--- /dev/null
+++ b/server/datastore/mysql/challenges.go
@@ -0,0 +1,100 @@
+package mysql
+
+import (
+ "context"
+ "crypto/rand"
+ "database/sql"
+ "encoding/base64"
+ "fmt"
+ "time"
+
+ "github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
+ "github.com/fleetdm/fleet/v4/server/fleet"
+ "github.com/jmoiron/sqlx"
+)
+
+// NewChallenge generates a random, base64-encoded challenge and inserts it into the challenges
+// table. It returns the generated challenge or an error if the insertion fails.
+func (ds *Datastore) NewChallenge(ctx context.Context) (string, error) {
+ key := make([]byte, 24)
+ _, err := rand.Read(key)
+ if err != nil {
+ return "", err
+ }
+ challenge := base64.URLEncoding.EncodeToString(key)
+ _, err = ds.writer(ctx).ExecContext(ctx, `INSERT INTO challenges (challenge) VALUES (?)`, challenge)
+ if err != nil {
+ return "", err
+ }
+ fmt.Println("New challenge created:", challenge)
+ return challenge, nil
+}
+
+// ConsumeChallenge checks if a valid challenge exists in the challenges table
+// and deletes it if it does. The error will include sql.ErrNoRows if the challenge
+// is not found or is expired.
+func (ds *Datastore) ConsumeChallenge(ctx context.Context, challenge string) error {
+ if challenge == "" {
+ // no challenge provided, treat as invalid
+ return ctxerr.Wrap(ctx, sql.ErrNoRows, "consume challenge called with empty challenge")
+ }
+ // use transaction to ensure atomicity of the challenge check and deletion
+ var valid bool
+ // msg will hold the reason for invalidation if applicable because any transaction err means
+ // we want to retry/rollback, rather when we want to return a validation error to the caller
+ var msg string
+ err := ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
+ // check if matching challenge exists and retrieve its creation time
+ var createdAt time.Time
+ if err := sqlx.GetContext(ctx, tx, &createdAt, `SELECT created_at FROM challenges WHERE challenge = ?`, challenge); err != nil {
+ if err == sql.ErrNoRows {
+ // invalid, challenge not found
+ msg = "challenge not found"
+ return nil
+ }
+ // some other error, return it
+ return ctxerr.Wrap(ctx, err, "get challenge")
+ }
+ // delete challenge regardless of validity
+ r, err := tx.ExecContext(ctx, `DELETE FROM challenges WHERE challenge = ?`, challenge)
+ if err != nil {
+ return ctxerr.Wrap(ctx, err, "delete challenge")
+ }
+ if rowCt, _ := r.RowsAffected(); rowCt < 1 {
+ // unlikely to happen since just checked existence and we're in a transaction,
+ // but we'll treat as invalid so we log as error for debugging purposes just in case
+ msg = "challenge not found for deletion"
+ return nil
+ }
+ // check expiry
+ if time.Since(createdAt) <= fleet.OneTimeChallengeTTL {
+ valid = true
+ } else {
+ msg = "challenge expired"
+ }
+ return nil
+ })
+
+ switch {
+ case err != nil:
+ // if we encountered an error during the transaction, return it
+ return ctxerr.Wrap(ctx, err, "consume challenge transaction")
+ case valid:
+ // challenge consumed successfully
+ return nil
+ default:
+ // challenge was invalid or expired, treat as not found
+ return ctxerr.Wrap(ctx, sql.ErrNoRows, msg)
+ }
+}
+
+// CleanupExpiredChallenges removes expired challenges from the challenges table.
+func (ds *Datastore) CleanupExpiredChallenges(ctx context.Context) (int64, error) {
+ res, err := ds.writer(ctx).ExecContext(ctx, `DELETE FROM challenges WHERE created_at < ?`, time.Now().Add(-fleet.OneTimeChallengeTTL))
+ if err != nil {
+ return 0, ctxerr.Wrap(ctx, err, "cleanup expired challenges")
+ }
+ rowCt, _ := res.RowsAffected()
+
+ return rowCt, nil
+}
diff --git a/server/datastore/mysql/migrations/tables/20250609112613_AddChallengesTable.go b/server/datastore/mysql/migrations/tables/20250609112613_AddChallengesTable.go
new file mode 100644
index 0000000000..488c31e4cb
--- /dev/null
+++ b/server/datastore/mysql/migrations/tables/20250609112613_AddChallengesTable.go
@@ -0,0 +1,33 @@
+package tables
+
+import (
+ "database/sql"
+ "fmt"
+)
+
+func init() {
+ MigrationClient.AddMigration(Up_20250609112613, Down_20250609112613)
+}
+
+func Up_20250609112613(tx *sql.Tx) error {
+ stmt := `
+ -- The challenges table holds generated challenges intended for single-use applications.
+ -- Whenever a challenge is checked it should be deleted from this table.
+ CREATE TABLE IF NOT EXISTS challenges (
+ -- challenge is randomly generated string encoded with base64.URLEncoding.
+ challenge CHAR(32),
+ created_at TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP(6),
+ updated_at TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
+ PRIMARY KEY (challenge)
+ ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;`
+
+ if _, err := tx.Exec(stmt); err != nil {
+ return fmt.Errorf("creating challenges table: %w", err)
+ }
+
+ return nil
+}
+
+func Down_20250609112613(tx *sql.Tx) error {
+ return nil
+}
diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql
index 83d752bb0c..4e974f002a 100644
--- a/server/datastore/mysql/schema.sql
+++ b/server/datastore/mysql/schema.sql
@@ -193,6 +193,15 @@ CREATE TABLE `carve_metadata` (
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
+CREATE TABLE `challenges` (
+ `challenge` char(32) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `created_at` timestamp(6) NULL DEFAULT CURRENT_TIMESTAMP(6),
+ `updated_at` timestamp(6) NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
+ PRIMARY KEY (`challenge`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `cron_stats` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
@@ -1361,9 +1370,9 @@ CREATE TABLE `migration_status_tables` (
`is_applied` tinyint(1) NOT NULL,
`tstamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
-) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=393 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=394 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
-INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20241002104104,1,'2020-01-01 01:01:01'),(314,20241002104105,1,'2020-01-01 01:01:01'),(315,20241002104106,1,'2020-01-01 01:01:01'),(316,20241002210000,1,'2020-01-01 01:01:01'),(317,20241003145349,1,'2020-01-01 01:01:01'),(318,20241004005000,1,'2020-01-01 01:01:01'),(319,20241008083925,1,'2020-01-01 01:01:01'),(320,20241009090010,1,'2020-01-01 01:01:01'),(321,20241017163402,1,'2020-01-01 01:01:01'),(322,20241021224359,1,'2020-01-01 01:01:01'),(323,20241022140321,1,'2020-01-01 01:01:01'),(324,20241025111236,1,'2020-01-01 01:01:01'),(325,20241025112748,1,'2020-01-01 01:01:01'),(326,20241025141855,1,'2020-01-01 01:01:01'),(327,20241110152839,1,'2020-01-01 01:01:01'),(328,20241110152840,1,'2020-01-01 01:01:01'),(329,20241110152841,1,'2020-01-01 01:01:01'),(330,20241116233322,1,'2020-01-01 01:01:01'),(331,20241122171434,1,'2020-01-01 01:01:01'),(332,20241125150614,1,'2020-01-01 01:01:01'),(333,20241203125346,1,'2020-01-01 01:01:01'),(334,20241203130032,1,'2020-01-01 01:01:01'),(335,20241205122800,1,'2020-01-01 01:01:01'),(336,20241209164540,1,'2020-01-01 01:01:01'),(337,20241210140021,1,'2020-01-01 01:01:01'),(338,20241219180042,1,'2020-01-01 01:01:01'),(339,20241220100000,1,'2020-01-01 01:01:01'),(340,20241220114903,1,'2020-01-01 01:01:01'),(341,20241220114904,1,'2020-01-01 01:01:01'),(342,20241224000000,1,'2020-01-01 01:01:01'),(343,20241230000000,1,'2020-01-01 01:01:01'),(344,20241231112624,1,'2020-01-01 01:01:01'),(345,20250102121439,1,'2020-01-01 01:01:01'),(346,20250121094045,1,'2020-01-01 01:01:01'),(347,20250121094500,1,'2020-01-01 01:01:01'),(348,20250121094600,1,'2020-01-01 01:01:01'),(349,20250121094700,1,'2020-01-01 01:01:01'),(350,20250124194347,1,'2020-01-01 01:01:01'),(351,20250127162751,1,'2020-01-01 01:01:01'),(352,20250213104005,1,'2020-01-01 01:01:01'),(353,20250214205657,1,'2020-01-01 01:01:01'),(354,20250217093329,1,'2020-01-01 01:01:01'),(355,20250219090511,1,'2020-01-01 01:01:01'),(356,20250219100000,1,'2020-01-01 01:01:01'),(357,20250219142401,1,'2020-01-01 01:01:01'),(358,20250224184002,1,'2020-01-01 01:01:01'),(359,20250225085436,1,'2020-01-01 01:01:01'),(360,20250226000000,1,'2020-01-01 01:01:01'),(361,20250226153445,1,'2020-01-01 01:01:01'),(362,20250304162702,1,'2020-01-01 01:01:01'),(363,20250306144233,1,'2020-01-01 01:01:01'),(364,20250313163430,1,'2020-01-01 01:01:01'),(365,20250317130944,1,'2020-01-01 01:01:01'),(366,20250318165922,1,'2020-01-01 01:01:01'),(367,20250320132525,1,'2020-01-01 01:01:01'),(368,20250320200000,1,'2020-01-01 01:01:01'),(369,20250326161930,1,'2020-01-01 01:01:01'),(370,20250326161931,1,'2020-01-01 01:01:01'),(371,20250331042354,1,'2020-01-01 01:01:01'),(372,20250331154206,1,'2020-01-01 01:01:01'),(373,20250401155831,1,'2020-01-01 01:01:01'),(374,20250408133233,1,'2020-01-01 01:01:01'),(375,20250410104321,1,'2020-01-01 01:01:01'),(376,20250421085116,1,'2020-01-01 01:01:01'),(377,20250422095806,1,'2020-01-01 01:01:01'),(378,20250424153059,1,'2020-01-01 01:01:01'),(379,20250430103833,1,'2020-01-01 01:01:01'),(380,20250430112622,1,'2020-01-01 01:01:01'),(381,20250501162727,1,'2020-01-01 01:01:01'),(382,20250502154517,1,'2020-01-01 01:01:01'),(383,20250502222222,1,'2020-01-01 01:01:01'),(384,20250507170845,1,'2020-01-01 01:01:01'),(385,20250513162912,1,'2020-01-01 01:01:01'),(386,20250519161614,1,'2020-01-01 01:01:01'),(387,20250519170000,1,'2020-01-01 01:01:01'),(388,20250520153848,1,'2020-01-01 01:01:01'),(389,20250528115932,1,'2020-01-01 01:01:01'),(390,20250529102706,1,'2020-01-01 01:01:01'),(391,20250603105558,1,'2020-01-01 01:01:01'),(392,20250609102714,1,'2020-01-01 01:01:01');
+INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20241002104104,1,'2020-01-01 01:01:01'),(314,20241002104105,1,'2020-01-01 01:01:01'),(315,20241002104106,1,'2020-01-01 01:01:01'),(316,20241002210000,1,'2020-01-01 01:01:01'),(317,20241003145349,1,'2020-01-01 01:01:01'),(318,20241004005000,1,'2020-01-01 01:01:01'),(319,20241008083925,1,'2020-01-01 01:01:01'),(320,20241009090010,1,'2020-01-01 01:01:01'),(321,20241017163402,1,'2020-01-01 01:01:01'),(322,20241021224359,1,'2020-01-01 01:01:01'),(323,20241022140321,1,'2020-01-01 01:01:01'),(324,20241025111236,1,'2020-01-01 01:01:01'),(325,20241025112748,1,'2020-01-01 01:01:01'),(326,20241025141855,1,'2020-01-01 01:01:01'),(327,20241110152839,1,'2020-01-01 01:01:01'),(328,20241110152840,1,'2020-01-01 01:01:01'),(329,20241110152841,1,'2020-01-01 01:01:01'),(330,20241116233322,1,'2020-01-01 01:01:01'),(331,20241122171434,1,'2020-01-01 01:01:01'),(332,20241125150614,1,'2020-01-01 01:01:01'),(333,20241203125346,1,'2020-01-01 01:01:01'),(334,20241203130032,1,'2020-01-01 01:01:01'),(335,20241205122800,1,'2020-01-01 01:01:01'),(336,20241209164540,1,'2020-01-01 01:01:01'),(337,20241210140021,1,'2020-01-01 01:01:01'),(338,20241219180042,1,'2020-01-01 01:01:01'),(339,20241220100000,1,'2020-01-01 01:01:01'),(340,20241220114903,1,'2020-01-01 01:01:01'),(341,20241220114904,1,'2020-01-01 01:01:01'),(342,20241224000000,1,'2020-01-01 01:01:01'),(343,20241230000000,1,'2020-01-01 01:01:01'),(344,20241231112624,1,'2020-01-01 01:01:01'),(345,20250102121439,1,'2020-01-01 01:01:01'),(346,20250121094045,1,'2020-01-01 01:01:01'),(347,20250121094500,1,'2020-01-01 01:01:01'),(348,20250121094600,1,'2020-01-01 01:01:01'),(349,20250121094700,1,'2020-01-01 01:01:01'),(350,20250124194347,1,'2020-01-01 01:01:01'),(351,20250127162751,1,'2020-01-01 01:01:01'),(352,20250213104005,1,'2020-01-01 01:01:01'),(353,20250214205657,1,'2020-01-01 01:01:01'),(354,20250217093329,1,'2020-01-01 01:01:01'),(355,20250219090511,1,'2020-01-01 01:01:01'),(356,20250219100000,1,'2020-01-01 01:01:01'),(357,20250219142401,1,'2020-01-01 01:01:01'),(358,20250224184002,1,'2020-01-01 01:01:01'),(359,20250225085436,1,'2020-01-01 01:01:01'),(360,20250226000000,1,'2020-01-01 01:01:01'),(361,20250226153445,1,'2020-01-01 01:01:01'),(362,20250304162702,1,'2020-01-01 01:01:01'),(363,20250306144233,1,'2020-01-01 01:01:01'),(364,20250313163430,1,'2020-01-01 01:01:01'),(365,20250317130944,1,'2020-01-01 01:01:01'),(366,20250318165922,1,'2020-01-01 01:01:01'),(367,20250320132525,1,'2020-01-01 01:01:01'),(368,20250320200000,1,'2020-01-01 01:01:01'),(369,20250326161930,1,'2020-01-01 01:01:01'),(370,20250326161931,1,'2020-01-01 01:01:01'),(371,20250331042354,1,'2020-01-01 01:01:01'),(372,20250331154206,1,'2020-01-01 01:01:01'),(373,20250401155831,1,'2020-01-01 01:01:01'),(374,20250408133233,1,'2020-01-01 01:01:01'),(375,20250410104321,1,'2020-01-01 01:01:01'),(376,20250421085116,1,'2020-01-01 01:01:01'),(377,20250422095806,1,'2020-01-01 01:01:01'),(378,20250424153059,1,'2020-01-01 01:01:01'),(379,20250430103833,1,'2020-01-01 01:01:01'),(380,20250430112622,1,'2020-01-01 01:01:01'),(381,20250501162727,1,'2020-01-01 01:01:01'),(382,20250502154517,1,'2020-01-01 01:01:01'),(383,20250502222222,1,'2020-01-01 01:01:01'),(384,20250507170845,1,'2020-01-01 01:01:01'),(385,20250513162912,1,'2020-01-01 01:01:01'),(386,20250519161614,1,'2020-01-01 01:01:01'),(387,20250519170000,1,'2020-01-01 01:01:01'),(388,20250520153848,1,'2020-01-01 01:01:01'),(389,20250528115932,1,'2020-01-01 01:01:01'),(390,20250529102706,1,'2020-01-01 01:01:01'),(391,20250603105558,1,'2020-01-01 01:01:01'),(392,20250609102714,1,'2020-01-01 01:01:01'),(393,20250609112613,1,'2020-01-01 01:01:01');
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `mobile_device_management_solutions` (
diff --git a/server/fleet/datastore.go b/server/fleet/datastore.go
index 673dd56def..7b4b193519 100644
--- a/server/fleet/datastore.go
+++ b/server/fleet/datastore.go
@@ -2056,6 +2056,10 @@ type Datastore interface {
// ListHostMDMManagedCertificates returns the managed certificates for the given host UUID
ListHostMDMManagedCertificates(ctx context.Context, hostUUID string) ([]*MDMManagedCertificate, error)
+ // ResendHostCustomSCEPProfile marks a custom SCEP profile to be resent to the host with the given UUID. It
+ // also deactivates prior nano commands for the profile UUID and host UUID.
+ ResendHostCustomSCEPProfile(ctx context.Context, hostUUID string, profUUID string) error
+
// /////////////////////////////////////////////////////////////////////////////
// Secret variables
@@ -2124,6 +2128,19 @@ type Datastore interface {
// UpdateScimLastRequest updates the last SCIM request info
UpdateScimLastRequest(ctx context.Context, lastRequest *ScimLastRequest) error
+ // /////////////////////////////////////////////////////////////////////////////
+ // Challenges
+
+ // NewChallenge generates a random, base64-encoded challenge and inserts it into the challenges table.
+ NewChallenge(ctx context.Context) (string, error)
+ // ConsumeChallenge checks if a valid challenge exists in the challenges table
+ // and deletes it if it does. The error will include sql.ErrNoRows if the challenge
+ // is not found or is expired.
+ ConsumeChallenge(ctx context.Context, challenge string) error
+ // CleanupExpiredChallenges removes expired challenges from the challenges table,
+ // intended to be run as a cron job.
+ CleanupExpiredChallenges(ctx context.Context) (int64, error)
+
// /////////////////////////////////////////////////////////////////////////////
// Microsoft Compliance Partner
diff --git a/server/fleet/mdm.go b/server/fleet/mdm.go
index 1a6e3ebc6d..b98d45e892 100644
--- a/server/fleet/mdm.go
+++ b/server/fleet/mdm.go
@@ -45,6 +45,9 @@ const (
FleetVarDigiCertPasswordPrefix = "DIGICERT_PASSWORD_" // nolint:gosec // G101: Potential hardcoded credentials
FleetVarCustomSCEPChallengePrefix = "CUSTOM_SCEP_CHALLENGE_"
FleetVarCustomSCEPProxyURLPrefix = "CUSTOM_SCEP_PROXY_URL_"
+
+ // OneTimeChallengeTTL is the time to live for one-time challenges.
+ OneTimeChallengeTTL = 1 * time.Hour
)
type AppleMDM struct {
diff --git a/server/mdm/scep/server/service.go b/server/mdm/scep/server/service.go
index d2eecf955c..b89f8d3a48 100644
--- a/server/mdm/scep/server/service.go
+++ b/server/mdm/scep/server/service.go
@@ -33,19 +33,36 @@ type Service interface {
}
// ServiceWithIdentifier is the interface for all supported SCEP server operations.
+// It extends the core SCEP server with ad hoc, polymorphic "identifier" functionality.
+//
+// FIXME: This seems to have been introduced as workaround to support Fleet-specific features
+// but its usage in practice is non-intuitive. In the context of Fleet's SCEP proxy,
+// the identifier has been implemented as comma-separated string that corresponds to various
+// values used when processing SCEP requests, which vary based on the specific implementation
+// (e.g., NDES vs. custom SCEP proxy). This functionality has been used to support
+// ad hoc validation schemes.
type ServiceWithIdentifier interface {
// GetCACaps returns a list of options
// which are supported by the server.
+ //
+ // NOTE: See type definition of ServiceWithIdentifier
+ // for additional context on identifier usage.
GetCACaps(ctx context.Context, identifier string) ([]byte, error)
// GetCACert returns CA certificate or
// a CA certificate chain with intermediates
// in a PKCS#7 Degenerate Certificates format
// message is an optional string for the CA
+ //
+ // NOTE: See type definition of ServiceWithIdentifier
+ // for additional context on identifier usage.
GetCACert(ctx context.Context, message string, identifier string) ([]byte, int, error)
// PKIOperation handles incoming SCEP messages such as PKCSReq and
// sends back a CertRep PKIMessag.
+ //
+ // NOTE: See type definition of ServiceWithIdentifier
+ // for additional context on identifier usage.
PKIOperation(ctx context.Context, msg []byte, identifier string) ([]byte, error)
// GetNextCACert returns a replacement certificate or certificate chain
diff --git a/server/mdm/scep/server/transport.go b/server/mdm/scep/server/transport.go
index 2ee9606386..648fe5333c 100644
--- a/server/mdm/scep/server/transport.go
+++ b/server/mdm/scep/server/transport.go
@@ -7,7 +7,6 @@ import (
"errors"
"fmt"
"io"
- "io/ioutil"
"net/http"
"net/url"
@@ -175,7 +174,7 @@ func message(r *http.Request) ([]byte, error) {
}
return []byte(msg), nil
case "POST":
- return ioutil.ReadAll(io.LimitReader(r.Body, maxPayloadSize))
+ return io.ReadAll(io.LimitReader(r.Body, maxPayloadSize))
default:
return nil, errors.New("method not supported")
}
@@ -228,13 +227,13 @@ func encodeSCEPResponse(ctx context.Context, w http.ResponseWriter, response int
// DecodeSCEPResponse decodes a SCEP response
func DecodeSCEPResponse(ctx context.Context, r *http.Response) (interface{}, error) {
if r.StatusCode != http.StatusOK && r.StatusCode >= 400 {
- body, _ := ioutil.ReadAll(io.LimitReader(r.Body, 4096))
+ body, _ := io.ReadAll(io.LimitReader(r.Body, 4096))
return nil, fmt.Errorf("http request failed with status %s, msg: %s",
r.Status,
string(body),
)
}
- data, err := ioutil.ReadAll(io.LimitReader(r.Body, maxPayloadSize))
+ data, err := io.ReadAll(io.LimitReader(r.Body, maxPayloadSize))
if err != nil {
return nil, err
}
diff --git a/server/mock/datastore_mock.go b/server/mock/datastore_mock.go
index 7eced15251..1942f6c1a3 100644
--- a/server/mock/datastore_mock.go
+++ b/server/mock/datastore_mock.go
@@ -1294,6 +1294,8 @@ type RenewMDMManagedCertificatesFunc func(ctx context.Context) error
type ListHostMDMManagedCertificatesFunc func(ctx context.Context, hostUUID string) ([]*fleet.MDMManagedCertificate, error)
+type ResendHostCustomSCEPProfileFunc func(ctx context.Context, hostUUID string, profUUID string) error
+
type UpsertSecretVariablesFunc func(ctx context.Context, secretVariables []fleet.SecretVariable) error
type GetSecretVariablesFunc func(ctx context.Context, names []string) ([]fleet.SecretVariable, error)
@@ -1366,6 +1368,12 @@ type ScimLastRequestFunc func(ctx context.Context) (*fleet.ScimLastRequest, erro
type UpdateScimLastRequestFunc func(ctx context.Context, lastRequest *fleet.ScimLastRequest) error
+type NewChallengeFunc func(ctx context.Context) (string, error)
+
+type ConsumeChallengeFunc func(ctx context.Context, challenge string) error
+
+type CleanupExpiredChallengesFunc func(ctx context.Context) (int64, error)
+
type ConditionalAccessMicrosoftCreateIntegrationFunc func(ctx context.Context, tenantID string, proxyServerSecret string) error
type ConditionalAccessMicrosoftGetFunc func(ctx context.Context) (*fleet.ConditionalAccessMicrosoftIntegration, error)
@@ -3289,6 +3297,9 @@ type DataStore struct {
ListHostMDMManagedCertificatesFunc ListHostMDMManagedCertificatesFunc
ListHostMDMManagedCertificatesFuncInvoked bool
+ ResendHostCustomSCEPProfileFunc ResendHostCustomSCEPProfileFunc
+ ResendHostCustomSCEPProfileFuncInvoked bool
+
UpsertSecretVariablesFunc UpsertSecretVariablesFunc
UpsertSecretVariablesFuncInvoked bool
@@ -3397,6 +3408,15 @@ type DataStore struct {
UpdateScimLastRequestFunc UpdateScimLastRequestFunc
UpdateScimLastRequestFuncInvoked bool
+ NewChallengeFunc NewChallengeFunc
+ NewChallengeFuncInvoked bool
+
+ ConsumeChallengeFunc ConsumeChallengeFunc
+ ConsumeChallengeFuncInvoked bool
+
+ CleanupExpiredChallengesFunc CleanupExpiredChallengesFunc
+ CleanupExpiredChallengesFuncInvoked bool
+
ConditionalAccessMicrosoftCreateIntegrationFunc ConditionalAccessMicrosoftCreateIntegrationFunc
ConditionalAccessMicrosoftCreateIntegrationFuncInvoked bool
@@ -7873,6 +7893,13 @@ func (s *DataStore) ListHostMDMManagedCertificates(ctx context.Context, hostUUID
return s.ListHostMDMManagedCertificatesFunc(ctx, hostUUID)
}
+func (s *DataStore) ResendHostCustomSCEPProfile(ctx context.Context, hostUUID string, profUUID string) error {
+ s.mu.Lock()
+ s.ResendHostCustomSCEPProfileFuncInvoked = true
+ s.mu.Unlock()
+ return s.ResendHostCustomSCEPProfileFunc(ctx, hostUUID, profUUID)
+}
+
func (s *DataStore) UpsertSecretVariables(ctx context.Context, secretVariables []fleet.SecretVariable) error {
s.mu.Lock()
s.UpsertSecretVariablesFuncInvoked = true
@@ -8125,6 +8152,27 @@ func (s *DataStore) UpdateScimLastRequest(ctx context.Context, lastRequest *flee
return s.UpdateScimLastRequestFunc(ctx, lastRequest)
}
+func (s *DataStore) NewChallenge(ctx context.Context) (string, error) {
+ s.mu.Lock()
+ s.NewChallengeFuncInvoked = true
+ s.mu.Unlock()
+ return s.NewChallengeFunc(ctx)
+}
+
+func (s *DataStore) ConsumeChallenge(ctx context.Context, challenge string) error {
+ s.mu.Lock()
+ s.ConsumeChallengeFuncInvoked = true
+ s.mu.Unlock()
+ return s.ConsumeChallengeFunc(ctx, challenge)
+}
+
+func (s *DataStore) CleanupExpiredChallenges(ctx context.Context) (int64, error) {
+ s.mu.Lock()
+ s.CleanupExpiredChallengesFuncInvoked = true
+ s.mu.Unlock()
+ return s.CleanupExpiredChallengesFunc(ctx)
+}
+
func (s *DataStore) ConditionalAccessMicrosoftCreateIntegration(ctx context.Context, tenantID string, proxyServerSecret string) error {
s.mu.Lock()
s.ConditionalAccessMicrosoftCreateIntegrationFuncInvoked = true
diff --git a/server/service/apple_mdm.go b/server/service/apple_mdm.go
index ad1b20a503..25f45363fa 100644
--- a/server/service/apple_mdm.go
+++ b/server/service/apple_mdm.go
@@ -4510,9 +4510,14 @@ func preprocessProfileContents(
"This error should never happen since we validated/populated CAs earlier", "ca_name", caName)
continue
}
+ // Generate a new SCEP challenge for the profile
+ challenge, err := ds.NewChallenge(ctx)
+ if err != nil {
+ return ctxerr.Wrap(ctx, err, "generating SCEP challenge")
+ }
// Insert the SCEP URL into the profile contents
proxyURL := fmt.Sprintf("%s%s%s", appConfig.MDMUrl(), apple_mdm.SCEPProxyPath,
- url.PathEscape(fmt.Sprintf("%s,%s,%s", hostUUID, profUUID, caName)))
+ url.PathEscape(fmt.Sprintf("%s,%s,%s,%s", hostUUID, profUUID, caName, challenge)))
hostContents, err = replaceExactFleetPrefixVariableInXML(fleet.FleetVarCustomSCEPProxyURLPrefix, ca.Name, hostContents, proxyURL)
if err != nil {
return ctxerr.Wrap(ctx, err, "replacing Fleet variable for SCEP proxy URL")
diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go
index 389cd21558..3493457292 100644
--- a/server/service/integration_mdm_test.go
+++ b/server/service/integration_mdm_test.go
@@ -14734,6 +14734,99 @@ func (s *integrationMDMTestSuite) TestCustomSCEPIntegration() {
scepServer := scep_server.StartTestSCEPServer(t)
scepServerURL := scepServer.URL + "/scep"
+ // parseSCEPProfile returns the parsed SCEP profile along with the identifier from the profile URL
+ parseSCEPProfile := func(t *testing.T, raw []byte, scepConfig fleet.CustomSCEPProxyIntegration) (SCEPProfileContent, string) {
+ var fullCmd micromdm.CommandPayload
+ require.NoError(t, plist.Unmarshal(raw, &fullCmd))
+ require.NotNil(t, fullCmd.Command)
+ require.NotNil(t, fullCmd.Command.InstallProfile)
+ rawProfile := fullCmd.Command.InstallProfile.Payload
+ if !bytes.HasPrefix(rawProfile, []byte("