diff --git a/changes/40177-config-profile-name-status b/changes/40177-config-profile-name-status new file mode 100644 index 0000000000..7f4c20069f --- /dev/null +++ b/changes/40177-config-profile-name-status @@ -0,0 +1,2 @@ +* Added logging of profile names alongside MDM commands installing or removing them +* Added indication in the UI when a profile command was deferred ("NotNow" status) diff --git a/frontend/__mocks__/commandMock.ts b/frontend/__mocks__/commandMock.ts index f3abe0aba2..7bb045ffa3 100644 --- a/frontend/__mocks__/commandMock.ts +++ b/frontend/__mocks__/commandMock.ts @@ -9,6 +9,7 @@ const DEFAULT_COMMAND_MOCK: ICommand = { updated_at: "2024-01-01T00:00:00Z", request_type: "InstallProfile", hostname: "default-hostname", + name: null, }; export const createMockCommand = (overrides?: Partial): ICommand => ({ @@ -47,6 +48,7 @@ const DEFAULT_COMMAND_RESULT_MOCK: ICommandResult = { Acknowledged Installation complete `), + name: null, }; /** diff --git a/frontend/interfaces/command.ts b/frontend/interfaces/command.ts index 982a02cd36..fd28935e92 100644 --- a/frontend/interfaces/command.ts +++ b/frontend/interfaces/command.ts @@ -12,6 +12,7 @@ export interface ICommand { updated_at: string; request_type: string; hostname: string; + name: string | null; // Profile name when command is for installing/removing a macOS profile } /** @@ -30,10 +31,9 @@ export interface ICommandResult { payload: string; /** Base64-encoded string containing the MDM command response */ result: string; + name: string | null; // Profile name when command is for installing/removing a macOS profile /** ResultsMetadata contains command-specific metadata. * VPP install commands include a "software_installed" boolean and * "vpp_verify_timeout_seconds" integer. */ results_metadata?: Record; } - -export type IMDMCommandResult = ICommandResult; diff --git a/frontend/pages/hosts/components/CommandDetailsModal/CommandDetailsModal.tsx b/frontend/pages/hosts/components/CommandDetailsModal/CommandDetailsModal.tsx index d6022829b7..762bf9ef0e 100644 --- a/frontend/pages/hosts/components/CommandDetailsModal/CommandDetailsModal.tsx +++ b/frontend/pages/hosts/components/CommandDetailsModal/CommandDetailsModal.tsx @@ -11,12 +11,12 @@ import commandApi, { IGetHostCommandResultsQueryKey, } from "services/entities/command"; +import InputField from "components/forms/fields/InputField"; import Modal from "components/Modal"; import Spinner from "components/Spinner"; import DataError from "components/DataError"; import IconStatusMessage from "components/IconStatusMessage"; import { IconNames } from "components/icons"; -import Textarea from "components/Textarea"; import ModalFooter from "components/ModalFooter"; import Button from "components/buttons/Button"; @@ -50,12 +50,19 @@ const getStatusMessage = (result: ICommandResult): React.ReactNode => { })})` : null; + const namePart = result.name ? ( + <> + {" "} + for {result.name} + + ) : null; + switch (result.status) { case "CommandFormatError": case "Error": return ( - The {result.request_type} command failed on{" "} + The {result.request_type} command{namePart} failed on{" "} {result.hostname} {displayTime}. @@ -64,7 +71,7 @@ const getStatusMessage = (result: ICommandResult): React.ReactNode => { case "Acknowledged": return ( - The {result.request_type} command ran on{" "} + The {result.request_type} command{namePart} was acknowledged by{" "} {result.hostname} {displayTime}. @@ -73,15 +80,15 @@ const getStatusMessage = (result: ICommandResult): React.ReactNode => { case "Pending": return ( - The {result.request_type} command is running or will run on{" "} - {result.hostname} when it comes online. + The {result.request_type} command{namePart} is pending on{" "} + {result.hostname}. ); case "NotNow": return ( - The {result.request_type} command didn't run on{" "} + The {result.request_type} command{namePart} is deferred on{" "} {result.hostname} because the host was locked or was running on battery power while in Power Nap. Fleet will try again. @@ -135,21 +142,26 @@ const ModalContent = ({ message={getStatusMessage(result)} /> {!!result.payload && ( - + )} {!!result.result && ( - + value={result.result} + readOnly + enableCopy + /> )} ); diff --git a/frontend/pages/hosts/components/CommandDetailsModal/_styles.scss b/frontend/pages/hosts/components/CommandDetailsModal/_styles.scss index 297532bcb0..7df1a37e3f 100644 --- a/frontend/pages/hosts/components/CommandDetailsModal/_styles.scss +++ b/frontend/pages/hosts/components/CommandDetailsModal/_styles.scss @@ -4,15 +4,16 @@ flex-direction: column; gap: $pad-large; padding-bottom: $pad-small; - - .textarea-wrapper { - gap: $pad-small; - } } - // shortening the hight of the textarea - // so the modal height stays within the viewport - .textarea--code { + .input-field__textarea { + // shortening the height of the textarea + // so the modal height stays within the viewport max-height: 200px; + height: auto; + field-sizing: content; + overflow-y: auto; + background-color: $ui-off-white; + font-family: "SourceCodePro", $monospace; } } diff --git a/frontend/pages/hosts/details/cards/Activity/CommandItem/CommandItem.tsx b/frontend/pages/hosts/details/cards/Activity/CommandItem/CommandItem.tsx index e53c998f19..8711a49838 100644 --- a/frontend/pages/hosts/details/cards/Activity/CommandItem/CommandItem.tsx +++ b/frontend/pages/hosts/details/cards/Activity/CommandItem/CommandItem.tsx @@ -18,27 +18,48 @@ interface ICommandItemProps { onShowDetails: ShowCommandDetailsHandler; } -const CommandItem = ({ command, onShowDetails }: ICommandItemProps) => { - const { command_status, request_type, updated_at } = command; +const isProfileCommand = (requestType: string): boolean => + requestType === "InstallProfile" || requestType === "RemoveProfile"; + +const getStatusText = (command: ICommand): string => { + const { command_status, status } = command; + + // Differentiate NotNow from regular Pending + if (status === "NotNow") { + return "is deferred"; + } - let statusVerb = ""; switch (command_status) { case "pending": - statusVerb = "will run"; - break; - case "ran": + return "is pending"; case "failed": - statusVerb = command_status; - break; + return "failed"; + case "ran": default: - statusVerb = "ran"; + return "was acknowledged"; } +}; + +const CommandItem = ({ command, onShowDetails }: ICommandItemProps) => { + const { request_type, updated_at, name } = command; + + const statusText = getStatusText(command); const onShowCommandDetails = (e: React.MouseEvent) => { e.stopPropagation(); onShowDetails(command); }; + const activityText = name ? ( + <> + The {request_type} command for {name} {statusText}. + + ) : ( + <> + The {request_type} command {statusText}. + + ); + return ( { createdAt={new Date(updated_at)} onClickFeedItem={onShowCommandDetails} > - The {request_type} command {statusVerb}. + {activityText} ); }; diff --git a/server/datastore/mysql/apple_mdm.go b/server/datastore/mysql/apple_mdm.go index 2ab02cce8c..8b654574d6 100644 --- a/server/datastore/mysql/apple_mdm.go +++ b/server/datastore/mysql/apple_mdm.go @@ -1032,7 +1032,8 @@ SELECT ncr.result, ncr.updated_at, nc.request_type, - nc.command as payload + nc.command as payload, + nc.name FROM nano_command_results ncr INNER JOIN @@ -1085,7 +1086,8 @@ SELECT COALESCE(ncr.status, 'Pending') AS status, request_type, nc.command AS payload, - COALESCE(ncr.result, '') AS result + COALESCE(ncr.result, '') AS result, + nc.name FROM nano_enrollment_queue nq JOIN nano_commands nc ON nq.command_uuid = nc.command_uuid @@ -1130,7 +1132,8 @@ SELECT COALESCE(nvq.result_updated_at, nvq.created_at) as updated_at, nvq.request_type, h.hostname, - h.team_id + h.team_id, + nvq.name FROM nano_view_queue nvq INNER JOIN diff --git a/server/datastore/mysql/mdm.go b/server/datastore/mysql/mdm.go index a464bd4017..a6363261d1 100644 --- a/server/datastore/mysql/mdm.go +++ b/server/datastore/mysql/mdm.go @@ -48,7 +48,8 @@ SELECT COALESCE(nvq.result_updated_at, nvq.created_at) as updated_at, nvq.request_type as request_type, h.hostname, - h.team_id + h.team_id, + nvq.name FROM nano_view_queue nvq INNER JOIN @@ -67,7 +68,8 @@ SELECT COALESCE(wmc.updated_at, wmc.created_at) as updated_at, wmc.target_loc_uri as request_type, h.hostname, - h.team_id + h.team_id, + NULL as name FROM windows_mdm_commands wmc LEFT JOIN windows_mdm_command_queue wmcq ON wmcq.command_uuid = wmc.command_uuid LEFT JOIN windows_mdm_command_results wmcr ON wmc.command_uuid = wmcr.command_uuid @@ -225,7 +227,8 @@ SELECT WHEN COALESCE(NULLIF(ncr.status, ''), 'Pending') = 'Error' THEN 'failed' ELSE 'pending' END AS command_status, - request_type + request_type, + nc.name FROM nano_enrollment_queue nq JOIN nano_commands nc ON nq.command_uuid = nc.command_uuid @@ -251,7 +254,8 @@ WHERE wc.created_at AS updated_at, '101' AS status, 'pending' AS command_status, - wc.target_loc_uri AS request_type + wc.target_loc_uri AS request_type, + NULL AS name FROM windows_mdm_command_queue wq JOIN mdm_windows_enrollments mwe ON mwe.id = wq.enrollment_id @@ -287,7 +291,8 @@ WHERE ) AS UNSIGNED ) >= 400 THEN 'failed' END AS command_status, - wc.target_loc_uri AS request_type + wc.target_loc_uri AS request_type, + NULL AS name FROM windows_mdm_command_results wcr JOIN mdm_windows_enrollments mwe ON mwe.id = wcr.enrollment_id diff --git a/server/datastore/mysql/mdm_test.go b/server/datastore/mysql/mdm_test.go index 65292f4281..3153170167 100644 --- a/server/datastore/mysql/mdm_test.go +++ b/server/datastore/mysql/mdm_test.go @@ -57,6 +57,7 @@ func TestMDMShared(t *testing.T) { {"TestDeleteMDMProfilesCancelsInstalls", testDeleteMDMProfilesCancelsInstalls}, {"TestDeleteTeamCancelsWindowsProfileInstalls", testDeleteTeamCancelsWindowsProfileInstalls}, {"TestCleanUpMDMManagedCertificates", testCleanUpMDMManagedCertificates}, + {"TestEnqueueCommandWithName", testEnqueueCommandWithName}, } for _, c := range cases { @@ -9142,7 +9143,7 @@ func testDeleteMDMProfilesCancelsInstalls(t *testing.T, ds *Datastore) { forceSetAppleHostProfileStatus(t, ds, host2.UUID, test.ToMDMAppleConfigProfile(profNameToProf["A2"]), fleet.MDMOperationTypeInstall, fleet.MDMDeliveryVerifying) // enqueue the corresponding command for the installed profile cmdUUID := uuid.New().String() - err = commander.InstallProfile(ctx, []string{host2.UUID}, appleProfs[1].Mobileconfig, cmdUUID) + err = commander.InstallProfile(ctx, []string{host2.UUID}, appleProfs[1].Mobileconfig, cmdUUID, "") require.NoError(t, err) ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { _, err := q.ExecContext(ctx, `UPDATE host_mdm_apple_profiles SET command_uuid = ? WHERE host_uuid = ? AND profile_uuid = ?`, cmdUUID, host2.UUID, profNameToProf["A2"].ProfileUUID) @@ -9173,7 +9174,7 @@ func testDeleteMDMProfilesCancelsInstalls(t *testing.T, ds *Datastore) { forceSetAppleHostProfileStatus(t, ds, host1.UUID, test.ToMDMAppleConfigProfile(profNameToProf["A3"]), fleet.MDMOperationTypeInstall, fleet.MDMDeliveryPending) // enqueue the corresponding command for the installed profile cmdUUID = uuid.New().String() - err = commander.InstallProfile(ctx, []string{host1.UUID}, appleProfs[2].Mobileconfig, cmdUUID) + err = commander.InstallProfile(ctx, []string{host1.UUID}, appleProfs[2].Mobileconfig, cmdUUID, "") require.NoError(t, err) ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { _, err := q.ExecContext(ctx, `UPDATE host_mdm_apple_profiles SET command_uuid = ? WHERE host_uuid = ? AND profile_uuid = ?`, cmdUUID, host1.UUID, profNameToProf["A3"].ProfileUUID) @@ -9394,6 +9395,155 @@ func forceSetAndroidHostProfileStatus(t *testing.T, ds *Datastore, hostUUID stri }) } +func testEnqueueCommandWithName(t *testing.T, ds *Datastore) { + ctx := context.Background() + + SetTestABMAssets(t, ds, "fleet") + + // Create and enroll a macOS host + macH, err := ds.NewHost(ctx, &fleet.Host{ + Hostname: "test-host-cmd-name", + OsqueryHostID: new("osquery-macos-cmd-name"), + NodeKey: new("node-key-macos-cmd-name"), + UUID: uuid.NewString(), + Platform: "darwin", + HardwareSerial: "CMDNAME123", + }) + require.NoError(t, err) + nanoEnroll(t, ds, macH, false) + + commander, mdmStorage := createMDMAppleCommanderAndStorage(t, ds) + + // Test 1: InstallProfile with a name + cmdUUID1 := uuid.New().String() + mc := mobileconfig.Mobileconfig(fmt.Appendf(nil, ` + + + + PayloadContent + + PayloadDisplayName + Test Profile + PayloadIdentifier + com.test.profile + PayloadType + Configuration + PayloadUUID + %s + PayloadVersion + 1 + +`, uuid.New().String())) + err = commander.InstallProfile(ctx, []string{macH.UUID}, mc, cmdUUID1, "Test Profile Name") + require.NoError(t, err) + + // Verify name in DB + var storedName sql.NullString + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { + return sqlx.GetContext(ctx, q, &storedName, `SELECT name FROM nano_commands WHERE command_uuid = ?`, cmdUUID1) + }) + require.True(t, storedName.Valid) + require.Equal(t, "Test Profile Name", storedName.String) + + // Also verify via ListMDMCommands + cmds, _, _, err := ds.ListMDMCommands(ctx, fleet.TeamFilter{User: test.UserAdmin}, &fleet.MDMCommandListOptions{}) + require.NoError(t, err) + require.Len(t, cmds, 1) + require.NotNil(t, cmds[0].Name) + require.Equal(t, "Test Profile Name", *cmds[0].Name) + + // Test 2: EnqueueCommand without a name + cmdUUID2 := uuid.New().String() + rawCmd := createRawAppleCmd("ProfileList", cmdUUID2) + err = commander.EnqueueCommand(ctx, []string{macH.UUID}, rawCmd) + require.NoError(t, err) + + // Verify name is NULL in DB + var storedName2 sql.NullString + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { + return sqlx.GetContext(ctx, q, &storedName2, `SELECT name FROM nano_commands WHERE command_uuid = ?`, cmdUUID2) + }) + require.False(t, storedName2.Valid) + + // Verify name is null in the API + // Verify ListMDMCommands also exposes nil Name for unnamed commands + cmds, _, _, err = ds.ListMDMCommands(ctx, fleet.TeamFilter{User: test.UserAdmin}, &fleet.MDMCommandListOptions{}) + require.NoError(t, err) + require.Len(t, cmds, 2) + + gotByUUID := make(map[string]*fleet.MDMCommand, len(cmds)) + for _, c := range cmds { + gotByUUID[c.CommandUUID] = c + } + require.Equal(t, "Test Profile Name", *gotByUUID[cmdUUID1].Name) + require.Nil(t, gotByUUID[cmdUUID2].Name) + + // Test 3: Name exactly 255 characters — should NOT be truncated + cmdUUID3 := uuid.New().String() + name255 := strings.Repeat("a", 255) + rawXML3 := createRawAppleCmd("InstallProfile", cmdUUID3) + decodedCmd3, err := mdm.DecodeCommand([]byte(rawXML3)) + require.NoError(t, err) + _, err = mdmStorage.EnqueueCommand(ctx, []string{macH.UUID}, &mdm.CommandWithSubtype{ + Command: *decodedCmd3, + Subtype: mdm.CommandSubtypeNone, + Name: name255, + }) + require.NoError(t, err) + + var storedName3 sql.NullString + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { + return sqlx.GetContext(ctx, q, &storedName3, `SELECT name FROM nano_commands WHERE command_uuid = ?`, cmdUUID3) + }) + require.True(t, storedName3.Valid) + require.Equal(t, name255, storedName3.String) + require.Len(t, []rune(storedName3.String), 255) + + // Test 4: Name longer than 255 ASCII characters — should be truncated to 255 + cmdUUID4 := uuid.New().String() + name300 := strings.Repeat("b", 300) + rawXML4 := createRawAppleCmd("InstallProfile", cmdUUID4) + decodedCmd4, err := mdm.DecodeCommand([]byte(rawXML4)) + require.NoError(t, err) + _, err = mdmStorage.EnqueueCommand(ctx, []string{macH.UUID}, &mdm.CommandWithSubtype{ + Command: *decodedCmd4, + Subtype: mdm.CommandSubtypeNone, + Name: name300, + }) + require.NoError(t, err) + + var storedName4 sql.NullString + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { + return sqlx.GetContext(ctx, q, &storedName4, `SELECT name FROM nano_commands WHERE command_uuid = ?`, cmdUUID4) + }) + require.True(t, storedName4.Valid) + require.Equal(t, strings.Repeat("b", 255), storedName4.String) + require.Len(t, []rune(storedName4.String), 255) + + // Test 5: Name with multi-byte utf8mb4 characters exceeding 255 — should truncate to 255 runes (not bytes) + cmdUUID5 := uuid.New().String() + // Each emoji is a single rune but 4 bytes in utf8mb4. Build a 300-rune string. + nameMultibyte := strings.Repeat("🍎", 300) + require.Len(t, []rune(nameMultibyte), 300) + rawXML5 := createRawAppleCmd("InstallProfile", cmdUUID5) + decodedCmd5, err := mdm.DecodeCommand([]byte(rawXML5)) + require.NoError(t, err) + _, err = mdmStorage.EnqueueCommand(ctx, []string{macH.UUID}, &mdm.CommandWithSubtype{ + Command: *decodedCmd5, + Subtype: mdm.CommandSubtypeNone, + Name: nameMultibyte, + }) + require.NoError(t, err) + + var storedName5 sql.NullString + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { + return sqlx.GetContext(ctx, q, &storedName5, `SELECT name FROM nano_commands WHERE command_uuid = ?`, cmdUUID5) + }) + require.True(t, storedName5.Valid) + require.Equal(t, strings.Repeat("🍎", 255), storedName5.String) + require.Len(t, []rune(storedName5.String), 255) +} + func testCleanUpMDMManagedCertificates(t *testing.T, ds *Datastore) { ctx := t.Context() diff --git a/server/datastore/mysql/migrations/tables/20260409153713_AddNameToNanoCommands.go b/server/datastore/mysql/migrations/tables/20260409153713_AddNameToNanoCommands.go new file mode 100644 index 0000000000..ecb4eb844d --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20260409153713_AddNameToNanoCommands.go @@ -0,0 +1,57 @@ +package tables + +import ( + "database/sql" + "fmt" +) + +func init() { + MigrationClient.AddMigration(Up_20260409153713, Down_20260409153713) +} + +func Up_20260409153713(tx *sql.Tx) error { + if !columnExists(tx, "nano_commands", "name") { + _, err := tx.Exec(` +ALTER TABLE nano_commands ADD COLUMN name varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL`) + if err != nil { + return fmt.Errorf("failed to add nano_commands.name column: %w", err) + } + } + + // Recreate the view to include the new name column + _, err := tx.Exec(` + CREATE OR REPLACE SQL SECURITY INVOKER VIEW nano_view_queue AS +SELECT + q.id COLLATE utf8mb4_unicode_ci AS id, + q.created_at, + q.active, + q.priority, + c.command_uuid COLLATE utf8mb4_unicode_ci AS command_uuid, + c.request_type COLLATE utf8mb4_unicode_ci AS request_type, + c.command COLLATE utf8mb4_unicode_ci AS command, + c.name AS name, + r.updated_at AS result_updated_at, + r.status COLLATE utf8mb4_unicode_ci AS status, + r.result COLLATE utf8mb4_unicode_ci AS result +FROM + nano_enrollment_queue AS q + + INNER JOIN nano_commands AS c + ON q.command_uuid = c.command_uuid + + LEFT JOIN nano_command_results r + ON r.command_uuid = q.command_uuid AND r.id = q.id +ORDER BY + q.priority DESC, + q.created_at; + `) + if err != nil { + return fmt.Errorf("failed to recreate nano_view_queue with name column: %w", err) + } + + return nil +} + +func Down_20260409153713(_ *sql.Tx) error { + return nil +} diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql index 02f9a67183..182afecf6f 100644 --- a/server/datastore/mysql/schema.sql +++ b/server/datastore/mysql/schema.sql @@ -1933,9 +1933,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=512 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=513 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'),(393,20250609112613,1,'2020-01-01 01:01:01'),(394,20250613103810,1,'2020-01-01 01:01:01'),(395,20250616193950,1,'2020-01-01 01:01:01'),(396,20250624140757,1,'2020-01-01 01:01:01'),(397,20250626130239,1,'2020-01-01 01:01:01'),(398,20250629131032,1,'2020-01-01 01:01:01'),(399,20250701155654,1,'2020-01-01 01:01:01'),(400,20250707095725,1,'2020-01-01 01:01:01'),(401,20250716152435,1,'2020-01-01 01:01:01'),(402,20250718091828,1,'2020-01-01 01:01:01'),(403,20250728122229,1,'2020-01-01 01:01:01'),(404,20250731122715,1,'2020-01-01 01:01:01'),(405,20250731151000,1,'2020-01-01 01:01:01'),(406,20250803000000,1,'2020-01-01 01:01:01'),(407,20250805083116,1,'2020-01-01 01:01:01'),(408,20250807140441,1,'2020-01-01 01:01:01'),(409,20250808000000,1,'2020-01-01 01:01:01'),(410,20250811155036,1,'2020-01-01 01:01:01'),(411,20250813205039,1,'2020-01-01 01:01:01'),(412,20250814123333,1,'2020-01-01 01:01:01'),(413,20250815130115,1,'2020-01-01 01:01:01'),(414,20250816115553,1,'2020-01-01 01:01:01'),(415,20250817154557,1,'2020-01-01 01:01:01'),(416,20250825113751,1,'2020-01-01 01:01:01'),(417,20250827113140,1,'2020-01-01 01:01:01'),(418,20250828120836,1,'2020-01-01 01:01:01'),(419,20250902112642,1,'2020-01-01 01:01:01'),(420,20250904091745,1,'2020-01-01 01:01:01'),(421,20250905090000,1,'2020-01-01 01:01:01'),(422,20250922083056,1,'2020-01-01 01:01:01'),(423,20250923120000,1,'2020-01-01 01:01:01'),(424,20250926123048,1,'2020-01-01 01:01:01'),(425,20251015103505,1,'2020-01-01 01:01:01'),(426,20251015103600,1,'2020-01-01 01:01:01'),(427,20251015103700,1,'2020-01-01 01:01:01'),(428,20251015103800,1,'2020-01-01 01:01:01'),(429,20251015103900,1,'2020-01-01 01:01:01'),(430,20251028140000,1,'2020-01-01 01:01:01'),(431,20251028140100,1,'2020-01-01 01:01:01'),(432,20251028140110,1,'2020-01-01 01:01:01'),(433,20251028140200,1,'2020-01-01 01:01:01'),(434,20251028140300,1,'2020-01-01 01:01:01'),(435,20251028140400,1,'2020-01-01 01:01:01'),(436,20251031154558,1,'2020-01-01 01:01:01'),(437,20251103160848,1,'2020-01-01 01:01:01'),(438,20251104112849,1,'2020-01-01 01:01:01'),(439,20251106000000,1,'2020-01-01 01:01:01'),(440,20251107164629,1,'2020-01-01 01:01:01'),(441,20251107170854,1,'2020-01-01 01:01:01'),(442,20251110172137,1,'2020-01-01 01:01:01'),(443,20251111153133,1,'2020-01-01 01:01:01'),(444,20251117020000,1,'2020-01-01 01:01:01'),(445,20251117020100,1,'2020-01-01 01:01:01'),(446,20251117020200,1,'2020-01-01 01:01:01'),(447,20251121100000,1,'2020-01-01 01:01:01'),(448,20251121124239,1,'2020-01-01 01:01:01'),(449,20251124090450,1,'2020-01-01 01:01:01'),(450,20251124135808,1,'2020-01-01 01:01:01'),(451,20251124140138,1,'2020-01-01 01:01:01'),(452,20251124162948,1,'2020-01-01 01:01:01'),(453,20251127113559,1,'2020-01-01 01:01:01'),(454,20251202162232,1,'2020-01-01 01:01:01'),(455,20251203170808,1,'2020-01-01 01:01:01'),(456,20251207050413,1,'2020-01-01 01:01:01'),(457,20251208215800,1,'2020-01-01 01:01:01'),(458,20251209221730,1,'2020-01-01 01:01:01'),(459,20251209221850,1,'2020-01-01 01:01:01'),(460,20251215163721,1,'2020-01-01 01:01:01'),(461,20251217000000,1,'2020-01-01 01:01:01'),(462,20251217120000,1,'2020-01-01 01:01:01'),(463,20251229000000,1,'2020-01-01 01:01:01'),(464,20251229000010,1,'2020-01-01 01:01:01'),(465,20251229000020,1,'2020-01-01 01:01:01'),(466,20260106000000,1,'2020-01-01 01:01:01'),(467,20260108200708,1,'2020-01-01 01:01:01'),(468,20260108214732,1,'2020-01-01 01:01:01'),(469,20260109231821,1,'2020-01-01 01:01:01'),(470,20260113012054,1,'2020-01-01 01:01:01'),(471,20260124200020,1,'2020-01-01 01:01:01'),(472,20260126150840,1,'2020-01-01 01:01:01'),(473,20260126210724,1,'2020-01-01 01:01:01'),(474,20260202151756,1,'2020-01-01 01:01:01'),(475,20260205184907,1,'2020-01-01 01:01:01'),(476,20260210151544,1,'2020-01-01 01:01:01'),(477,20260210155109,1,'2020-01-01 01:01:01'),(478,20260210181120,1,'2020-01-01 01:01:01'),(479,20260211200153,1,'2020-01-01 01:01:01'),(480,20260217141240,1,'2020-01-01 01:01:01'),(481,20260217200906,1,'2020-01-01 01:01:01'),(482,20260218175704,1,'2020-01-01 01:01:01'),(483,20260314120000,1,'2020-01-01 01:01:01'),(484,20260316120000,1,'2020-01-01 01:01:01'),(485,20260316120001,1,'2020-01-01 01:01:01'),(486,20260316120002,1,'2020-01-01 01:01:01'),(487,20260316120003,1,'2020-01-01 01:01:01'),(488,20260316120004,1,'2020-01-01 01:01:01'),(489,20260316120005,1,'2020-01-01 01:01:01'),(490,20260316120006,1,'2020-01-01 01:01:01'),(491,20260316120007,1,'2020-01-01 01:01:01'),(492,20260316120008,1,'2020-01-01 01:01:01'),(493,20260316120009,1,'2020-01-01 01:01:01'),(494,20260316120010,1,'2020-01-01 01:01:01'),(495,20260317120000,1,'2020-01-01 01:01:01'),(496,20260318184559,1,'2020-01-01 01:01:01'),(497,20260319120000,1,'2020-01-01 01:01:01'),(498,20260323144117,1,'2020-01-01 01:01:01'),(499,20260324161944,1,'2020-01-01 01:01:01'),(500,20260324223334,1,'2020-01-01 01:01:01'),(501,20260326131501,1,'2020-01-01 01:01:01'),(502,20260326210603,1,'2020-01-01 01:01:01'),(503,20260331000000,1,'2020-01-01 01:01:01'),(504,20260401153000,1,'2020-01-01 01:01:01'),(505,20260401153001,1,'2020-01-01 01:01:01'),(506,20260401153503,1,'2020-01-01 01:01:01'),(507,20260403120000,1,'2020-01-01 01:01:01'),(508,20260406114157,1,'2020-01-01 01:01:01'),(509,20260407185144,1,'2020-01-01 01:01:01'),(510,20260407214038,1,'2020-01-01 01:01:01'),(511,20260408175311,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'),(394,20250613103810,1,'2020-01-01 01:01:01'),(395,20250616193950,1,'2020-01-01 01:01:01'),(396,20250624140757,1,'2020-01-01 01:01:01'),(397,20250626130239,1,'2020-01-01 01:01:01'),(398,20250629131032,1,'2020-01-01 01:01:01'),(399,20250701155654,1,'2020-01-01 01:01:01'),(400,20250707095725,1,'2020-01-01 01:01:01'),(401,20250716152435,1,'2020-01-01 01:01:01'),(402,20250718091828,1,'2020-01-01 01:01:01'),(403,20250728122229,1,'2020-01-01 01:01:01'),(404,20250731122715,1,'2020-01-01 01:01:01'),(405,20250731151000,1,'2020-01-01 01:01:01'),(406,20250803000000,1,'2020-01-01 01:01:01'),(407,20250805083116,1,'2020-01-01 01:01:01'),(408,20250807140441,1,'2020-01-01 01:01:01'),(409,20250808000000,1,'2020-01-01 01:01:01'),(410,20250811155036,1,'2020-01-01 01:01:01'),(411,20250813205039,1,'2020-01-01 01:01:01'),(412,20250814123333,1,'2020-01-01 01:01:01'),(413,20250815130115,1,'2020-01-01 01:01:01'),(414,20250816115553,1,'2020-01-01 01:01:01'),(415,20250817154557,1,'2020-01-01 01:01:01'),(416,20250825113751,1,'2020-01-01 01:01:01'),(417,20250827113140,1,'2020-01-01 01:01:01'),(418,20250828120836,1,'2020-01-01 01:01:01'),(419,20250902112642,1,'2020-01-01 01:01:01'),(420,20250904091745,1,'2020-01-01 01:01:01'),(421,20250905090000,1,'2020-01-01 01:01:01'),(422,20250922083056,1,'2020-01-01 01:01:01'),(423,20250923120000,1,'2020-01-01 01:01:01'),(424,20250926123048,1,'2020-01-01 01:01:01'),(425,20251015103505,1,'2020-01-01 01:01:01'),(426,20251015103600,1,'2020-01-01 01:01:01'),(427,20251015103700,1,'2020-01-01 01:01:01'),(428,20251015103800,1,'2020-01-01 01:01:01'),(429,20251015103900,1,'2020-01-01 01:01:01'),(430,20251028140000,1,'2020-01-01 01:01:01'),(431,20251028140100,1,'2020-01-01 01:01:01'),(432,20251028140110,1,'2020-01-01 01:01:01'),(433,20251028140200,1,'2020-01-01 01:01:01'),(434,20251028140300,1,'2020-01-01 01:01:01'),(435,20251028140400,1,'2020-01-01 01:01:01'),(436,20251031154558,1,'2020-01-01 01:01:01'),(437,20251103160848,1,'2020-01-01 01:01:01'),(438,20251104112849,1,'2020-01-01 01:01:01'),(439,20251106000000,1,'2020-01-01 01:01:01'),(440,20251107164629,1,'2020-01-01 01:01:01'),(441,20251107170854,1,'2020-01-01 01:01:01'),(442,20251110172137,1,'2020-01-01 01:01:01'),(443,20251111153133,1,'2020-01-01 01:01:01'),(444,20251117020000,1,'2020-01-01 01:01:01'),(445,20251117020100,1,'2020-01-01 01:01:01'),(446,20251117020200,1,'2020-01-01 01:01:01'),(447,20251121100000,1,'2020-01-01 01:01:01'),(448,20251121124239,1,'2020-01-01 01:01:01'),(449,20251124090450,1,'2020-01-01 01:01:01'),(450,20251124135808,1,'2020-01-01 01:01:01'),(451,20251124140138,1,'2020-01-01 01:01:01'),(452,20251124162948,1,'2020-01-01 01:01:01'),(453,20251127113559,1,'2020-01-01 01:01:01'),(454,20251202162232,1,'2020-01-01 01:01:01'),(455,20251203170808,1,'2020-01-01 01:01:01'),(456,20251207050413,1,'2020-01-01 01:01:01'),(457,20251208215800,1,'2020-01-01 01:01:01'),(458,20251209221730,1,'2020-01-01 01:01:01'),(459,20251209221850,1,'2020-01-01 01:01:01'),(460,20251215163721,1,'2020-01-01 01:01:01'),(461,20251217000000,1,'2020-01-01 01:01:01'),(462,20251217120000,1,'2020-01-01 01:01:01'),(463,20251229000000,1,'2020-01-01 01:01:01'),(464,20251229000010,1,'2020-01-01 01:01:01'),(465,20251229000020,1,'2020-01-01 01:01:01'),(466,20260106000000,1,'2020-01-01 01:01:01'),(467,20260108200708,1,'2020-01-01 01:01:01'),(468,20260108214732,1,'2020-01-01 01:01:01'),(469,20260109231821,1,'2020-01-01 01:01:01'),(470,20260113012054,1,'2020-01-01 01:01:01'),(471,20260124200020,1,'2020-01-01 01:01:01'),(472,20260126150840,1,'2020-01-01 01:01:01'),(473,20260126210724,1,'2020-01-01 01:01:01'),(474,20260202151756,1,'2020-01-01 01:01:01'),(475,20260205184907,1,'2020-01-01 01:01:01'),(476,20260210151544,1,'2020-01-01 01:01:01'),(477,20260210155109,1,'2020-01-01 01:01:01'),(478,20260210181120,1,'2020-01-01 01:01:01'),(479,20260211200153,1,'2020-01-01 01:01:01'),(480,20260217141240,1,'2020-01-01 01:01:01'),(481,20260217200906,1,'2020-01-01 01:01:01'),(482,20260218175704,1,'2020-01-01 01:01:01'),(483,20260314120000,1,'2020-01-01 01:01:01'),(484,20260316120000,1,'2020-01-01 01:01:01'),(485,20260316120001,1,'2020-01-01 01:01:01'),(486,20260316120002,1,'2020-01-01 01:01:01'),(487,20260316120003,1,'2020-01-01 01:01:01'),(488,20260316120004,1,'2020-01-01 01:01:01'),(489,20260316120005,1,'2020-01-01 01:01:01'),(490,20260316120006,1,'2020-01-01 01:01:01'),(491,20260316120007,1,'2020-01-01 01:01:01'),(492,20260316120008,1,'2020-01-01 01:01:01'),(493,20260316120009,1,'2020-01-01 01:01:01'),(494,20260316120010,1,'2020-01-01 01:01:01'),(495,20260317120000,1,'2020-01-01 01:01:01'),(496,20260318184559,1,'2020-01-01 01:01:01'),(497,20260319120000,1,'2020-01-01 01:01:01'),(498,20260323144117,1,'2020-01-01 01:01:01'),(499,20260324161944,1,'2020-01-01 01:01:01'),(500,20260324223334,1,'2020-01-01 01:01:01'),(501,20260326131501,1,'2020-01-01 01:01:01'),(502,20260326210603,1,'2020-01-01 01:01:01'),(503,20260331000000,1,'2020-01-01 01:01:01'),(504,20260401153000,1,'2020-01-01 01:01:01'),(505,20260401153001,1,'2020-01-01 01:01:01'),(506,20260401153503,1,'2020-01-01 01:01:01'),(507,20260403120000,1,'2020-01-01 01:01:01'),(508,20260406114157,1,'2020-01-01 01:01:01'),(509,20260407185144,1,'2020-01-01 01:01:01'),(510,20260407214038,1,'2020-01-01 01:01:01'),(511,20260408175311,1,'2020-01-01 01:01:01'),(512,20260409153713,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` ( @@ -2005,6 +2005,7 @@ CREATE TABLE `nano_commands` ( `created_at` timestamp(6) NULL DEFAULT CURRENT_TIMESTAMP(6), `updated_at` timestamp(6) NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), `subtype` enum('None','ProfileWithSecrets') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'None', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, PRIMARY KEY (`command_uuid`), CONSTRAINT `nano_commands_chk_1` CHECK ((`command_uuid` <> _utf8mb4'')), CONSTRAINT `nano_commands_chk_2` CHECK ((`request_type` <> _utf8mb4'')) @@ -2161,6 +2162,7 @@ SET @saved_cs_client = @@character_set_client; 1 AS `command_uuid`, 1 AS `request_type`, 1 AS `command`, + 1 AS `name`, 1 AS `result_updated_at`, 1 AS `status`, 1 AS `result`*/; @@ -3315,7 +3317,7 @@ CREATE TABLE `yara_rules` ( /*!50001 SET collation_connection = utf8mb4_unicode_ci */; /*!50001 CREATE ALGORITHM=UNDEFINED */ /*!50013 DEFINER=`root`@`%` SQL SECURITY INVOKER */ -/*!50001 VIEW `nano_view_queue` AS select (`q`.`id` collate utf8mb4_unicode_ci) AS `id`,`q`.`created_at` AS `created_at`,`q`.`active` AS `active`,`q`.`priority` AS `priority`,(`c`.`command_uuid` collate utf8mb4_unicode_ci) AS `command_uuid`,(`c`.`request_type` collate utf8mb4_unicode_ci) AS `request_type`,(`c`.`command` collate utf8mb4_unicode_ci) AS `command`,`r`.`updated_at` AS `result_updated_at`,(`r`.`status` collate utf8mb4_unicode_ci) AS `status`,(`r`.`result` collate utf8mb4_unicode_ci) AS `result` from ((`nano_enrollment_queue` `q` join `nano_commands` `c` on((`q`.`command_uuid` = `c`.`command_uuid`))) left join `nano_command_results` `r` on(((`r`.`command_uuid` = `q`.`command_uuid`) and (`r`.`id` = `q`.`id`)))) order by `q`.`priority` desc,`q`.`created_at` */; +/*!50001 VIEW `nano_view_queue` AS select (`q`.`id` collate utf8mb4_unicode_ci) AS `id`,`q`.`created_at` AS `created_at`,`q`.`active` AS `active`,`q`.`priority` AS `priority`,(`c`.`command_uuid` collate utf8mb4_unicode_ci) AS `command_uuid`,(`c`.`request_type` collate utf8mb4_unicode_ci) AS `request_type`,(`c`.`command` collate utf8mb4_unicode_ci) AS `command`,`c`.`name` AS `name`,`r`.`updated_at` AS `result_updated_at`,(`r`.`status` collate utf8mb4_unicode_ci) AS `status`,(`r`.`result` collate utf8mb4_unicode_ci) AS `result` from ((`nano_enrollment_queue` `q` join `nano_commands` `c` on((`q`.`command_uuid` = `c`.`command_uuid`))) left join `nano_command_results` `r` on(((`r`.`command_uuid` = `q`.`command_uuid`) and (`r`.`id` = `q`.`id`)))) order by `q`.`priority` desc,`q`.`created_at` */; /*!50001 SET character_set_client = @saved_cs_client */; /*!50001 SET character_set_results = @saved_cs_results */; /*!50001 SET collation_connection = @saved_col_connection */; diff --git a/server/fleet/apple_mdm.go b/server/fleet/apple_mdm.go index 99f5b4ed18..94228eb467 100644 --- a/server/fleet/apple_mdm.go +++ b/server/fleet/apple_mdm.go @@ -30,8 +30,8 @@ var ( ) type MDMAppleCommandIssuer interface { - InstallProfile(ctx context.Context, hostUUIDs []string, profile mobileconfig.Mobileconfig, uuid string) error - RemoveProfile(ctx context.Context, hostUUIDs []string, identifier string, uuid string) error + InstallProfile(ctx context.Context, hostUUIDs []string, profile mobileconfig.Mobileconfig, uuid string, name string) error + RemoveProfile(ctx context.Context, hostUUIDs []string, identifier string, uuid string, name string) error DeviceLock(ctx context.Context, host *Host, uuid string) (unlockPIN string, err error) EnableLostMode(ctx context.Context, host *Host, commandUUID string, orgName string) error DisableLostMode(ctx context.Context, host *Host, commandUUID string) error @@ -631,6 +631,8 @@ type MDMAppleCommand struct { // to authorize the user to see the command, it is not returned as part of // the response payload. TeamID *uint `json:"-" db:"team_id"` + // Name is the optional human-readable name of the command, used for indicating the profile added/removed, if any. + Name *string `json:"name" db:"name"` } // MDMAppleSetupAssistant represents the setup assistant set for a given team diff --git a/server/fleet/apple_profiles.go b/server/fleet/apple_profiles.go index 9853dc8cdb..7f696c795d 100644 --- a/server/fleet/apple_profiles.go +++ b/server/fleet/apple_profiles.go @@ -16,6 +16,7 @@ import ( type CmdTarget struct { CmdUUID string ProfileIdentifier string + ProfileName string EnrollmentIDs []string } diff --git a/server/fleet/mdm.go b/server/fleet/mdm.go index c3027031f3..a8084437da 100644 --- a/server/fleet/mdm.go +++ b/server/fleet/mdm.go @@ -374,6 +374,8 @@ type MDMCommandResult struct { Hostname string `json:"hostname" db:"-"` // Payload is the contents of the command Payload []byte `json:"payload" db:"payload"` + // Name is the optional human-readable name of the command, currently used for profile name when adding/removing + Name *string `json:"name" db:"name"` // ResultsMetadata contains command-specific metadata. // VPP install commands include a "software_installed" boolean and // "vpp_verify_timeout_seconds" integer. @@ -400,6 +402,8 @@ type MDMCommand struct { // to authorize the user to see the command, it is not returned as part of // the response payload. TeamID *uint `json:"-" db:"team_id"` + // Name is the optional human-readable name of the command, currently used for profile name when adding/removing + Name *string `json:"name" db:"name"` // CommandStatus is the fleet computed field representing the status of the command // based on the MDM protocol status CommandStatus MDMCommandStatusFilter `json:"command_status" db:"command_status"` diff --git a/server/mdm/apple/commander.go b/server/mdm/apple/commander.go index 5cebd42230..eed349e0dc 100644 --- a/server/mdm/apple/commander.go +++ b/server/mdm/apple/commander.go @@ -45,12 +45,16 @@ func NewMDMAppleCommander(mdmStorage fleet.MDMAppleStore, mdmPushService nanomdm // InstallProfile sends the homonymous MDM command to the given hosts, it also // takes care of the base64 encoding of the provided profile bytes. -func (svc *MDMAppleCommander) InstallProfile(ctx context.Context, hostUUIDs []string, profile mobileconfig.Mobileconfig, uuid string) error { +func (svc *MDMAppleCommander) InstallProfile(ctx context.Context, hostUUIDs []string, profile mobileconfig.Mobileconfig, uuid string, name string) error { raw, err := svc.SignAndEncodeInstallProfile(ctx, profile, uuid) if err != nil { return err } - err = svc.EnqueueCommand(ctx, hostUUIDs, raw) + cmd, err := mdm.DecodeCommand([]byte(raw)) + if err != nil { + return ctxerr.Wrap(ctx, err, "decoding InstallProfile command") + } + err = svc.enqueueAndNotify(ctx, hostUUIDs, cmd, mdm.CommandSubtypeNone, name) return ctxerr.Wrap(ctx, err, "commander install profile") } @@ -80,7 +84,7 @@ func (svc *MDMAppleCommander) SignAndEncodeInstallProfile(ctx context.Context, p } // RemoveProfile sends the homonymous MDM command to the given hosts. -func (svc *MDMAppleCommander) RemoveProfile(ctx context.Context, hostUUIDs []string, profileIdentifier string, uuid string) error { +func (svc *MDMAppleCommander) RemoveProfile(ctx context.Context, hostUUIDs []string, profileIdentifier string, uuid string, name string) error { raw := fmt.Sprintf(` @@ -96,7 +100,11 @@ func (svc *MDMAppleCommander) RemoveProfile(ctx context.Context, hostUUIDs []str `, uuid, profileIdentifier) - err := svc.EnqueueCommand(ctx, hostUUIDs, raw) + cmd, err := mdm.DecodeCommand([]byte(raw)) + if err != nil { + return ctxerr.Wrap(ctx, err, "decoding RemoveProfile command") + } + err = svc.enqueueAndNotify(ctx, hostUUIDs, cmd, mdm.CommandSubtypeNone, name) return ctxerr.Wrap(ctx, err, "commander remove profile") } @@ -193,7 +201,7 @@ func (svc *MDMAppleCommander) EnableLostMode(ctx context.Context, host *fleet.Ho cmd, err := mdm.DecodeCommand([]byte(raw)) if err != nil { - return ctxerr.Wrap(ctx, err, "decoding command") + return ctxerr.Wrap(ctx, err, "decoding EnableLostMode command") } if err := svc.storage.EnqueueDeviceLockCommand(ctx, host, cmd, ""); err != nil { @@ -263,7 +271,7 @@ func (svc *MDMAppleCommander) EraseDevice(ctx context.Context, host *fleet.Host, cmd, err := mdm.DecodeCommand([]byte(raw)) if err != nil { - return ctxerr.Wrap(ctx, err, "decoding command") + return ctxerr.Wrap(ctx, err, "decoding DeviceWipe command") } if err := svc.storage.EnqueueDeviceWipeCommand(ctx, host, cmd); err != nil { @@ -516,7 +524,7 @@ func (svc *MDMAppleCommander) ClearPasscode(ctx context.Context, hostUUIDs []str } cmd.Command.RequestType = fleet.AppleMDMCommandTypeClearPasscode - return svc.enqueueAndNotify(ctx, hostUUIDs, cmd, mdm.CommandSubtypeNone) + return svc.enqueueAndNotify(ctx, hostUUIDs, cmd, mdm.CommandSubtypeNone, "") } // EnqueueCommand takes care of enqueuing the commands and sending push @@ -531,14 +539,14 @@ func (svc *MDMAppleCommander) EnqueueCommand(ctx context.Context, hostUUIDs []st return ctxerr.Wrap(ctx, err, "decoding command") } - return svc.enqueueAndNotify(ctx, hostUUIDs, cmd, mdm.CommandSubtypeNone) + return svc.enqueueAndNotify(ctx, hostUUIDs, cmd, mdm.CommandSubtypeNone, "") } func (svc *MDMAppleCommander) enqueueAndNotify(ctx context.Context, hostUUIDs []string, cmd *mdm.Command, - subtype mdm.CommandSubtype, + subtype mdm.CommandSubtype, name string, ) error { if _, err := svc.storage.EnqueueCommand(ctx, hostUUIDs, - &mdm.CommandWithSubtype{Command: *cmd, Subtype: subtype}); err != nil { + &mdm.CommandWithSubtype{Command: *cmd, Subtype: subtype, Name: name}); err != nil { return ctxerr.Wrap(ctx, err, "enqueuing command") } @@ -551,7 +559,7 @@ func (svc *MDMAppleCommander) enqueueAndNotify(ctx context.Context, hostUUIDs [] // EnqueueCommandInstallProfileWithSecrets is a special case of EnqueueCommand that does not expand secret variables. // Secret variables are expanded when the command is sent to the device, and secrets are never stored in the database unencrypted. func (svc *MDMAppleCommander) EnqueueCommandInstallProfileWithSecrets(ctx context.Context, hostUUIDs []string, - rawCommand mobileconfig.Mobileconfig, commandUUID string, + rawCommand mobileconfig.Mobileconfig, commandUUID string, name string, ) error { cmd := &mdm.Command{ CommandUUID: commandUUID, @@ -559,7 +567,7 @@ func (svc *MDMAppleCommander) EnqueueCommandInstallProfileWithSecrets(ctx contex } cmd.Command.RequestType = "InstallProfile" - return svc.enqueueAndNotify(ctx, hostUUIDs, cmd, mdm.CommandSubtypeProfileWithSecrets) + return svc.enqueueAndNotify(ctx, hostUUIDs, cmd, mdm.CommandSubtypeProfileWithSecrets, name) } func (svc *MDMAppleCommander) SendNotifications(ctx context.Context, hostUUIDs []string) error { diff --git a/server/mdm/apple/commander_test.go b/server/mdm/apple/commander_test.go index 67ed513d24..22dcce2e3c 100644 --- a/server/mdm/apple/commander_test.go +++ b/server/mdm/apple/commander_test.go @@ -104,7 +104,7 @@ func TestMDMAppleCommander(t *testing.T) { } cmdUUID := uuid.New().String() - err := cmdr.InstallProfile(ctx, hostUUIDs, mc, cmdUUID) + err := cmdr.InstallProfile(ctx, hostUUIDs, mc, cmdUUID, "") require.NoError(t, err) require.True(t, mdmStorage.EnqueueCommandFuncInvoked) mdmStorage.EnqueueCommandFuncInvoked = false @@ -118,7 +118,7 @@ func TestMDMAppleCommander(t *testing.T) { return nil, nil } cmdUUID = uuid.New().String() - err = cmdr.RemoveProfile(ctx, hostUUIDs, payloadIdentifier, cmdUUID) + err = cmdr.RemoveProfile(ctx, hostUUIDs, payloadIdentifier, cmdUUID, "") require.True(t, mdmStorage.EnqueueCommandFuncInvoked) mdmStorage.EnqueueCommandFuncInvoked = false require.True(t, mdmStorage.RetrievePushInfoFuncInvoked) @@ -673,3 +673,140 @@ func TestMDMAppleCommanderClearPasscode(t *testing.T) { require.True(t, mdmStorage.EnqueueCommandFuncInvoked) require.True(t, mdmStorage.RetrievePushInfoFuncInvoked) } + +func TestMDMAppleCommanderPassesCommandName(t *testing.T) { + ctx := context.Background() + mdmStorage := &mdmmock.MDMAppleStore{} + pushFactory, _ := newMockAPNSPushProviderFactory() + pusher := nanomdm_pushsvc.New( + mdmStorage, + mdmStorage, + pushFactory, + stdlogfmt.New(), + ) + cmdr := NewMDMAppleCommander(mdmStorage, pusher) + + hostUUIDs := []string{"A"} + payloadName := "com.foo.bar" + payloadIdentifier := "com-foo-bar" + mc := mobileconfigForTest(payloadName, payloadIdentifier) + + mdmStorage.RetrievePushInfoFunc = func(p0 context.Context, targetUUIDs []string) (map[string]*mdm.Push, error) { + pushes := make(map[string]*mdm.Push, len(targetUUIDs)) + for _, uuid := range targetUUIDs { + pushes[uuid] = &mdm.Push{ + PushMagic: "magic" + uuid, + Token: []byte("token" + uuid), + Topic: "topic" + uuid, + } + } + return pushes, nil + } + mdmStorage.RetrievePushCertFunc = func(ctx context.Context, topic string) (*tls.Certificate, string, error) { + cert, err := tls.LoadX509KeyPair("../../service/testdata/server.pem", "../../service/testdata/server.key") + return &cert, "", err + } + mdmStorage.IsPushCertStaleFunc = func(ctx context.Context, topic string, staleToken string) (bool, error) { + return false, nil + } + mdmStorage.GetAllMDMConfigAssetsByNameFunc = func(ctx context.Context, assetNames []fleet.MDMAssetName, + _ sqlx.QueryerContext, + ) (map[fleet.MDMAssetName]fleet.MDMConfigAsset, error) { + certPEM, err := os.ReadFile("../../service/testdata/server.pem") + require.NoError(t, err) + keyPEM, err := os.ReadFile("../../service/testdata/server.key") + require.NoError(t, err) + return map[fleet.MDMAssetName]fleet.MDMConfigAsset{ + fleet.MDMAssetCACert: {Value: certPEM}, + fleet.MDMAssetCAKey: {Value: keyPEM}, + }, nil + } + + t.Run("InstallProfile with a name", func(t *testing.T) { + var gotName string + mdmStorage.EnqueueCommandFunc = func(ctx context.Context, id []string, cmd *mdm.CommandWithSubtype) (map[string]error, error) { + gotName = cmd.Name + return nil, nil + } + mdmStorage.EnqueueCommandFuncInvoked = false + + cmdUUID := uuid.New().String() + err := cmdr.InstallProfile(ctx, hostUUIDs, mc, cmdUUID, "My Profile") + require.NoError(t, err) + require.True(t, mdmStorage.EnqueueCommandFuncInvoked) + require.Equal(t, "My Profile", gotName) + }) + + t.Run("InstallProfile without a name", func(t *testing.T) { + var gotName string + mdmStorage.EnqueueCommandFunc = func(ctx context.Context, id []string, cmd *mdm.CommandWithSubtype) (map[string]error, error) { + gotName = cmd.Name + return nil, nil + } + mdmStorage.EnqueueCommandFuncInvoked = false + + cmdUUID := uuid.New().String() + err := cmdr.InstallProfile(ctx, hostUUIDs, mc, cmdUUID, "") + require.NoError(t, err) + require.True(t, mdmStorage.EnqueueCommandFuncInvoked) + require.Empty(t, gotName) + }) + + t.Run("RemoveProfile with a name", func(t *testing.T) { + var gotName string + mdmStorage.EnqueueCommandFunc = func(ctx context.Context, id []string, cmd *mdm.CommandWithSubtype) (map[string]error, error) { + gotName = cmd.Name + return nil, nil + } + mdmStorage.EnqueueCommandFuncInvoked = false + + cmdUUID := uuid.New().String() + err := cmdr.RemoveProfile(ctx, hostUUIDs, payloadIdentifier, cmdUUID, "My Profile") + require.NoError(t, err) + require.True(t, mdmStorage.EnqueueCommandFuncInvoked) + require.Equal(t, "My Profile", gotName) + }) + + t.Run("EnqueueCommand no name", func(t *testing.T) { + var gotName string + mdmStorage.EnqueueCommandFunc = func(ctx context.Context, id []string, cmd *mdm.CommandWithSubtype) (map[string]error, error) { + gotName = cmd.Name + return nil, nil + } + mdmStorage.EnqueueCommandFuncInvoked = false + + cmdUUID := uuid.New().String() + rawCmd := fmt.Sprintf(` + + + + CommandUUID + %s + Command + + RequestType + ProfileList + + +`, cmdUUID) + err := cmdr.EnqueueCommand(ctx, hostUUIDs, rawCmd) + require.NoError(t, err) + require.True(t, mdmStorage.EnqueueCommandFuncInvoked) + require.Empty(t, gotName) + }) + + t.Run("EnqueueCommandInstallProfileWithSecrets with a name", func(t *testing.T) { + var gotName string + mdmStorage.EnqueueCommandFunc = func(ctx context.Context, id []string, cmd *mdm.CommandWithSubtype) (map[string]error, error) { + gotName = cmd.Name + return nil, nil + } + mdmStorage.EnqueueCommandFuncInvoked = false + + cmdUUID := uuid.New().String() + err := cmdr.EnqueueCommandInstallProfileWithSecrets(ctx, hostUUIDs, mc, cmdUUID, "Secret Profile") + require.NoError(t, err) + require.True(t, mdmStorage.EnqueueCommandFuncInvoked) + require.Equal(t, "Secret Profile", gotName) + }) +} diff --git a/server/mdm/apple/profile_processor.go b/server/mdm/apple/profile_processor.go index 5dfb62aafc..e23d0ae545 100644 --- a/server/mdm/apple/profile_processor.go +++ b/server/mdm/apple/profile_processor.go @@ -97,14 +97,15 @@ func ProcessAndEnqueueProfiles(ctx context.Context, switch op { case fleet.MDMOperationTypeInstall: if _, ok := profilesWithSecrets[profUUID]; ok { - err = commander.EnqueueCommandInstallProfileWithSecrets(ctx, target.EnrollmentIDs, profileContents[profUUID], target.CmdUUID) + err = commander.EnqueueCommandInstallProfileWithSecrets(ctx, target.EnrollmentIDs, profileContents[profUUID], target.CmdUUID, target.ProfileName) } else { - err = commander.InstallProfile(ctx, target.EnrollmentIDs, profileContents[profUUID], target.CmdUUID) + err = commander.InstallProfile(ctx, target.EnrollmentIDs, profileContents[profUUID], target.CmdUUID, target.ProfileName) } case fleet.MDMOperationTypeRemove: - err = commander.RemoveProfile(ctx, target.EnrollmentIDs, target.ProfileIdentifier, target.CmdUUID) + err = commander.RemoveProfile(ctx, target.EnrollmentIDs, target.ProfileIdentifier, target.CmdUUID, target.ProfileName) } + // Determine whether the command was enqueued (even if push notification failed). var e *APNSDeliveryError switch { case errors.As(err, &e): @@ -117,6 +118,7 @@ func ProcessAndEnqueueProfiles(ctx context.Context, default: ch <- remoteResult{nil, target.CmdUUID} } + } for profUUID, target := range installTargets { wgProd.Add(1) @@ -630,6 +632,7 @@ func preprocessProfileContents( addedTargets[tempProfUUID] = &fleet.CmdTarget{ CmdUUID: tempCmdUUID, ProfileIdentifier: target.ProfileIdentifier, + ProfileName: target.ProfileName, EnrollmentIDs: []string{enrollmentID}, } profileContents[tempProfUUID] = mobileconfig.Mobileconfig(hostContents) diff --git a/server/mdm/nanomdm/mdm/command.go b/server/mdm/nanomdm/mdm/command.go index c01bbdb3ea..beca666ea9 100644 --- a/server/mdm/nanomdm/mdm/command.go +++ b/server/mdm/nanomdm/mdm/command.go @@ -62,6 +62,7 @@ type Command struct { type CommandWithSubtype struct { Command Subtype CommandSubtype + Name string } // DecodeCommand unmarshals rawCommand into command diff --git a/server/mdm/nanomdm/storage/mysql/queue.go b/server/mdm/nanomdm/storage/mysql/queue.go index 8930cf88de..d07bd7a9ae 100644 --- a/server/mdm/nanomdm/storage/mysql/queue.go +++ b/server/mdm/nanomdm/storage/mysql/queue.go @@ -18,10 +18,18 @@ func enqueue(ctx context.Context, tx sqlx.ExtContext, ids []string, cmd *mdm.Com if len(ids) < 1 { return errors.New("no id(s) supplied to queue command to") } + var nameArg sql.NullString + if cmd.Name != "" { + name := cmd.Name + if runes := []rune(name); len(runes) > 255 { + name = string(runes[:255]) + } + nameArg = sql.NullString{String: name, Valid: true} + } _, err := tx.ExecContext( ctx, - `INSERT INTO nano_commands (command_uuid, request_type, command, subtype) VALUES (?, ?, ?, ?)`, - cmd.CommandUUID, cmd.Command.Command.RequestType, cmd.Raw, cmd.Subtype, + `INSERT INTO nano_commands (command_uuid, request_type, command, subtype, name) VALUES (?, ?, ?, ?, ?)`, + cmd.CommandUUID, cmd.Command.Command.RequestType, cmd.Raw, cmd.Subtype, nameArg, ) if err != nil { return err diff --git a/server/mdm/nanomdm/storage/mysql/schema.sql b/server/mdm/nanomdm/storage/mysql/schema.sql index 17c51888b6..6a18081068 100644 --- a/server/mdm/nanomdm/storage/mysql/schema.sql +++ b/server/mdm/nanomdm/storage/mysql/schema.sql @@ -144,6 +144,7 @@ CREATE TABLE nano_commands ( updated_at TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), subtype ENUM('None','ProfileWithSecrets') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'None', + name varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, PRIMARY KEY (command_uuid), @@ -231,6 +232,7 @@ SELECT c.command_uuid, c.request_type, c.command, + c.name, r.updated_at AS result_updated_at, r.status, r.result diff --git a/server/service/apple_mdm.go b/server/service/apple_mdm.go index 349616aaac..cd289f65f4 100644 --- a/server/service/apple_mdm.go +++ b/server/service/apple_mdm.go @@ -2330,7 +2330,7 @@ func (svc *Service) enqueueMDMAppleCommandRemoveEnrollmentProfile(ctx context.Co } cmdUUID := uuid.New().String() - err = svc.mdmAppleCommander.RemoveProfile(ctx, []string{nanoEnroll.ID}, apple_mdm.FleetPayloadIdentifier, cmdUUID) + err = svc.mdmAppleCommander.RemoveProfile(ctx, []string{nanoEnroll.ID}, apple_mdm.FleetPayloadIdentifier, cmdUUID, "") if err != nil { return ctxerr.Wrap(ctx, err, "enqueuing mdm apple remove profile command") } @@ -5262,6 +5262,7 @@ func ReconcileAppleProfiles( target = &fleet.CmdTarget{ CmdUUID: uuid.New().String(), ProfileIdentifier: p.ProfileIdentifier, + ProfileName: p.ProfileName, } installTargets[p.ProfileUUID] = target } @@ -5365,6 +5366,7 @@ func ReconcileAppleProfiles( target = &fleet.CmdTarget{ CmdUUID: uuid.New().String(), ProfileIdentifier: p.ProfileIdentifier, + ProfileName: p.ProfileName, } removeTargets[p.ProfileUUID] = target } @@ -5697,7 +5699,7 @@ func RenewSCEPCertificates( if err != nil { return ctxerr.Wrap(ctx, err, "generating enrollment profile for hosts without enroll reference") } - if err := renewMDMAppleEnrollmentProfile(ctx, ds, commander, logger, filteredAssocs, profile); err != nil { + if err := renewMDMAppleEnrollmentProfile(ctx, ds, commander, logger, filteredAssocs, profile, appConfig.OrgInfo.OrgName+" enrollment"); err != nil { return ctxerr.Wrap(ctx, err, "sending profile to hosts without associations") } } @@ -5738,7 +5740,7 @@ func RenewSCEPCertificates( } // each host with association needs a different enrollment profile, and thus a different command. - if err := renewMDMAppleEnrollmentProfile(ctx, ds, commander, logger, []fleet.SCEPIdentityAssociation{assoc}, profile); err != nil { + if err := renewMDMAppleEnrollmentProfile(ctx, ds, commander, logger, []fleet.SCEPIdentityAssociation{assoc}, profile, appConfig.OrgInfo.OrgName+" account driven enrollment"); err != nil { return ctxerr.Wrap(ctx, err, "sending account driven enrollment profile renewal to hosts") } } @@ -5767,7 +5769,7 @@ func RenewSCEPCertificates( } // each host with association needs a different enrollment profile, and thus a different command. - if err := renewMDMAppleEnrollmentProfile(ctx, ds, commander, logger, []fleet.SCEPIdentityAssociation{assoc}, profile); err != nil { + if err := renewMDMAppleEnrollmentProfile(ctx, ds, commander, logger, []fleet.SCEPIdentityAssociation{assoc}, profile, appConfig.OrgInfo.OrgName+" enrollment"); err != nil { return ctxerr.Wrap(ctx, err, "sending profile to hosts without associations") } } @@ -5805,7 +5807,7 @@ func RenewSCEPCertificates( return ctxerr.Wrap(ctx, err, "generating enrollment profile for hosts requiring ACME renewal") } - if err := renewMDMAppleEnrollmentProfile(ctx, ds, commander, logger, []fleet.SCEPIdentityAssociation{assoc}, profile); err != nil { + if err := renewMDMAppleEnrollmentProfile(ctx, ds, commander, logger, []fleet.SCEPIdentityAssociation{assoc}, profile, appConfig.OrgInfo.OrgName+" ACME enrollment"); err != nil { return ctxerr.Wrap(ctx, err, "sending ACME enrollment profile to hosts") } } @@ -5823,7 +5825,7 @@ func RenewSCEPCertificates( } if migrationEnrollmentProfile != "" && hasAssocsFromMigration { profileBytes := []byte(migrationEnrollmentProfile) - if err := renewMDMAppleEnrollmentProfile(ctx, ds, commander, logger, assocsFromMigration, profileBytes); err != nil { + if err := renewMDMAppleEnrollmentProfile(ctx, ds, commander, logger, assocsFromMigration, profileBytes, appConfig.OrgInfo.OrgName+" migration enrollment"); err != nil { return ctxerr.Wrap(ctx, err, "sending profile to hosts from migration") } } @@ -5838,6 +5840,7 @@ func renewMDMAppleEnrollmentProfile( logger *slog.Logger, assocs []fleet.SCEPIdentityAssociation, profile []byte, + profileName string, ) error { cmdUUID := uuid.NewString() var uuids []string @@ -5857,7 +5860,7 @@ func renewMDMAppleEnrollmentProfile( uuids = append(uuids, assoc.HostUUID) } - if err := commander.InstallProfile(ctx, uuids, profile, cmdUUID); err != nil { + if err := commander.InstallProfile(ctx, uuids, profile, cmdUUID, profileName); err != nil { return ctxerr.Wrapf(ctx, err, "sending InstallProfile command for hosts %s", uuids) } diff --git a/server/service/apple_mdm_test.go b/server/service/apple_mdm_test.go index 6189f57811..9810805a28 100644 --- a/server/service/apple_mdm_test.go +++ b/server/service/apple_mdm_test.go @@ -4638,6 +4638,7 @@ func TestRenewSCEPCertificatesBranches(t *testing.T) { error, ) { require.Equal(t, "InstallProfile", cmd.Command.Command.RequestType) + require.Equal(t, "fl33t enrollment", cmd.Name) wantCommandUUID = cmd.CommandUUID return map[string]error{}, nil } @@ -4681,6 +4682,7 @@ func TestRenewSCEPCertificatesBranches(t *testing.T) { error, ) { require.Equal(t, "InstallProfile", cmd.Command.Command.RequestType) + require.Equal(t, "fl33t enrollment", cmd.Name) wantCommandUUID = cmd.CommandUUID return map[string]error{}, nil } @@ -4740,6 +4742,7 @@ func TestRenewSCEPCertificatesBranches(t *testing.T) { error, ) { require.Equal(t, "InstallProfile", cmd.Command.Command.RequestType) + require.Equal(t, "fl33t account driven enrollment", cmd.Name) require.Equal(t, 1, len(id)) _, idAlreadyExists := wantCommandUUIDs[id[0]] // Should only get one for each host @@ -4794,6 +4797,7 @@ func TestRenewSCEPCertificatesBranches(t *testing.T) { error, ) { require.Equal(t, "InstallProfile", cmd.Command.Command.RequestType) + require.Equal(t, "fl33t account driven enrollment", cmd.Name) require.Equal(t, 1, len(id)) _, idAlreadyExists := wantCommandUUIDs[id[0]] // Should only get one for each host @@ -4985,6 +4989,7 @@ func TestRenewACMECertificatesBranches(t *testing.T) { }}, nil } appleStore.EnqueueCommandFunc = func(ctx context.Context, id []string, cmd *mdm.CommandWithSubtype) (map[string]error, error) { + require.Equal(t, "fl33t enrollment", cmd.Name) return map[string]error{}, nil } t.Cleanup(func() { @@ -5018,6 +5023,7 @@ func TestRenewACMECertificatesBranches(t *testing.T) { } appleStore.EnqueueCommandFunc = func(ctx context.Context, id []string, cmd *mdm.CommandWithSubtype) (map[string]error, error) { require.Equal(t, "InstallProfile", cmd.Command.Command.RequestType) + require.Equal(t, "fl33t ACME enrollment", cmd.Name) wantCommandUUID = cmd.CommandUUID // Verify the profile is an ACME profile by checking it contains the device serial var fullCmd micromdm.CommandPayload @@ -5064,6 +5070,7 @@ func TestRenewACMECertificatesBranches(t *testing.T) { } appleStore.EnqueueCommandFunc = func(ctx context.Context, id []string, cmd *mdm.CommandWithSubtype) (map[string]error, error) { require.Equal(t, "InstallProfile", cmd.Command.Command.RequestType) + require.Equal(t, "fl33t ACME enrollment", cmd.Name) wantCommandUUID = cmd.CommandUUID var fullCmd micromdm.CommandPayload require.NoError(t, plist.Unmarshal(cmd.Raw, &fullCmd)) @@ -5142,6 +5149,14 @@ func TestRenewACMECertificatesBranches(t *testing.T) { appleStore.EnqueueCommandFunc = func(ctx context.Context, id []string, cmd *mdm.CommandWithSubtype) (map[string]error, error) { require.Equal(t, "InstallProfile", cmd.Command.Command.RequestType) for _, hostUUID := range id { + switch hostUUID { + case "hostUUID-acme": + require.Equal(t, "fl33t ACME enrollment", cmd.Name) + case "hostUUID-scep": + require.Equal(t, "fl33t enrollment", cmd.Name) + default: + require.Failf(t, "Unexpected host UUID", "Unexpected host UUID: %s", hostUUID) + } enqueuedHostUUIDs[hostUUID] = true } return map[string]error{}, nil diff --git a/server/worker/apple_mdm.go b/server/worker/apple_mdm.go index 51548748f8..5bc9dbf04b 100644 --- a/server/worker/apple_mdm.go +++ b/server/worker/apple_mdm.go @@ -725,6 +725,7 @@ func (a *AppleMDM) installProfilesForEnrollingHost(ctx context.Context, hostUUID target := &fleet.CmdTarget{ CmdUUID: uuid.NewString(), ProfileIdentifier: profile.ProfileIdentifier, + ProfileName: profile.ProfileName, EnrollmentIDs: []string{hostUUID}, } installTargets[profile.ProfileUUID] = target