diff --git a/changes/22896-ui-windows-automatic-migration b/changes/22896-ui-windows-automatic-migration
new file mode 100644
index 000000000000..ae0234123bfd
--- /dev/null
+++ b/changes/22896-ui-windows-automatic-migration
@@ -0,0 +1 @@
+- add UI changes for windows mdm page and allow for automatic migration for windows hosts.
diff --git a/changes/22897-add-windows-migration-enabled-setting b/changes/22897-add-windows-migration-enabled-setting
new file mode 100644
index 000000000000..15866a98c7af
--- /dev/null
+++ b/changes/22897-add-windows-migration-enabled-setting
@@ -0,0 +1 @@
+* Added support for the new `windows_migration_enabled` setting (can be set via `fleetctl`, the `PATCH /api/latest/fleet/config` API endpoint and the UI). Requires a premium license.
diff --git a/cmd/fleetctl/gitops_test.go b/cmd/fleetctl/gitops_test.go
index 72d999823003..ad11066b2aa5 100644
--- a/cmd/fleetctl/gitops_test.go
+++ b/cmd/fleetctl/gitops_test.go
@@ -3231,6 +3231,31 @@ software:
}
}
+func TestGitOpsWindowsMigration(t *testing.T) {
+ cases := []struct {
+ file string
+ wantErr string
+ }{
+ // booleans are Windows MDM enabled and Windows migration enabled
+ {"testdata/gitops/global_config_windows_migration_true_true.yml", ""},
+ {"testdata/gitops/global_config_windows_migration_false_true.yml", "Windows MDM is not enabled"},
+ {"testdata/gitops/global_config_windows_migration_true_false.yml", ""},
+ {"testdata/gitops/global_config_windows_migration_false_false.yml", ""},
+ }
+ for _, c := range cases {
+ t.Run(filepath.Base(c.file), func(t *testing.T) {
+ setupFullGitOpsPremiumServer(t)
+
+ _, err := runAppNoChecks([]string{"gitops", "-f", c.file})
+ if c.wantErr == "" {
+ require.NoError(t, err)
+ } else {
+ require.ErrorContains(t, err, c.wantErr)
+ }
+ })
+ }
+}
+
type memKeyValueStore struct {
m sync.Map
}
diff --git a/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json b/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json
index c3779641775a..1cbede5abf54 100644
--- a/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json
+++ b/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json
@@ -118,6 +118,7 @@
"deadline_days": 7,
"grace_period_days": 3
},
+ "windows_migration_enabled": false,
"macos_migration": {
"enable": false,
"mode": "",
diff --git a/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml b/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml
index ff6fbaa22eae..042be511914c 100644
--- a/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml
+++ b/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml
@@ -27,6 +27,7 @@ spec:
volume_purchasing_program: null
windows_enabled_and_configured: false
enable_disk_encryption: false
+ windows_migration_enabled: false
macos_migration:
enable: false
mode: ""
diff --git a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json
index dea76a995b16..f8c421065b52 100644
--- a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json
+++ b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json
@@ -70,6 +70,7 @@
"deadline_days": 7,
"grace_period_days": 3
},
+ "windows_migration_enabled": false,
"macos_migration": {
"enable": false,
"mode": "",
diff --git a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml
index f6b8136407cd..5f6e877f163b 100644
--- a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml
+++ b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml
@@ -27,6 +27,7 @@ spec:
enabled_and_configured: false
windows_enabled_and_configured: false
enable_disk_encryption: false
+ windows_migration_enabled: false
macos_migration:
enable: false
mode: ""
diff --git a/cmd/fleetctl/testdata/gitops/global_config_windows_migration_false_false.yml b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_false_false.yml
new file mode 100644
index 000000000000..645be877d3a3
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_false_false.yml
@@ -0,0 +1,75 @@
+controls:
+ macos_settings:
+ windows_settings:
+ scripts:
+ enable_disk_encryption: false
+ macos_migration:
+ enable: false
+ mode: ""
+ webhook_url: ""
+ macos_setup:
+ bootstrap_package: null
+ enable_end_user_authentication: false
+ macos_setup_assistant: null
+ macos_updates:
+ deadline: null
+ minimum_version: null
+ windows_enabled_and_configured: false
+ windows_migration_enabled: false
+ windows_updates:
+ deadline_days: null
+ grace_period_days: null
+queries:
+policies:
+agent_options:
+ command_line_flags:
+ distributed_denylist_duration: 0
+ config:
+ decorators:
+ load:
+ - SELECT uuid AS host_uuid FROM system_info;
+ - SELECT hostname AS hostname FROM system_info;
+ options:
+ disable_distributed: false
+ distributed_interval: 10
+ distributed_plugin: tls
+ distributed_tls_max_attempts: 3
+ logger_tls_endpoint: /api/v1/osquery/log
+ pack_delimiter: /
+org_settings:
+ server_settings:
+ deferred_save_host: false
+ enable_analytics: true
+ live_query_disabled: false
+ query_report_cap: 2000
+ query_reports_disabled: false
+ scripts_disabled: false
+ server_url: $FLEET_SERVER_URL
+ ai_features_disabled: true
+ org_info:
+ contact_url: https://fleetdm.com/company/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: $ORG_NAME
+ smtp_settings:
+ sso_settings:
+ integrations:
+ mdm:
+ end_user_authentication:
+ webhook_settings:
+ fleet_desktop: # Applies to Fleet Premium only
+ transparency_url: https://fleetdm.com/transparency
+ host_expiry_settings: # Applies to all teams
+ host_expiry_enabled: false
+ activity_expiry_settings:
+ activity_expiry_enabled: true
+ activity_expiry_window: 60
+ features: # Features added to all teams
+ enable_host_users: true
+ enable_software_inventory: true
+ vulnerability_settings:
+ databases_path: ""
+ secrets: # These secrets are used to enroll hosts to the "All teams" team
+ - secret: SampleSecret123
+ - secret: ABC
+software:
diff --git a/cmd/fleetctl/testdata/gitops/global_config_windows_migration_false_true.yml b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_false_true.yml
new file mode 100644
index 000000000000..99cdf07c3918
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_false_true.yml
@@ -0,0 +1,75 @@
+controls:
+ macos_settings:
+ windows_settings:
+ scripts:
+ enable_disk_encryption: false
+ macos_migration:
+ enable: false
+ mode: ""
+ webhook_url: ""
+ macos_setup:
+ bootstrap_package: null
+ enable_end_user_authentication: false
+ macos_setup_assistant: null
+ macos_updates:
+ deadline: null
+ minimum_version: null
+ windows_enabled_and_configured: false
+ windows_migration_enabled: true
+ windows_updates:
+ deadline_days: null
+ grace_period_days: null
+queries:
+policies:
+agent_options:
+ command_line_flags:
+ distributed_denylist_duration: 0
+ config:
+ decorators:
+ load:
+ - SELECT uuid AS host_uuid FROM system_info;
+ - SELECT hostname AS hostname FROM system_info;
+ options:
+ disable_distributed: false
+ distributed_interval: 10
+ distributed_plugin: tls
+ distributed_tls_max_attempts: 3
+ logger_tls_endpoint: /api/v1/osquery/log
+ pack_delimiter: /
+org_settings:
+ server_settings:
+ deferred_save_host: false
+ enable_analytics: true
+ live_query_disabled: false
+ query_report_cap: 2000
+ query_reports_disabled: false
+ scripts_disabled: false
+ server_url: $FLEET_SERVER_URL
+ ai_features_disabled: true
+ org_info:
+ contact_url: https://fleetdm.com/company/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: $ORG_NAME
+ smtp_settings:
+ sso_settings:
+ integrations:
+ mdm:
+ end_user_authentication:
+ webhook_settings:
+ fleet_desktop: # Applies to Fleet Premium only
+ transparency_url: https://fleetdm.com/transparency
+ host_expiry_settings: # Applies to all teams
+ host_expiry_enabled: false
+ activity_expiry_settings:
+ activity_expiry_enabled: true
+ activity_expiry_window: 60
+ features: # Features added to all teams
+ enable_host_users: true
+ enable_software_inventory: true
+ vulnerability_settings:
+ databases_path: ""
+ secrets: # These secrets are used to enroll hosts to the "All teams" team
+ - secret: SampleSecret123
+ - secret: ABC
+software:
diff --git a/cmd/fleetctl/testdata/gitops/global_config_windows_migration_true_false.yml b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_true_false.yml
new file mode 100644
index 000000000000..c934e124a205
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_true_false.yml
@@ -0,0 +1,75 @@
+controls:
+ macos_settings:
+ windows_settings:
+ scripts:
+ enable_disk_encryption: false
+ macos_migration:
+ enable: false
+ mode: ""
+ webhook_url: ""
+ macos_setup:
+ bootstrap_package: null
+ enable_end_user_authentication: false
+ macos_setup_assistant: null
+ macos_updates:
+ deadline: null
+ minimum_version: null
+ windows_enabled_and_configured: true
+ windows_migration_enabled: false
+ windows_updates:
+ deadline_days: null
+ grace_period_days: null
+queries:
+policies:
+agent_options:
+ command_line_flags:
+ distributed_denylist_duration: 0
+ config:
+ decorators:
+ load:
+ - SELECT uuid AS host_uuid FROM system_info;
+ - SELECT hostname AS hostname FROM system_info;
+ options:
+ disable_distributed: false
+ distributed_interval: 10
+ distributed_plugin: tls
+ distributed_tls_max_attempts: 3
+ logger_tls_endpoint: /api/v1/osquery/log
+ pack_delimiter: /
+org_settings:
+ server_settings:
+ deferred_save_host: false
+ enable_analytics: true
+ live_query_disabled: false
+ query_report_cap: 2000
+ query_reports_disabled: false
+ scripts_disabled: false
+ server_url: $FLEET_SERVER_URL
+ ai_features_disabled: true
+ org_info:
+ contact_url: https://fleetdm.com/company/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: $ORG_NAME
+ smtp_settings:
+ sso_settings:
+ integrations:
+ mdm:
+ end_user_authentication:
+ webhook_settings:
+ fleet_desktop: # Applies to Fleet Premium only
+ transparency_url: https://fleetdm.com/transparency
+ host_expiry_settings: # Applies to all teams
+ host_expiry_enabled: false
+ activity_expiry_settings:
+ activity_expiry_enabled: true
+ activity_expiry_window: 60
+ features: # Features added to all teams
+ enable_host_users: true
+ enable_software_inventory: true
+ vulnerability_settings:
+ databases_path: ""
+ secrets: # These secrets are used to enroll hosts to the "All teams" team
+ - secret: SampleSecret123
+ - secret: ABC
+software:
diff --git a/cmd/fleetctl/testdata/gitops/global_config_windows_migration_true_true.yml b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_true_true.yml
new file mode 100644
index 000000000000..f6ab917ecf92
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_true_true.yml
@@ -0,0 +1,75 @@
+controls:
+ macos_settings:
+ windows_settings:
+ scripts:
+ enable_disk_encryption: false
+ macos_migration:
+ enable: false
+ mode: ""
+ webhook_url: ""
+ macos_setup:
+ bootstrap_package: null
+ enable_end_user_authentication: false
+ macos_setup_assistant: null
+ macos_updates:
+ deadline: null
+ minimum_version: null
+ windows_enabled_and_configured: true
+ windows_migration_enabled: true
+ windows_updates:
+ deadline_days: null
+ grace_period_days: null
+queries:
+policies:
+agent_options:
+ command_line_flags:
+ distributed_denylist_duration: 0
+ config:
+ decorators:
+ load:
+ - SELECT uuid AS host_uuid FROM system_info;
+ - SELECT hostname AS hostname FROM system_info;
+ options:
+ disable_distributed: false
+ distributed_interval: 10
+ distributed_plugin: tls
+ distributed_tls_max_attempts: 3
+ logger_tls_endpoint: /api/v1/osquery/log
+ pack_delimiter: /
+org_settings:
+ server_settings:
+ deferred_save_host: false
+ enable_analytics: true
+ live_query_disabled: false
+ query_report_cap: 2000
+ query_reports_disabled: false
+ scripts_disabled: false
+ server_url: $FLEET_SERVER_URL
+ ai_features_disabled: true
+ org_info:
+ contact_url: https://fleetdm.com/company/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: $ORG_NAME
+ smtp_settings:
+ sso_settings:
+ integrations:
+ mdm:
+ end_user_authentication:
+ webhook_settings:
+ fleet_desktop: # Applies to Fleet Premium only
+ transparency_url: https://fleetdm.com/transparency
+ host_expiry_settings: # Applies to all teams
+ host_expiry_enabled: false
+ activity_expiry_settings:
+ activity_expiry_enabled: true
+ activity_expiry_window: 60
+ features: # Features added to all teams
+ enable_host_users: true
+ enable_software_inventory: true
+ vulnerability_settings:
+ databases_path: ""
+ secrets: # These secrets are used to enroll hosts to the "All teams" team
+ - secret: SampleSecret123
+ - secret: ABC
+software:
diff --git a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml
index 49c129df695b..50250bc34eaa 100644
--- a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml
+++ b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml
@@ -27,6 +27,7 @@ spec:
enabled_and_configured: true
windows_enabled_and_configured: false
enable_disk_encryption: false
+ windows_migration_enabled: false
macos_migration:
enable: false
mode: ""
diff --git a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml
index 27e6a2a5459e..b74e3c2d8f5e 100644
--- a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml
+++ b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml
@@ -27,6 +27,7 @@ spec:
enabled_and_configured: true
windows_enabled_and_configured: false
enable_disk_encryption: false
+ windows_migration_enabled: false
macos_migration:
enable: false
mode: ""
diff --git a/docs/Contributing/Audit-logs.md b/docs/Contributing/Audit-logs.md
index 26c8a02f3dc5..0650b6179864 100644
--- a/docs/Contributing/Audit-logs.md
+++ b/docs/Contributing/Audit-logs.md
@@ -894,6 +894,18 @@ Generated when a user turns off MDM features for all Windows hosts.
This activity does not contain any detail fields.
+## enabled_windows_mdm_migration
+
+Generated when a user enables automatic MDM migration for Windows hosts, if Windows MDM is turned on.
+
+This activity does not contain any detail fields.
+
+## disabled_windows_mdm_migration
+
+Generated when a user disables automatic MDM migration for Windows hosts, if Windows MDM is turned on.
+
+This activity does not contain any detail fields.
+
## ran_script
Generated when a script is sent to be run for a host.
diff --git a/ee/server/service/devices.go b/ee/server/service/devices.go
index 77a6c7ce46fe..b0752ef0ec89 100644
--- a/ee/server/service/devices.go
+++ b/ee/server/service/devices.go
@@ -17,8 +17,6 @@ func (svc *Service) ListDevicePolicies(ctx context.Context, host *fleet.Host) ([
return svc.ds.ListPoliciesForHost(ctx, host)
}
-const refetchMDMUnenrollCriticalQueryDuration = 3 * time.Minute
-
// TriggerMigrateMDMDevice triggers the webhook associated with the MDM
// migration to Fleet configuration. It is located in the ee package instead of
// the server/webhooks one because it is a Fleet Premium only feature and for
@@ -88,7 +86,7 @@ func (svc *Service) TriggerMigrateMDMDevice(ctx context.Context, host *fleet.Hos
// if the webhook was successfully triggered, we update the host to
// constantly run the query to check if it has been unenrolled from its
// existing third-party MDM.
- refetchUntil := svc.clock.Now().Add(refetchMDMUnenrollCriticalQueryDuration)
+ refetchUntil := svc.clock.Now().Add(fleet.RefetchMDMUnenrollCriticalQueryDuration)
host.RefetchCriticalQueriesUntil = &refetchUntil
if err := svc.ds.UpdateHostRefetchCriticalQueriesUntil(ctx, host.ID, &refetchUntil); err != nil {
return ctxerr.Wrap(ctx, err, "save host with refetch critical queries timestamp")
diff --git a/frontend/__mocks__/configMock.ts b/frontend/__mocks__/configMock.ts
index c589f0538629..d1f0a644cd7b 100644
--- a/frontend/__mocks__/configMock.ts
+++ b/frontend/__mocks__/configMock.ts
@@ -39,6 +39,7 @@ const DEFAULT_CONFIG_MDM_MOCK: IMdmConfig = {
deadline_days: null,
grace_period_days: null,
},
+ windows_migration_enabled: false,
end_user_authentication: {
entity_id: "",
issuer_uri: "",
diff --git a/frontend/components/forms/fields/Checkbox/_styles.scss b/frontend/components/forms/fields/Checkbox/_styles.scss
index 8e8d02342220..6d311ead479f 100644
--- a/frontend/components/forms/fields/Checkbox/_styles.scss
+++ b/frontend/components/forms/fields/Checkbox/_styles.scss
@@ -68,6 +68,7 @@
}
.checkbox-unchecked-state {
stroke: $ui-fleet-black-25;
+ fill: $ui-fleet-black-25;
}
}
}
diff --git a/frontend/interfaces/activity.ts b/frontend/interfaces/activity.ts
index d6fcafc8c7ce..14618ad9e2cf 100644
--- a/frontend/interfaces/activity.ts
+++ b/frontend/interfaces/activity.ts
@@ -69,6 +69,8 @@ export enum ActivityType {
TransferredHosts = "transferred_hosts",
EnabledWindowsMdm = "enabled_windows_mdm",
DisabledWindowsMdm = "disabled_windows_mdm",
+ EnabledWindowsMdmMigration = "enabled_windows_mdm_migration",
+ DisabledWindowsMdmMigration = "disabled_windows_mdm_migration",
RanScript = "ran_script",
AddedScript = "added_script",
DeletedScript = "deleted_script",
diff --git a/frontend/interfaces/config.ts b/frontend/interfaces/config.ts
index 0de36a5d369b..12d61f3c85e5 100644
--- a/frontend/interfaces/config.ts
+++ b/frontend/interfaces/config.ts
@@ -57,6 +57,7 @@ export interface IMdmConfig {
apple_bm_terms_expired: boolean;
apple_bm_enabled_and_configured: boolean;
windows_enabled_and_configured: boolean;
+ windows_migration_enabled: boolean;
end_user_authentication: IEndUserAuthentication;
macos_updates: IAppleDeviceUpdates;
ios_updates: IAppleDeviceUpdates;
diff --git a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx
index b162a22fcc9f..5e724059bf7e 100644
--- a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx
+++ b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx
@@ -37,6 +37,8 @@ const PREMIUM_ACTIVITIES = new Set([
"enabled_macos_setup_end_user_auth",
"disabled_macos_setup_end_user_auth",
"tranferred_hosts",
+ "enabled_windows_mdm_migration",
+ "disabled_windows_mdm_migration",
]);
const getProfileMessageSuffix = (
@@ -663,6 +665,24 @@ const TAGGED_TEMPLATES = {
disabledWindowsMdm: () => {
return <> told Fleet to turn off Windows MDM features.>;
},
+ enabledWindowsMdmMigration: () => {
+ return (
+ <>
+ {" "}
+ told Fleet to automatically migrate Windows hosts connected to another
+ MDM solution.
+ >
+ );
+ },
+ disabledWindowsMdmMigration: () => {
+ return (
+ <>
+ {" "}
+ told Fleet to stop migrating Windows hosts connected to another MDM
+ solution.
+ >
+ );
+ },
// TODO: Combine ranScript template with host details page templates
// frontend/pages/hosts/details/cards/Activity/PastActivity/PastActivity.tsx and
// frontend/pages/hosts/details/cards/Activity/UpcomingActivity/UpcomingActivity.tsx
@@ -1262,6 +1282,12 @@ const getDetail = (
case ActivityType.DisabledWindowsMdm: {
return TAGGED_TEMPLATES.disabledWindowsMdm();
}
+ case ActivityType.EnabledWindowsMdmMigration: {
+ return TAGGED_TEMPLATES.enabledWindowsMdmMigration();
+ }
+ case ActivityType.DisabledWindowsMdmMigration: {
+ return TAGGED_TEMPLATES.disabledWindowsMdmMigration();
+ }
case ActivityType.RanScript: {
return TAGGED_TEMPLATES.ranScript(activity, onDetailsClick);
}
diff --git a/frontend/pages/DashboardPage/cards/MDM/MDM.tsx b/frontend/pages/DashboardPage/cards/MDM/MDM.tsx
index 583460c1287f..6824ebe5ae64 100644
--- a/frontend/pages/DashboardPage/cards/MDM/MDM.tsx
+++ b/frontend/pages/DashboardPage/cards/MDM/MDM.tsx
@@ -2,11 +2,7 @@ import React, { useMemo, useState } from "react";
import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
import { Row } from "react-table";
-import {
- IMdmStatusCardData,
- IMdmSolution,
- IMdmSummaryMdmSolution,
-} from "interfaces/mdm";
+import { IMdmStatusCardData, IMdmSummaryMdmSolution } from "interfaces/mdm";
import TabsWrapper from "components/TabsWrapper";
import TableContainer from "components/TableContainer";
diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/WindowsMdmPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/WindowsMdmPage.tsx
index 405cf3b4a65a..27dbdddc1516 100644
--- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/WindowsMdmPage.tsx
+++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/WindowsMdmPage.tsx
@@ -1,4 +1,4 @@
-import React, { useContext } from "react";
+import React, { useContext, useState } from "react";
import { InjectedRouter } from "react-router";
import { isAxiosError } from "axios";
@@ -11,18 +11,22 @@ import { AppContext } from "context/app";
import MainContent from "components/MainContent/MainContent";
import Button from "components/buttons/Button";
import BackLink from "components/BackLink/BackLink";
+import Slider from "components/forms/fields/Slider";
+import Checkbox from "components/forms/fields/Checkbox";
const baseClass = "windows-mdm-page";
interface ISetWindowsMdmOptions {
- enable: boolean;
+ enableMdm: boolean;
+ enableAutoMigration: boolean;
successMessage: string;
errorMessage: string;
router: InjectedRouter;
}
const useSetWindowsMdm = ({
- enable,
+ enableMdm,
+ enableAutoMigration,
successMessage,
errorMessage,
router,
@@ -35,13 +39,14 @@ const useSetWindowsMdm = ({
try {
const updatedConfig = await configAPI.updateMDMConfig(
{
- windows_enabled_and_configured: enable,
+ windows_enabled_and_configured: enableMdm,
+ windows_migration_enabled: enableAutoMigration,
},
true
);
setConfig(updatedConfig);
} catch (e) {
- if (enable && isAxiosError(e) && e.response?.status === 422) {
+ if (enableMdm && isAxiosError(e) && e.response?.status === 422) {
flashErrMsg =
getErrorReason(e, {
nameEquals: "mdm.windows_enabled_and_configured",
@@ -62,62 +67,44 @@ const useSetWindowsMdm = ({
return turnOnWindowsMdm;
};
-interface IWindowsMdmOnContentProps {
+interface IWindowsMdmPageProps {
router: InjectedRouter;
}
-const WindowsMdmOnContent = ({ router }: IWindowsMdmOnContentProps) => {
- const turnOnWindowsMdm = useSetWindowsMdm({
- enable: true,
- successMessage: "Windows MDM turned on (servers excluded).",
- errorMessage: "Unable to turn on Windows MDM. Please try again.",
- router,
- });
+const WindowsMdmPage = ({ router }: IWindowsMdmPageProps) => {
+ const { config, isPremiumTier } = useContext(AppContext);
- return (
- <>
-
Turn on Windows MDM
- This will turn MDM on for Windows hosts with fleetd.
- Hosts connected to another MDM solution won't be migrated.
- MDM won't be turned on for Windows servers.
-
- >
+ const [mdmOn, setMdmOn] = useState(
+ config?.mdm?.windows_enabled_and_configured ?? false
+ );
+ const [autoMigration, setAutoMigration] = useState(
+ config?.mdm?.windows_migration_enabled ?? false
);
-};
-
-interface IWindowsMdmOffContentProps {
- router: InjectedRouter;
-}
-const WindowsMdmOffContent = ({ router }: IWindowsMdmOffContentProps) => {
- const turnOffWindowsMdm = useSetWindowsMdm({
- enable: false,
- successMessage: "Windows MDM turned off.",
- errorMessage: "Unable to turn off Windows MDM. Please try again.",
+ const updateWindowsMdm = useSetWindowsMdm({
+ enableMdm: mdmOn,
+ enableAutoMigration: autoMigration,
+ successMessage: "Windows MDM settings successfully updated.",
+ errorMessage: "Unable to update Windows MDM. Please try again.",
router,
});
- return (
- <>
- Turn off Windows MDM
-
- MDM will no longer be turned on for Windows hosts that enroll to Fleet.
-
- Hosts with MDM already turned on will not have MDM removed.
-
- >
- );
-};
+ const onChangeMdmOn = () => {
+ setMdmOn(!mdmOn);
+ mdmOn && setAutoMigration(false);
+ };
-interface IWindowsMdmPageProps {
- router: InjectedRouter;
-}
+ const onChangeAutoMigration = () => {
+ setAutoMigration(!autoMigration);
+ };
-const WindowsMdmPage = ({ router }: IWindowsMdmPageProps) => {
- const { config } = useContext(AppContext);
+ const onSaveMdm = () => {
+ updateWindowsMdm();
+ };
- const isWindowsMdmEnabled =
- config?.mdm?.windows_enabled_and_configured ?? false;
+ const descriptionText = mdmOn
+ ? "Turns on MDM for Windows hosts that enroll to Fleet (excluding servers)."
+ : "Hosts with MDM already turned on will not have MDM removed.";
return (
@@ -127,11 +114,30 @@ const WindowsMdmPage = ({ router }: IWindowsMdmPageProps) => {
path={PATHS.ADMIN_INTEGRATIONS_MDM}
className={`${baseClass}__back-to-mdm`}
/>
- {isWindowsMdmEnabled ? (
-
- ) : (
-
- )}
+ Manage Windows MDM
+
>
);
diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/__styles.scss b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/__styles.scss
index ecf4b4c49c76..64d127974611 100644
--- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/__styles.scss
+++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/__styles.scss
@@ -5,12 +5,13 @@
}
h1 {
- margin-bottom: $pad-xxlarge;
+ margin-bottom: $pad-large;
font-size: $x-large;
}
- p {
- font-size: $x-small;
- margin: 0 0 $pad-large;
+ form {
+ display: flex;
+ flex-direction: column;
+ gap: $pad-large;
}
}
diff --git a/frontend/utilities/constants.tsx b/frontend/utilities/constants.tsx
index d7e2497655a4..3c5820df3562 100644
--- a/frontend/utilities/constants.tsx
+++ b/frontend/utilities/constants.tsx
@@ -340,7 +340,10 @@ export const MDM_STATUS_TOOLTIP: Record = {
),
"On (manual)": (
- MDM was turned on manually. End users can turn MDM off.
+
+ MDM was turned on manually (macOS), or hosts were automatically migrated
+ with fleetd (Windows). End users can turn MDM off.
+
),
Off: undefined, // no tooltip specified
Pending: (
diff --git a/orbit/changes/22898-support-windows-mdm-migration b/orbit/changes/22898-support-windows-mdm-migration
new file mode 100644
index 000000000000..e4e0401d2ba4
--- /dev/null
+++ b/orbit/changes/22898-support-windows-mdm-migration
@@ -0,0 +1 @@
+* Added support to migrate the MDM provider of Windows devices to Fleet.
diff --git a/orbit/pkg/update/execwinapi_windows.go b/orbit/pkg/update/execwinapi_windows.go
index 3c0988a0fcff..d07e50192f37 100644
--- a/orbit/pkg/update/execwinapi_windows.go
+++ b/orbit/pkg/update/execwinapi_windows.go
@@ -175,7 +175,8 @@ func generateWindowsMDMAccessTokenPayload(args WindowsMDMEnrollmentArgs) ([]byte
return json.Marshal(pld)
}
-// IsRunningOnWindowsServer determines if the process is running on a Windows server. Exported so it can be used across packages.
+// IsRunningOnWindowsServer determines if the process is running on a Windows
+// server. Exported so it can be used across packages.
func IsRunningOnWindowsServer() (bool, error) {
installType, err := readInstallationType()
if err != nil {
diff --git a/orbit/pkg/update/notifications.go b/orbit/pkg/update/notifications.go
index cd5f25645834..8e1b1003a6bf 100644
--- a/orbit/pkg/update/notifications.go
+++ b/orbit/pkg/update/notifications.go
@@ -165,14 +165,22 @@ func ApplyWindowsMDMEnrollmentFetcherMiddleware(
var errIsWindowsServer = errors.New("device is a Windows Server")
-// GetConfig calls the wrapped Fetcher's GetConfig method, and if the fleet
-// server set the "needs windows enrollment" flag to true, executes the command
-// to enroll into Windows MDM (or not, if the device is a Windows Server).
+// Run checks if the fleet server set the "needs windows {un}enrollment" flag
+// to true, and executes the command to {un}enroll into Windows MDM (or not, if
+// the device is a Windows Server). It also unenrolls the device if the flag
+// "needs MDM migration" is set to true, so that the device can then be
+// enrolled in Fleet MDM.
func (w *windowsMDMEnrollmentConfigReceiver) Run(cfg *fleet.OrbitConfig) error {
- if cfg.Notifications.NeedsProgrammaticWindowsMDMEnrollment {
+ switch {
+ case cfg.Notifications.NeedsProgrammaticWindowsMDMEnrollment:
w.attemptEnrollment(cfg.Notifications)
- } else if cfg.Notifications.NeedsProgrammaticWindowsMDMUnenrollment {
- w.attemptUnenrollment()
+ case cfg.Notifications.NeedsProgrammaticWindowsMDMUnenrollment,
+ cfg.Notifications.NeedsMDMMigration:
+ label := "unenroll"
+ if cfg.Notifications.NeedsMDMMigration {
+ label = "migrate"
+ }
+ w.attemptUnenrollment(label)
}
return nil
}
@@ -227,18 +235,18 @@ func (w *windowsMDMEnrollmentConfigReceiver) attemptEnrollment(notifs fleet.Orbi
}
}
-func (w *windowsMDMEnrollmentConfigReceiver) attemptUnenrollment() {
+func (w *windowsMDMEnrollmentConfigReceiver) attemptUnenrollment(actionLabel string) {
if w.mu.TryLock() {
defer w.mu.Unlock()
// do not unenroll Windows Servers, and do not attempt unenrollment if the
// last run is not at least Frequency ago.
if w.isWindowsServer {
- log.Debug().Msg("skipped calling UnregisterDeviceWithManagement to unenroll Windows device, device is a server")
+ log.Debug().Msgf("skipped calling UnregisterDeviceWithManagement to %s Windows device, device is a server", actionLabel)
return
}
if time.Since(w.lastUnenrollRun) <= w.Frequency {
- log.Debug().Msg("skipped calling UnregisterDeviceWithManagement to unenroll Windows device, last run was too recent")
+ log.Debug().Msgf("skipped calling UnregisterDeviceWithManagement to %s Windows device, last run was too recent", actionLabel)
return
}
@@ -252,15 +260,15 @@ func (w *windowsMDMEnrollmentConfigReceiver) attemptUnenrollment() {
if err := fn(args); err != nil {
if errors.Is(err, errIsWindowsServer) {
w.isWindowsServer = true
- log.Info().Msg("device is a Windows Server, skipping unenrollment")
+ log.Info().Msgf("device is a Windows Server, skipping %s", actionLabel)
} else {
- log.Info().Err(err).Msg("calling UnregisterDeviceWithManagement to unenroll Windows device failed")
+ log.Info().Err(err).Msgf("calling UnregisterDeviceWithManagement to %s Windows device failed", actionLabel)
}
return
}
w.lastUnenrollRun = time.Now()
- log.Info().Msg("successfully called UnregisterDeviceWithManagement to unenroll Windows device")
+ log.Info().Msgf("successfully called UnregisterDeviceWithManagement to %s Windows device", actionLabel)
}
}
diff --git a/orbit/pkg/update/notifications_test.go b/orbit/pkg/update/notifications_test.go
index 1455d8fc7e5b..a16b9dfbf15d 100644
--- a/orbit/pkg/update/notifications_test.go
+++ b/orbit/pkg/update/notifications_test.go
@@ -191,21 +191,27 @@ func TestWindowsMDMEnrollment(t *testing.T) {
desc string
enrollFlag *bool
unenrollFlag *bool
+ migrateFlag *bool
discoveryURL string
apiErr error
wantAPICalled bool
wantLog string
}{
- {"enroll=false", ptr.Bool(false), nil, "", nil, false, ""},
- {"enroll=true,discovery=''", ptr.Bool(true), nil, "", nil, false, "discovery endpoint is empty"},
- {"enroll=true,discovery!='',success", ptr.Bool(true), nil, "http://example.com", nil, true, "successfully called RegisterDeviceWithManagement"},
- {"enroll=true,discovery!='',fail", ptr.Bool(true), nil, "http://example.com", io.ErrUnexpectedEOF, true, "enroll Windows device failed"},
- {"enroll=true,discovery!='',server", ptr.Bool(true), nil, "http://example.com", errIsWindowsServer, true, "device is a Windows Server, skipping enrollment"},
-
- {"unenroll=false", nil, ptr.Bool(false), "", nil, false, ""},
- {"unenroll=true,success", nil, ptr.Bool(true), "", nil, true, "successfully called UnregisterDeviceWithManagement"},
- {"unenroll=true,fail", nil, ptr.Bool(true), "", io.ErrUnexpectedEOF, true, "unenroll Windows device failed"},
- {"unenroll=true,server", nil, ptr.Bool(true), "", errIsWindowsServer, true, "device is a Windows Server, skipping unenrollment"},
+ {"enroll=false", ptr.Bool(false), nil, nil, "", nil, false, ""},
+ {"enroll=true,discovery=''", ptr.Bool(true), nil, nil, "", nil, false, "discovery endpoint is empty"},
+ {"enroll=true,discovery!='',success", ptr.Bool(true), nil, nil, "http://example.com", nil, true, "successfully called RegisterDeviceWithManagement"},
+ {"enroll=true,discovery!='',fail", ptr.Bool(true), nil, nil, "http://example.com", io.ErrUnexpectedEOF, true, "enroll Windows device failed"},
+ {"enroll=true,discovery!='',server", ptr.Bool(true), nil, nil, "http://example.com", errIsWindowsServer, true, "device is a Windows Server, skipping enrollment"},
+
+ {"unenroll=false", nil, ptr.Bool(false), nil, "", nil, false, ""},
+ {"unenroll=true,success", nil, ptr.Bool(true), nil, "", nil, true, "successfully called UnregisterDeviceWithManagement to unenroll"},
+ {"unenroll=true,fail", nil, ptr.Bool(true), nil, "", io.ErrUnexpectedEOF, true, "unenroll Windows device failed"},
+ {"unenroll=true,server", nil, ptr.Bool(true), nil, "", errIsWindowsServer, true, "device is a Windows Server, skipping unenroll"},
+
+ {"migrate=false", nil, nil, ptr.Bool(false), "", nil, false, ""},
+ {"migrate=true,success", nil, nil, ptr.Bool(true), "", nil, true, "successfully called UnregisterDeviceWithManagement to migrate"},
+ {"migrate=true,fail", nil, nil, ptr.Bool(true), "", io.ErrUnexpectedEOF, true, "migrate Windows device failed"},
+ {"migrate=true,server", nil, nil, ptr.Bool(true), "", errIsWindowsServer, true, "device is a Windows Server, skipping migrate"},
}
for _, c := range cases {
@@ -215,12 +221,14 @@ func TestWindowsMDMEnrollment(t *testing.T) {
var (
enroll = c.enrollFlag != nil && *c.enrollFlag
unenroll = c.unenrollFlag != nil && *c.unenrollFlag
+ migrate = c.migrateFlag != nil && *c.migrateFlag
isUnenroll = c.unenrollFlag != nil
)
testConfig := &fleet.OrbitConfig{Notifications: fleet.OrbitConfigNotifications{
NeedsProgrammaticWindowsMDMEnrollment: enroll,
NeedsProgrammaticWindowsMDMUnenrollment: unenroll,
+ NeedsMDMMigration: migrate,
WindowsMDMDiscoveryEndpoint: c.discoveryURL,
}}
@@ -241,7 +249,7 @@ func TestWindowsMDMEnrollment(t *testing.T) {
err := enrollReceiver.Run(testConfig)
require.NoError(t, err) // the dummy receiver never returns an error
- if isUnenroll {
+ if isUnenroll || migrate {
require.Equal(t, c.wantAPICalled, unenrollGotCalled)
require.False(t, enrollGotCalled)
} else {
diff --git a/pkg/spec/gitops.go b/pkg/spec/gitops.go
index c26c9b500f3a..bdfdf1ceec3c 100644
--- a/pkg/spec/gitops.go
+++ b/pkg/spec/gitops.go
@@ -33,6 +33,7 @@ type Controls struct {
WindowsUpdates interface{} `json:"windows_updates"`
WindowsSettings interface{} `json:"windows_settings"`
WindowsEnabledAndConfigured interface{} `json:"windows_enabled_and_configured"`
+ WindowsMigrationEnabled interface{} `json:"windows_migration_enabled"`
EnableDiskEncryption interface{} `json:"enable_disk_encryption"`
@@ -46,7 +47,7 @@ func (c Controls) Set() bool {
c.IPadOSUpdates != nil || c.MacOSSettings != nil ||
c.MacOSSetup != nil || c.MacOSMigration != nil ||
c.WindowsUpdates != nil || c.WindowsSettings != nil || c.WindowsEnabledAndConfigured != nil ||
- c.EnableDiskEncryption != nil || len(c.Scripts) > 0
+ c.WindowsMigrationEnabled != nil || c.EnableDiskEncryption != nil || len(c.Scripts) > 0
}
type Policy struct {
diff --git a/pkg/spec/gitops_test.go b/pkg/spec/gitops_test.go
index 8ca334ce3620..bdc5423f0a40 100644
--- a/pkg/spec/gitops_test.go
+++ b/pkg/spec/gitops_test.go
@@ -216,6 +216,8 @@ func TestValidGitOpsYaml(t *testing.T) {
assert.True(t, ok, "ipados_updates not found")
_, ok = gitops.Controls.WindowsEnabledAndConfigured.(bool)
assert.True(t, ok, "windows_enabled_and_configured not found")
+ _, ok = gitops.Controls.WindowsMigrationEnabled.(bool)
+ assert.True(t, ok, "windows_migration_enabled not found")
_, ok = gitops.Controls.WindowsUpdates.(map[string]interface{})
assert.True(t, ok, "windows_updates not found")
diff --git a/pkg/spec/testdata/controls.yml b/pkg/spec/testdata/controls.yml
index 5da356792168..27fe44dc03b4 100644
--- a/pkg/spec/testdata/controls.yml
+++ b/pkg/spec/testdata/controls.yml
@@ -25,6 +25,7 @@ ipados_updates:
deadline: null
minimum_version: null
windows_enabled_and_configured: true
+windows_migration_enabled: false
windows_updates:
deadline_days: null
grace_period_days: null
diff --git a/pkg/spec/testdata/global_config_no_paths.yml b/pkg/spec/testdata/global_config_no_paths.yml
index c8d68f946237..36ce19ca3e49 100644
--- a/pkg/spec/testdata/global_config_no_paths.yml
+++ b/pkg/spec/testdata/global_config_no_paths.yml
@@ -27,6 +27,7 @@ controls: # Controls added to "No team"
deadline: null
minimum_version: null
windows_enabled_and_configured: true
+ windows_migration_enabled: false
windows_updates:
deadline_days: null
grace_period_days: null
diff --git a/pkg/spec/testdata/team_config_no_paths.yml b/pkg/spec/testdata/team_config_no_paths.yml
index e660d5eccce7..2ff83e4730fa 100644
--- a/pkg/spec/testdata/team_config_no_paths.yml
+++ b/pkg/spec/testdata/team_config_no_paths.yml
@@ -60,6 +60,7 @@ controls:
mode: ""
webhook_url: ""
windows_enabled_and_configured: true
+ windows_migration_enabled: false
queries:
- name: Scheduled query stats
description: Collect osquery performance stats directly from osquery
diff --git a/server/datastore/mysql/migrations/tables/20241125150614_AddAppConfigWindowsMigrationEnabledField.go b/server/datastore/mysql/migrations/tables/20241125150614_AddAppConfigWindowsMigrationEnabledField.go
new file mode 100644
index 000000000000..1765d00c6220
--- /dev/null
+++ b/server/datastore/mysql/migrations/tables/20241125150614_AddAppConfigWindowsMigrationEnabledField.go
@@ -0,0 +1,54 @@
+package tables
+
+import (
+ "database/sql"
+ "encoding/json"
+ "fmt"
+
+ "github.com/pkg/errors"
+)
+
+func init() {
+ MigrationClient.AddMigration(Up_20241125150614, Down_20241125150614)
+}
+
+func Up_20241125150614(tx *sql.Tx) error {
+ var raw json.RawMessage
+ var id uint
+ row := tx.QueryRow(`SELECT id, json_value FROM app_config_json LIMIT 1;`)
+ if err := row.Scan(&id, &raw); err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ return nil
+ }
+ return fmt.Errorf("select app_config_json: %w", err)
+ }
+
+ var config map[string]interface{}
+ if err := json.Unmarshal(raw, &config); err != nil {
+ return fmt.Errorf("unmarshal appconfig: %w", err)
+ }
+
+ mdm, ok := config["mdm"]
+ if !ok {
+ return errors.New("missing mdm section")
+ }
+ mdmMap, ok := mdm.(map[string]interface{})
+ if !ok {
+ return fmt.Errorf("invalid type for mdm: %T", mdm)
+ }
+ mdmMap["windows_migration_enabled"] = false
+
+ b, err := json.Marshal(config)
+ if err != nil {
+ return fmt.Errorf("marshal updated appconfig: %w", err)
+ }
+ if _, err := tx.Exec(`UPDATE app_config_json SET json_value = ? WHERE id = ?;`, b, id); err != nil {
+ return fmt.Errorf("update app_config_json: %w", err)
+ }
+
+ return nil
+}
+
+func Down_20241125150614(tx *sql.Tx) error {
+ return nil
+}
diff --git a/server/datastore/mysql/migrations/tables/20241125150614_AddAppConfigWindowsMigrationEnabledField_test.go b/server/datastore/mysql/migrations/tables/20241125150614_AddAppConfigWindowsMigrationEnabledField_test.go
new file mode 100644
index 000000000000..743f82de1db5
--- /dev/null
+++ b/server/datastore/mysql/migrations/tables/20241125150614_AddAppConfigWindowsMigrationEnabledField_test.go
@@ -0,0 +1,33 @@
+package tables
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/jmoiron/sqlx"
+ "github.com/stretchr/testify/require"
+)
+
+func TestUp_20241125150614(t *testing.T) {
+ db := applyUpToPrev(t)
+
+ // Apply current migration.
+ applyNext(t, db)
+
+ var appCfg json.RawMessage
+ err := sqlx.Get(db, &appCfg, `SELECT json_value FROM app_config_json LIMIT 1;`)
+ require.NoError(t, err)
+
+ var config map[string]interface{}
+ err = json.Unmarshal(appCfg, &config)
+ require.NoError(t, err)
+
+ mdm, ok := config["mdm"]
+ require.True(t, ok)
+ mdmMap, ok := mdm.(map[string]interface{})
+ require.True(t, ok)
+
+ _, ok = mdmMap["windows_enabled_and_configured"].(bool)
+ require.True(t, ok)
+ require.False(t, mdmMap["windows_migration_enabled"].(bool))
+}
diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql
index 038a0de892c1..450848024da6 100644
--- a/server/datastore/mysql/schema.sql
+++ b/server/datastore/mysql/schema.sql
@@ -64,7 +64,7 @@ CREATE TABLE `app_config_json` (
PRIMARY KEY (`id`)
) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
-INSERT INTO `app_config_json` VALUES (1,'{\"mdm\": {\"ios_updates\": {\"deadline\": null, \"minimum_version\": null}, \"macos_setup\": {\"script\": null, \"software\": null, \"bootstrap_package\": null, \"macos_setup_assistant\": null, \"enable_end_user_authentication\": false, \"enable_release_device_manually\": false}, \"macos_updates\": {\"deadline\": null, \"minimum_version\": null}, \"ipados_updates\": {\"deadline\": null, \"minimum_version\": null}, \"macos_settings\": {\"custom_settings\": null}, \"macos_migration\": {\"mode\": \"\", \"enable\": false, \"webhook_url\": \"\"}, \"windows_updates\": {\"deadline_days\": null, \"grace_period_days\": null}, \"apple_server_url\": \"\", \"windows_settings\": {\"custom_settings\": null}, \"apple_bm_terms_expired\": false, \"apple_business_manager\": null, \"enable_disk_encryption\": false, \"enabled_and_configured\": false, \"end_user_authentication\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"issuer_uri\": \"\", \"metadata_url\": \"\"}, \"volume_purchasing_program\": null, \"windows_enabled_and_configured\": false, \"apple_bm_enabled_and_configured\": false}, \"scripts\": null, \"features\": {\"enable_host_users\": true, \"enable_software_inventory\": false}, \"org_info\": {\"org_name\": \"\", \"contact_url\": \"\", \"org_logo_url\": \"\", \"org_logo_url_light_background\": \"\"}, \"integrations\": {\"jira\": null, \"zendesk\": null, \"google_calendar\": null, \"ndes_scep_proxy\": null}, \"sso_settings\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"enable_sso\": false, \"issuer_uri\": \"\", \"metadata_url\": \"\", \"idp_image_url\": \"\", \"enable_jit_role_sync\": false, \"enable_sso_idp_login\": false, \"enable_jit_provisioning\": false}, \"agent_options\": {\"config\": {\"options\": {\"logger_plugin\": \"tls\", \"pack_delimiter\": \"/\", \"logger_tls_period\": 10, \"distributed_plugin\": \"tls\", \"disable_distributed\": false, \"logger_tls_endpoint\": \"/api/osquery/log\", \"distributed_interval\": 10, \"distributed_tls_max_attempts\": 3}, \"decorators\": {\"load\": [\"SELECT uuid AS host_uuid FROM system_info;\", \"SELECT hostname AS hostname FROM system_info;\"]}}, \"overrides\": {}}, \"fleet_desktop\": {\"transparency_url\": \"\"}, \"smtp_settings\": {\"port\": 587, \"domain\": \"\", \"server\": \"\", \"password\": \"\", \"user_name\": \"\", \"configured\": false, \"enable_smtp\": false, \"enable_ssl_tls\": true, \"sender_address\": \"\", \"enable_start_tls\": true, \"verify_ssl_certs\": true, \"authentication_type\": \"0\", \"authentication_method\": \"0\"}, \"server_settings\": {\"server_url\": \"\", \"enable_analytics\": false, \"query_report_cap\": 0, \"scripts_disabled\": false, \"deferred_save_host\": false, \"live_query_disabled\": false, \"ai_features_disabled\": false, \"query_reports_disabled\": false}, \"webhook_settings\": {\"interval\": \"0s\", \"activities_webhook\": {\"destination_url\": \"\", \"enable_activities_webhook\": false}, \"host_status_webhook\": {\"days_count\": 0, \"destination_url\": \"\", \"host_percentage\": 0, \"enable_host_status_webhook\": false}, \"vulnerabilities_webhook\": {\"destination_url\": \"\", \"host_batch_size\": 0, \"enable_vulnerabilities_webhook\": false}, \"failing_policies_webhook\": {\"policy_ids\": null, \"destination_url\": \"\", \"host_batch_size\": 0, \"enable_failing_policies_webhook\": false}}, \"host_expiry_settings\": {\"host_expiry_window\": 0, \"host_expiry_enabled\": false}, \"vulnerability_settings\": {\"databases_path\": \"\"}, \"activity_expiry_settings\": {\"activity_expiry_window\": 0, \"activity_expiry_enabled\": false}}','2020-01-01 01:01:01','2020-01-01 01:01:01');
+INSERT INTO `app_config_json` VALUES (1,'{\"mdm\": {\"ios_updates\": {\"deadline\": null, \"minimum_version\": null}, \"macos_setup\": {\"script\": null, \"software\": null, \"bootstrap_package\": null, \"macos_setup_assistant\": null, \"enable_end_user_authentication\": false, \"enable_release_device_manually\": false}, \"macos_updates\": {\"deadline\": null, \"minimum_version\": null}, \"ipados_updates\": {\"deadline\": null, \"minimum_version\": null}, \"macos_settings\": {\"custom_settings\": null}, \"macos_migration\": {\"mode\": \"\", \"enable\": false, \"webhook_url\": \"\"}, \"windows_updates\": {\"deadline_days\": null, \"grace_period_days\": null}, \"apple_server_url\": \"\", \"windows_settings\": {\"custom_settings\": null}, \"apple_bm_terms_expired\": false, \"apple_business_manager\": null, \"enable_disk_encryption\": false, \"enabled_and_configured\": false, \"end_user_authentication\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"issuer_uri\": \"\", \"metadata_url\": \"\"}, \"volume_purchasing_program\": null, \"windows_migration_enabled\": false, \"windows_enabled_and_configured\": false, \"apple_bm_enabled_and_configured\": false}, \"scripts\": null, \"features\": {\"enable_host_users\": true, \"enable_software_inventory\": false}, \"org_info\": {\"org_name\": \"\", \"contact_url\": \"\", \"org_logo_url\": \"\", \"org_logo_url_light_background\": \"\"}, \"integrations\": {\"jira\": null, \"zendesk\": null, \"google_calendar\": null, \"ndes_scep_proxy\": null}, \"sso_settings\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"enable_sso\": false, \"issuer_uri\": \"\", \"metadata_url\": \"\", \"idp_image_url\": \"\", \"enable_jit_role_sync\": false, \"enable_sso_idp_login\": false, \"enable_jit_provisioning\": false}, \"agent_options\": {\"config\": {\"options\": {\"logger_plugin\": \"tls\", \"pack_delimiter\": \"/\", \"logger_tls_period\": 10, \"distributed_plugin\": \"tls\", \"disable_distributed\": false, \"logger_tls_endpoint\": \"/api/osquery/log\", \"distributed_interval\": 10, \"distributed_tls_max_attempts\": 3}, \"decorators\": {\"load\": [\"SELECT uuid AS host_uuid FROM system_info;\", \"SELECT hostname AS hostname FROM system_info;\"]}}, \"overrides\": {}}, \"fleet_desktop\": {\"transparency_url\": \"\"}, \"smtp_settings\": {\"port\": 587, \"domain\": \"\", \"server\": \"\", \"password\": \"\", \"user_name\": \"\", \"configured\": false, \"enable_smtp\": false, \"enable_ssl_tls\": true, \"sender_address\": \"\", \"enable_start_tls\": true, \"verify_ssl_certs\": true, \"authentication_type\": \"0\", \"authentication_method\": \"0\"}, \"server_settings\": {\"server_url\": \"\", \"enable_analytics\": false, \"query_report_cap\": 0, \"scripts_disabled\": false, \"deferred_save_host\": false, \"live_query_disabled\": false, \"ai_features_disabled\": false, \"query_reports_disabled\": false}, \"webhook_settings\": {\"interval\": \"0s\", \"activities_webhook\": {\"destination_url\": \"\", \"enable_activities_webhook\": false}, \"host_status_webhook\": {\"days_count\": 0, \"destination_url\": \"\", \"host_percentage\": 0, \"enable_host_status_webhook\": false}, \"vulnerabilities_webhook\": {\"destination_url\": \"\", \"host_batch_size\": 0, \"enable_vulnerabilities_webhook\": false}, \"failing_policies_webhook\": {\"policy_ids\": null, \"destination_url\": \"\", \"host_batch_size\": 0, \"enable_failing_policies_webhook\": false}}, \"host_expiry_settings\": {\"host_expiry_window\": 0, \"host_expiry_enabled\": false}, \"vulnerability_settings\": {\"databases_path\": \"\"}, \"activity_expiry_settings\": {\"activity_expiry_window\": 0, \"activity_expiry_enabled\": false}}','2020-01-01 01:01:01','2020-01-01 01:01:01');
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `calendar_events` (
@@ -1101,9 +1101,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=332 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=333 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');
+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');
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `mobile_device_management_solutions` (
diff --git a/server/fleet/activities.go b/server/fleet/activities.go
index 751218ac6eb5..1298476dde93 100644
--- a/server/fleet/activities.go
+++ b/server/fleet/activities.go
@@ -79,6 +79,8 @@ var ActivityDetailsList = []ActivityDetails{
ActivityTypeEnabledWindowsMDM{},
ActivityTypeDisabledWindowsMDM{},
+ ActivityTypeEnabledWindowsMDMMigration{},
+ ActivityTypeDisabledWindowsMDMMigration{},
ActivityTypeRanScript{},
ActivityTypeAddedScript{},
@@ -1236,6 +1238,28 @@ func (a ActivityTypeDisabledWindowsMDM) Documentation() (activity, details, deta
`This activity does not contain any detail fields.`, ``
}
+type ActivityTypeEnabledWindowsMDMMigration struct{}
+
+func (a ActivityTypeEnabledWindowsMDMMigration) ActivityName() string {
+ return "enabled_windows_mdm_migration"
+}
+
+func (a ActivityTypeEnabledWindowsMDMMigration) Documentation() (activity, details, detailsExample string) {
+ return `Generated when a user enables automatic MDM migration for Windows hosts, if Windows MDM is turned on.`,
+ `This activity does not contain any detail fields.`, ``
+}
+
+type ActivityTypeDisabledWindowsMDMMigration struct{}
+
+func (a ActivityTypeDisabledWindowsMDMMigration) ActivityName() string {
+ return "disabled_windows_mdm_migration"
+}
+
+func (a ActivityTypeDisabledWindowsMDMMigration) Documentation() (activity, details, detailsExample string) {
+ return `Generated when a user disables automatic MDM migration for Windows hosts, if Windows MDM is turned on.`,
+ `This activity does not contain any detail fields.`, ``
+}
+
type ActivityTypeRanScript struct {
HostID uint `json:"host_id"`
HostDisplayName string `json:"host_display_name"`
diff --git a/server/fleet/app.go b/server/fleet/app.go
index 5622c438a90d..662095832ee1 100644
--- a/server/fleet/app.go
+++ b/server/fleet/app.go
@@ -189,10 +189,11 @@ type MDM struct {
// WindowsUpdates defines the OS update settings for Windows devices.
WindowsUpdates WindowsUpdates `json:"windows_updates"`
- MacOSSettings MacOSSettings `json:"macos_settings"`
- MacOSSetup MacOSSetup `json:"macos_setup"`
- MacOSMigration MacOSMigration `json:"macos_migration"`
- EndUserAuthentication MDMEndUserAuthentication `json:"end_user_authentication"`
+ MacOSSettings MacOSSettings `json:"macos_settings"`
+ MacOSSetup MacOSSetup `json:"macos_setup"`
+ MacOSMigration MacOSMigration `json:"macos_migration"`
+ WindowsMigrationEnabled bool `json:"windows_migration_enabled"`
+ EndUserAuthentication MDMEndUserAuthentication `json:"end_user_authentication"`
// WindowsEnabledAndConfigured indicates if Fleet MDM is enabled for Windows.
// There is no other configuration required for Windows other than enabling
diff --git a/server/fleet/hosts.go b/server/fleet/hosts.go
index 95ce9ee268e8..d55e4ff3ceb0 100644
--- a/server/fleet/hosts.go
+++ b/server/fleet/hosts.go
@@ -344,7 +344,7 @@ type Host struct {
// is that the latter is a one-time request, while this one is a persistent
// until the timestamp expires. The initial use-case is to check for a host
// to be unenrolled from its old MDM solution, in the "migrate to Fleet MDM"
- // workflow.
+ // workflow (both Apple and Windows).
//
// In the future, if we want to use it for more than one use-case, we could
// add a "reason" field with well-known labels so we know what condition(s)
diff --git a/server/fleet/mdm.go b/server/fleet/mdm.go
index 4d3e09f68ec8..b441067398b8 100644
--- a/server/fleet/mdm.go
+++ b/server/fleet/mdm.go
@@ -18,6 +18,11 @@ const (
MDMAppleDeclarationUUIDPrefix = "d"
MDMAppleProfileUUIDPrefix = "a"
MDMWindowsProfileUUIDPrefix = "w"
+
+ // RefetchMDMUnenrollCriticalQueryDuration is the duration to set the
+ // RefetchCriticalQueriesUntil field when migrating a device from a
+ // third-party MDM solution to Fleet.
+ RefetchMDMUnenrollCriticalQueryDuration = 3 * time.Minute
)
type AppleMDM struct {
diff --git a/server/fleet/orbit.go b/server/fleet/orbit.go
index 6c06a963e263..357af033a6c5 100644
--- a/server/fleet/orbit.go
+++ b/server/fleet/orbit.go
@@ -8,7 +8,12 @@ import "encoding/json"
type OrbitConfigNotifications struct {
RenewEnrollmentProfile bool `json:"renew_enrollment_profile,omitempty"`
RotateDiskEncryptionKey bool `json:"rotate_disk_encryption_key,omitempty"`
- NeedsMDMMigration bool `json:"needs_mdm_migration,omitempty"`
+
+ // NeedsMDMMigration is set to true if MDM is enabled for the host's
+ // platform, MDM migration is enabled for that platform, and the host is
+ // eligible for such a migration (e.g. it is enrolled in a third-party MDM
+ // solution).
+ NeedsMDMMigration bool `json:"needs_mdm_migration,omitempty"`
// NeedsProgrammaticWindowsMDMEnrollment is sent as true if Windows MDM is
// enabled and the device should be enrolled as far as the server knows (e.g.
diff --git a/server/service/appconfig.go b/server/service/appconfig.go
index 030c2c650202..e0ee0317e67f 100644
--- a/server/service/appconfig.go
+++ b/server/service/appconfig.go
@@ -337,6 +337,15 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle
return nil, ctxerr.Wrap(ctx, err)
}
+ // if turning off Windows MDM and Windows Migration is not explicitly set to
+ // on in the same update, set it to off (otherwise, if it is explicitly set
+ // to true, return an error that it can't be done when MDM is off, this is
+ // addressed in validateMDM).
+ if oldAppConfig.MDM.WindowsEnabledAndConfigured != appConfig.MDM.WindowsEnabledAndConfigured &&
+ !appConfig.MDM.WindowsEnabledAndConfigured && !newAppConfig.MDM.WindowsMigrationEnabled {
+ appConfig.MDM.WindowsMigrationEnabled = false
+ }
+
type ndesStatusType string
const (
ndesStatusAdded ndesStatusType = "added"
@@ -869,6 +878,18 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle
}
}
+ if appConfig.MDM.WindowsEnabledAndConfigured && oldAppConfig.MDM.WindowsMigrationEnabled != appConfig.MDM.WindowsMigrationEnabled {
+ var act fleet.ActivityDetails
+ if appConfig.MDM.WindowsMigrationEnabled {
+ act = fleet.ActivityTypeEnabledWindowsMDMMigration{}
+ } else {
+ act = fleet.ActivityTypeDisabledWindowsMDMMigration{}
+ }
+ if err := svc.NewActivity(ctx, authz.UserFromContext(ctx), act); err != nil {
+ return nil, ctxerr.Wrapf(ctx, err, "create activity %s", act.ActivityName())
+ }
+ }
+
return obfuscatedAppConfig, nil
}
@@ -958,6 +979,9 @@ func (svc *Service) validateMDM(
if mdm.MacOSSetup.EnableEndUserAuthentication && oldMdm.MacOSSetup.EnableEndUserAuthentication != mdm.MacOSSetup.EnableEndUserAuthentication && !license.IsPremium() {
invalid.Append("macos_setup.enable_end_user_authentication", ErrMissingLicense.Error())
}
+ if mdm.WindowsMigrationEnabled && !license.IsPremium() {
+ invalid.Append("windows_migration_enabled", ErrMissingLicense.Error())
+ }
// we want to use `oldMdm` here as this boolean is set by the fleet
// server at startup and can't be modified by the user
@@ -1133,6 +1157,9 @@ func (svc *Service) validateMDM(
return nil
}
}
+ if !mdm.WindowsEnabledAndConfigured && mdm.WindowsMigrationEnabled {
+ invalid.Append("mdm.windows_migration_enabled", "Couldn't enable Windows MDM migration, Windows MDM is not enabled.")
+ }
return nil
}
diff --git a/server/service/client.go b/server/service/client.go
index 2a3b1a6c5d64..935ca0743433 100644
--- a/server/service/client.go
+++ b/server/service/client.go
@@ -1512,6 +1512,11 @@ func (c *Client) DoGitOps(
} else {
mdmAppConfig["windows_enabled_and_configured"] = false
}
+ // Put in default values for windows_migration_enabled
+ mdmAppConfig["windows_migration_enabled"] = config.Controls.WindowsMigrationEnabled
+ if config.Controls.WindowsMigrationEnabled == nil {
+ mdmAppConfig["windows_migration_enabled"] = false
+ }
if windowsEnabledAndConfiguredAssumption, ok := mdmAppConfig["windows_enabled_and_configured"].(bool); ok {
teamAssumptions = &fleet.TeamSpecsDryRunAssumptions{
WindowsEnabledAndConfigured: optjson.SetBool(windowsEnabledAndConfiguredAssumption),
diff --git a/server/service/integration_core_test.go b/server/service/integration_core_test.go
index 0dfab1f02f1c..73171dfb3c81 100644
--- a/server/service/integration_core_test.go
+++ b/server/service/integration_core_test.go
@@ -6314,6 +6314,12 @@ func (s *integrationTestSuite) TestPremiumEndpointsWithoutLicense() {
}`), http.StatusUnprocessableEntity)
errMsg = extractServerErrorText(res.Body)
require.Contains(t, errMsg, "missing or invalid license")
+
+ res = s.Do("PATCH", "/api/v1/fleet/config", json.RawMessage(`{
+ "mdm": { "windows_migration_enabled": true }
+ }`), http.StatusUnprocessableEntity)
+ errMsg = extractServerErrorText(res.Body)
+ require.Contains(t, errMsg, "missing or invalid license")
}
func (s *integrationTestSuite) TestScriptsEndpointsWithoutLicense() {
diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go
index 93bac5241052..dda4f93bf85b 100644
--- a/server/service/integration_enterprise_test.go
+++ b/server/service/integration_enterprise_test.go
@@ -15353,3 +15353,13 @@ func (s *integrationEnterpriseTestSuite) TestMaintainedApps() {
require.NoError(t, err)
require.Equal(t, req.PostInstallScript, string(postinstall))
}
+
+func (s *integrationEnterpriseTestSuite) TestWindowsMigrateMDMNotEnabled() {
+ t := s.T()
+
+ res := s.Do("PATCH", "/api/v1/fleet/config", json.RawMessage(`{
+ "mdm": { "windows_migration_enabled": true }
+ }`), http.StatusUnprocessableEntity)
+ errMsg := extractServerErrorText(res.Body)
+ require.Contains(t, errMsg, "Windows MDM is not enabled")
+}
diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go
index 0d481ad7e1d2..f480b3de05a0 100644
--- a/server/service/integration_mdm_test.go
+++ b/server/service/integration_mdm_test.go
@@ -5941,7 +5941,6 @@ func (s *integrationMDMTestSuite) TestAppConfigWindowsMDM() {
err = s.ds.SaveAppConfig(context.Background(), appConf)
require.NoError(s.T(), err)
- // the feature flag is enabled for the MDM test suite
var acResp appConfigResponse
s.DoJSON("GET", "/api/latest/fleet/config", nil, http.StatusOK, &acResp)
assert.False(t, acResp.MDM.WindowsEnabledAndConfigured)
@@ -5952,55 +5951,102 @@ func (s *integrationMDMTestSuite) TestAppConfigWindowsMDM() {
tm2, err := s.ds.NewTeam(ctx, &fleet.Team{Name: t.Name() + "2"})
require.NoError(t, err)
+ // enable Windows MDM
+ acResp = appConfigResponse{}
+ s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
+ "mdm": { "windows_enabled_and_configured": true }
+ }`), http.StatusOK, &acResp)
+ assert.True(t, acResp.MDM.WindowsEnabledAndConfigured)
+ assert.False(t, acResp.MDM.WindowsMigrationEnabled)
+ s.lastActivityOfTypeMatches(fleet.ActivityTypeEnabledWindowsMDM{}.ActivityName(), `{}`, 0)
+
// create some hosts - a Windows workstation in each team and no-team,
// Windows server in no team, Windows workstation enrolled in a 3rd-party in
// team 2, Windows workstation already enrolled in Fleet in no team, and a
// macOS host in no team.
metadataHosts := []struct {
- os string
- suffix string
- isServer bool
- teamID *uint
- enrolledName string
- shouldEnroll bool
+ os string
+ suffix string
+ isServer bool
+ teamID *uint
+ enrolledName string
+ shouldEnroll bool
+ shouldMigrate bool
}{
- {"windows", "win-no-team", false, nil, "", true},
- {"windows", "win-team-1", false, &tm1.ID, "", true},
- {"windows", "win-team-2", false, &tm2.ID, "", true},
- {"windows", "win-server", true, nil, "", false}, // is a server
- {"windows", "win-third-party", false, &tm2.ID, fleet.WellKnownMDMSimpleMDM, false}, // is enrolled in 3rd-party
- {"windows", "win-fleet", false, nil, fleet.WellKnownMDMFleet, false}, // is already Fleet-enrolled
- {"darwin", "macos-no-team", false, nil, "", false}, // is not Windows
+ {"windows", "win-no-team", false, nil, "", true, false},
+ {"windows", "win-team-1", false, &tm1.ID, "", true, false},
+ {"windows", "win-team-2", false, &tm2.ID, "", true, false},
+ {"windows", "win-server", true, nil, "", false, false}, // is a server
+ {"windows", "win-third-party", false, &tm2.ID, fleet.WellKnownMDMSimpleMDM, false, true}, // is enrolled in 3rd-party
+ {"windows", "win-fleet", false, nil, fleet.WellKnownMDMFleet, false, false}, // is already Fleet-enrolled
+ {"darwin", "macos-no-team", false, nil, "", false, false}, // is not Windows
+ {"windows", "win-server-third-party", true, nil, fleet.WellKnownMDMSimpleMDM, false, false}, // is enrolled in 3rd-party, but is a server
}
hostsBySuffix := make(map[string]*fleet.Host, len(metadataHosts))
for _, meta := range metadataHosts {
- h := createOrbitEnrolledHost(t, meta.os, meta.suffix, s.ds)
- createDeviceTokenForHost(t, s.ds, h.ID, meta.suffix)
- err := s.ds.SetOrUpdateMDMData(ctx, h.ID, meta.isServer, meta.enrolledName != "", "https://example.com", false, meta.enrolledName, "")
- require.NoError(t, err)
+ var host *fleet.Host
+ if meta.os == "windows" && meta.enrolledName == fleet.WellKnownMDMFleet {
+ // special-case to create a properly MDM-enrolled into Fleet host
+ host = createOrbitEnrolledHost(t, meta.os, meta.suffix, s.ds)
+ mdmDevice := mdmtest.NewTestMDMClientWindowsProgramatic(s.server.URL, *host.OrbitNodeKey)
+ err := mdmDevice.Enroll()
+ require.NoError(t, err)
+ err = s.ds.UpdateMDMWindowsEnrollmentsHostUUID(ctx, host.UUID, mdmDevice.DeviceID)
+ require.NoError(t, err)
+ err = s.ds.SetOrUpdateMDMData(ctx, host.ID, meta.isServer, true, s.server.URL, false, fleet.WellKnownMDMFleet, "")
+ require.NoError(t, err)
+ } else {
+ host = createOrbitEnrolledHost(t, meta.os, meta.suffix, s.ds)
+ createDeviceTokenForHost(t, s.ds, host.ID, meta.suffix)
+
+ serverURL := "https://example.com"
+ err := s.ds.SetOrUpdateMDMData(ctx, host.ID, meta.isServer, meta.enrolledName != "", serverURL, false, meta.enrolledName, "")
+ require.NoError(t, err)
+ }
+
if meta.teamID != nil {
- err = s.ds.AddHostsToTeam(ctx, meta.teamID, []uint{h.ID})
+ err = s.ds.AddHostsToTeam(ctx, meta.teamID, []uint{host.ID})
require.NoError(t, err)
}
- hostsBySuffix[meta.suffix] = h
+ hostsBySuffix[meta.suffix] = host
}
- // enable Windows MDM
+ // get the orbit config for each host, verify that only the expected ones
+ // receive the "needs enrollment to Windows MDM" notification.
+ for _, meta := range metadataHosts {
+ var resp orbitGetConfigResponse
+ s.DoJSON("POST", "/api/fleet/orbit/config",
+ json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *hostsBySuffix[meta.suffix].OrbitNodeKey)),
+ http.StatusOK, &resp)
+ require.Equal(t, meta.shouldEnroll, resp.Notifications.NeedsProgrammaticWindowsMDMEnrollment)
+ require.False(t, resp.Notifications.NeedsProgrammaticWindowsMDMUnenrollment)
+ require.False(t, resp.Notifications.NeedsMDMMigration)
+ if meta.shouldEnroll {
+ require.Contains(t, resp.Notifications.WindowsMDMDiscoveryEndpoint, microsoft_mdm.MDE2DiscoveryPath)
+ } else {
+ require.Empty(t, resp.Notifications.WindowsMDMDiscoveryEndpoint)
+ }
+ }
+
+ // enable Windows MDM migration
acResp = appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
- "mdm": { "windows_enabled_and_configured": true }
+ "mdm": { "windows_migration_enabled": true }
}`), http.StatusOK, &acResp)
assert.True(t, acResp.MDM.WindowsEnabledAndConfigured)
- s.lastActivityOfTypeMatches(fleet.ActivityTypeEnabledWindowsMDM{}.ActivityName(), `{}`, 0)
+ assert.True(t, acResp.MDM.WindowsMigrationEnabled)
+ s.lastActivityMatches(fleet.ActivityTypeEnabledWindowsMDMMigration{}.ActivityName(), `{}`, 0)
// get the orbit config for each host, verify that only the expected ones
- // receive the "needs enrollment to Windows MDM" notification.
+ // receive the "needs enrollment to Windows MDM" and "needs migration" notifications.
+ // They still get enrollment notifications as we have not proceeded with enrollment.
for _, meta := range metadataHosts {
var resp orbitGetConfigResponse
s.DoJSON("POST", "/api/fleet/orbit/config",
json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *hostsBySuffix[meta.suffix].OrbitNodeKey)),
http.StatusOK, &resp)
require.Equal(t, meta.shouldEnroll, resp.Notifications.NeedsProgrammaticWindowsMDMEnrollment)
+ require.Equal(t, meta.shouldMigrate, resp.Notifications.NeedsMDMMigration)
require.False(t, resp.Notifications.NeedsProgrammaticWindowsMDMUnenrollment)
if meta.shouldEnroll {
require.Contains(t, resp.Notifications.WindowsMDMDiscoveryEndpoint, microsoft_mdm.MDE2DiscoveryPath)
@@ -6009,7 +6055,7 @@ func (s *integrationMDMTestSuite) TestAppConfigWindowsMDM() {
}
}
- // turn on MDM for a host
+ // turn on MDM for another host
orbitHost, _ := createWindowsHostThenEnrollMDM(s.ds, s.server.URL, t)
// disable Microsoft MDM
@@ -6017,9 +6063,10 @@ func (s *integrationMDMTestSuite) TestAppConfigWindowsMDM() {
"mdm": { "windows_enabled_and_configured": false }
}`), http.StatusOK, &acResp)
assert.False(t, acResp.MDM.WindowsEnabledAndConfigured)
+ assert.False(t, acResp.MDM.WindowsMigrationEnabled)
s.lastActivityOfTypeMatches(fleet.ActivityTypeDisabledWindowsMDM{}.ActivityName(), `{}`, 0)
- // get the orbit config for win-no-team should return true for the
+ // get the orbit config for that MDM-enrolled host returns true for the
// unenrollment notification
var resp orbitGetConfigResponse
s.DoJSON("POST", "/api/fleet/orbit/config",
@@ -6027,7 +6074,25 @@ func (s *integrationMDMTestSuite) TestAppConfigWindowsMDM() {
http.StatusOK, &resp)
require.True(t, resp.Notifications.NeedsProgrammaticWindowsMDMUnenrollment)
require.False(t, resp.Notifications.NeedsProgrammaticWindowsMDMEnrollment)
+ require.False(t, resp.Notifications.NeedsMDMMigration)
require.Empty(t, resp.Notifications.WindowsMDMDiscoveryEndpoint)
+
+ // get the orbit config for each host, only the fleet-enrolled ones get the unenrollment,
+ // and none get enrollment/migration (because MDM is now off).
+ for _, meta := range metadataHosts {
+ var resp orbitGetConfigResponse
+ s.DoJSON("POST", "/api/fleet/orbit/config",
+ json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *hostsBySuffix[meta.suffix].OrbitNodeKey)),
+ http.StatusOK, &resp)
+ require.False(t, resp.Notifications.NeedsProgrammaticWindowsMDMEnrollment)
+ require.False(t, resp.Notifications.NeedsMDMMigration)
+ if meta.enrolledName == fleet.WellKnownMDMFleet {
+ require.True(t, resp.Notifications.NeedsProgrammaticWindowsMDMUnenrollment)
+ } else {
+ require.False(t, resp.Notifications.NeedsProgrammaticWindowsMDMUnenrollment)
+ }
+ require.Empty(t, resp.Notifications.WindowsMDMDiscoveryEndpoint)
+ }
}
func (s *integrationMDMTestSuite) TestOrbitConfigNudgeSettings() {
@@ -11911,6 +11976,88 @@ func (s *integrationMDMTestSuite) TestSetupExperience() {
require.True(t, awaitingConfig)
}
+func (s *integrationMDMTestSuite) TestWindowsMigrationEnabled() {
+ t := s.T()
+
+ var acResp appConfigResponse
+ s.DoJSON("GET", "/api/latest/fleet/config", nil, http.StatusOK, &acResp)
+ require.True(t, acResp.MDM.WindowsEnabledAndConfigured)
+ require.False(t, acResp.MDM.WindowsMigrationEnabled)
+
+ s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
+ "mdm": {
+ "windows_migration_enabled": true
+ }
+ }`), http.StatusOK, &acResp)
+ require.True(t, acResp.MDM.WindowsEnabledAndConfigured)
+ require.True(t, acResp.MDM.WindowsMigrationEnabled)
+ s.lastActivityMatches(fleet.ActivityTypeEnabledWindowsMDMMigration{}.ActivityName(), "", 0)
+
+ s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
+ "mdm": {
+ "windows_migration_enabled": false
+ }
+ }`), http.StatusOK, &acResp)
+ require.True(t, acResp.MDM.WindowsEnabledAndConfigured)
+ require.False(t, acResp.MDM.WindowsMigrationEnabled)
+ s.lastActivityMatches(fleet.ActivityTypeDisabledWindowsMDMMigration{}.ActivityName(), "", 0)
+
+ // set migrations back to true to see if they turn false when turning MDM off
+ s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
+ "mdm": {
+ "windows_migration_enabled": true
+ }
+ }`), http.StatusOK, &acResp)
+ require.True(t, acResp.MDM.WindowsEnabledAndConfigured)
+ require.True(t, acResp.MDM.WindowsMigrationEnabled)
+ lastEnabledID := s.lastActivityMatches(fleet.ActivityTypeEnabledWindowsMDMMigration{}.ActivityName(), "", 0)
+
+ // not providing any mdm update should leave the current values
+ s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
+ "mdm": {}
+ }`), http.StatusOK, &acResp)
+ require.True(t, acResp.MDM.WindowsEnabledAndConfigured)
+ require.True(t, acResp.MDM.WindowsMigrationEnabled)
+ // no new activity was created
+ s.lastActivityOfTypeMatches(fleet.ActivityTypeEnabledWindowsMDMMigration{}.ActivityName(), "", lastEnabledID)
+
+ // set to true again does not generate a new activity, was already true
+ s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
+ "mdm": {
+ "windows_migration_enabled": true
+ }
+ }`), http.StatusOK, &acResp)
+ require.True(t, acResp.MDM.WindowsEnabledAndConfigured)
+ require.True(t, acResp.MDM.WindowsMigrationEnabled)
+ s.lastActivityOfTypeMatches(fleet.ActivityTypeEnabledWindowsMDMMigration{}.ActivityName(), "", lastEnabledID)
+
+ res := s.Do("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
+ "mdm": {
+ "windows_enabled_and_configured": false,
+ "windows_migration_enabled": true
+ }
+ }`), http.StatusUnprocessableEntity)
+ errMsg := extractServerErrorText(res.Body)
+ require.Contains(t, errMsg, "Windows MDM is not enabled")
+
+ // turn off Windows MDM and try to enable migrations in a distinct call
+ s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
+ "mdm": {
+ "windows_enabled_and_configured": false
+ }
+ }`), http.StatusOK, &acResp)
+ require.False(t, acResp.MDM.WindowsEnabledAndConfigured)
+ require.False(t, acResp.MDM.WindowsMigrationEnabled)
+
+ res = s.Do("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
+ "mdm": {
+ "windows_migration_enabled": true
+ }
+ }`), http.StatusUnprocessableEntity)
+ errMsg = extractServerErrorText(res.Body)
+ require.Contains(t, errMsg, "Windows MDM is not enabled")
+}
+
func (s *integrationMDMTestSuite) TestHostsCantTurnMDMOff() {
t := s.T()
iOSHost, _ := s.createAppleMobileHostThenEnrollMDM("ios")
diff --git a/server/service/microsoft_mdm.go b/server/service/microsoft_mdm.go
index 78fde975ffb8..4d79d45ce83f 100644
--- a/server/service/microsoft_mdm.go
+++ b/server/service/microsoft_mdm.go
@@ -615,14 +615,22 @@ func NewCertStoreProvisioningData(enrollmentType string, identityFingerprint str
return certStore
}
-// IsEligibleForWindowsMDMEnrollment returns true if the host can be enrolled
+// isEligibleForWindowsMDMEnrollment returns true if the host can be enrolled
// in Fleet's Windows MDM (if it was enabled).
-func IsEligibleForWindowsMDMEnrollment(host *fleet.Host, mdmInfo *fleet.HostMDM) bool {
+func isEligibleForWindowsMDMEnrollment(host *fleet.Host, mdmInfo *fleet.HostMDM) bool {
return host.FleetPlatform() == "windows" &&
host.IsOsqueryEnrolled() &&
(mdmInfo == nil || (!mdmInfo.IsServer && !mdmInfo.Enrolled))
}
+// isEligibleForWindowsMDMMigration returns true if the host can be migrated to
+// Fleet's Windows MDM (if it was enabled).
+func isEligibleForWindowsMDMMigration(host *fleet.Host, mdmInfo *fleet.HostMDM) bool {
+ return host.FleetPlatform() == "windows" &&
+ host.IsOsqueryEnrolled() &&
+ (mdmInfo != nil && !mdmInfo.IsServer && mdmInfo.Enrolled && mdmInfo.Name != fleet.WellKnownMDMFleet)
+}
+
// NewApplicationProvisioningData returns a new ApplicationProvisioningData Characteristic
// The Application Provisioning configuration is used for bootstrapping a device with an OMA DM account
// The paramenters here maps to the W7 application CSP
@@ -976,7 +984,7 @@ func (svc *Service) authBinarySecurityToken(ctx context.Context, authToken *flee
}
// This ensures that only hosts that are eligible for Windows enrollment can be enrolled
- if !IsEligibleForWindowsMDMEnrollment(host, mdmInfo) {
+ if !isEligibleForWindowsMDMEnrollment(host, mdmInfo) {
return "", "", errors.New("host is not elegible for Windows MDM enrollment")
}
diff --git a/server/service/orbit.go b/server/service/orbit.go
index e8e622a32773..79771afddad4 100644
--- a/server/service/orbit.go
+++ b/server/service/orbit.go
@@ -266,13 +266,26 @@ func (svc *Service) GetOrbitConfig(ctx context.Context) (fleet.OrbitConfig, erro
// set the host's orbit notifications for Windows MDM
if appConfig.MDM.WindowsEnabledAndConfigured {
- if IsEligibleForWindowsMDMEnrollment(host, mdmInfo) {
+ if isEligibleForWindowsMDMEnrollment(host, mdmInfo) {
discoURL, err := microsoft_mdm.ResolveWindowsMDMDiscovery(appConfig.ServerSettings.ServerURL)
if err != nil {
return fleet.OrbitConfig{}, err
}
notifs.WindowsMDMDiscoveryEndpoint = discoURL
notifs.NeedsProgrammaticWindowsMDMEnrollment = true
+ } else if appConfig.MDM.WindowsMigrationEnabled && isEligibleForWindowsMDMMigration(host, mdmInfo) {
+ notifs.NeedsMDMMigration = true
+
+ // Set the host to refetch the "critical queries" quickly for some time,
+ // to improve ingestion time of the unenroll and make the host eligible to
+ // enroll into Fleet faster.
+ if host.RefetchCriticalQueriesUntil == nil {
+ refetchUntil := svc.clock.Now().Add(fleet.RefetchMDMUnenrollCriticalQueryDuration)
+ host.RefetchCriticalQueriesUntil = &refetchUntil
+ if err := svc.ds.UpdateHostRefetchCriticalQueriesUntil(ctx, host.ID, &refetchUntil); err != nil {
+ return fleet.OrbitConfig{}, err
+ }
+ }
}
}
if !appConfig.MDM.WindowsEnabledAndConfigured {
diff --git a/server/service/osquery.go b/server/service/osquery.go
index 21555df37270..ce88aa625003 100644
--- a/server/service/osquery.go
+++ b/server/service/osquery.go
@@ -691,7 +691,8 @@ const alwaysTrueQuery = "SELECT 1"
// list of detail queries that are returned when only the critical queries
// should be returned (due to RefetchCriticalQueriesUntil timestamp being set).
var criticalDetailQueries = map[string]bool{
- "mdm": true,
+ "mdm": true,
+ "mdm_windows": true,
}
// detailQueriesForHost returns the map of detail+additional queries that should be executed by
diff --git a/server/service/osquery_test.go b/server/service/osquery_test.go
index 5be2974a12ae..d0f37e1e9071 100644
--- a/server/service/osquery_test.go
+++ b/server/service/osquery_test.go
@@ -179,7 +179,7 @@ func TestGetClientConfig(t *testing.T) {
// Check scheduled queries are loaded properly
conf, err = svc.GetClientConfig(ctx3)
require.NoError(t, err)
- assert.JSONEq(t, `{
+ assert.JSONEq(t, `{
"pack_by_label": {
"queries":{
"time":{"query":"select * from time","interval":30,"removed":false}
@@ -208,7 +208,7 @@ func TestGetClientConfig(t *testing.T) {
"version": ""
}
}
- }
+ }
}`,
string(conf["packs"].(json.RawMessage)),
)
@@ -1165,8 +1165,12 @@ func TestHostDetailQueries(t *testing.T) {
host.RefetchCriticalQueriesUntil = ptr.Time(mockClock.Now().Add(1 * time.Minute))
queries, discovery, err = svc.detailQueriesForHost(ctx, &host)
require.NoError(t, err)
- require.Equal(t, len(criticalDetailQueries), len(queries), distQueriesMapKeys(queries))
+ // host is darwin so it gets only the darwin critical query
+ require.Equal(t, 1, len(queries), distQueriesMapKeys(queries))
for name := range criticalDetailQueries {
+ if strings.HasSuffix(name, "_windows") {
+ continue
+ }
assert.Contains(t, queries, hostDetailQueryPrefix+name)
}
verifyDiscovery(t, queries, discovery)
diff --git a/server/service/osquery_utils/queries.go b/server/service/osquery_utils/queries.go
index da62dfef17a6..8f7600c16c5f 100644
--- a/server/service/osquery_utils/queries.go
+++ b/server/service/osquery_utils/queries.go
@@ -1885,6 +1885,11 @@ func directIngestMDMWindows(ctx context.Context, logger log.Logger, host *fleet.
return nil
}
+ if host.RefetchCriticalQueriesUntil != nil {
+ level.Debug(logger).Log("msg", "ingesting Windows mdm data during refetch critical queries window", "host_id", host.ID,
+ "data", fmt.Sprintf("%+v", rows))
+ }
+
data := rows[0]
var enrolled bool
var automatic bool
@@ -1900,13 +1905,20 @@ func directIngestMDMWindows(ctx context.Context, logger log.Logger, host *fleet.
}
isServer := strings.Contains(strings.ToLower(data["installation_type"]), "server")
+ mdmSolutionName := deduceMDMNameWindows(data)
+ if !enrolled && mdmSolutionName != fleet.WellKnownMDMFleet && host.RefetchCriticalQueriesUntil != nil {
+ // the host was unenrolled from a non-Fleet MDM solution, and the refetch
+ // critical queries timestamp was set, so clear it.
+ host.RefetchCriticalQueriesUntil = nil
+ }
+
return ds.SetOrUpdateMDMData(ctx,
host.ID,
isServer,
enrolled,
serverURL,
automatic,
- deduceMDMNameWindows(data),
+ mdmSolutionName,
"",
)
}
diff --git a/tools/cloner-check/generated_files/appconfig.txt b/tools/cloner-check/generated_files/appconfig.txt
index 2454027fa9d2..6c48aadd230a 100644
--- a/tools/cloner-check/generated_files/appconfig.txt
+++ b/tools/cloner-check/generated_files/appconfig.txt
@@ -158,6 +158,7 @@ github.com/fleetdm/fleet/v4/server/fleet/MDM MacOSMigration fleet.MacOSMigration
github.com/fleetdm/fleet/v4/server/fleet/MacOSMigration Enable bool
github.com/fleetdm/fleet/v4/server/fleet/MacOSMigration Mode fleet.MacOSMigrationMode string
github.com/fleetdm/fleet/v4/server/fleet/MacOSMigration WebhookURL string
+github.com/fleetdm/fleet/v4/server/fleet/MDM WindowsMigrationEnabled bool
github.com/fleetdm/fleet/v4/server/fleet/MDM EndUserAuthentication fleet.MDMEndUserAuthentication
github.com/fleetdm/fleet/v4/server/fleet/MDMEndUserAuthentication SSOProviderSettings fleet.SSOProviderSettings
github.com/fleetdm/fleet/v4/server/fleet/MDM WindowsEnabledAndConfigured bool
diff --git a/tools/mdm/windows/poc-mdm-server/README.md b/tools/mdm/windows/poc-mdm-server/README.md
index 74760b2de26d..eebc49061ad4 100644
--- a/tools/mdm/windows/poc-mdm-server/README.md
+++ b/tools/mdm/windows/poc-mdm-server/README.md
@@ -23,6 +23,17 @@ This code is MIT licensed and it was forked from [here](https://github.com/oscar
## Usage
On the server side, you just need to run the project using the already provided cert and keys. The certificate is in `.pfx` file format, so you need to extract the certificate and key first, see https://stackoverflow.com/a/59120388/1094941.
+The "Import password" is "testpassword", and the names of the output files matter, on Linux something like this works (assuming you are in the certs/ directory):
+
+```
+# for the cert
+$ openssl pkcs12 -in dev_cert_mdmwindows_com.pfx -clcerts -nokeys -out dev_cert_mdmwindows_com_cert.pem
+
+# for the key
+$ openssl pkcs12 -in dev_cert_mdmwindows_com.pfx -out dev_cert_mdmwindows_com.key -nocerts -nodes
+```
+
+Note that an asn1 error might occur when running the server, if that's the case you need to patch your local Go toolchain by running `$ go run ./patch/patch.go` (`GOROOT` env var must be set to point to your `go env GOROOT` directory). It may require `sudo` depending on where your `go` installation is (due to https://github.com/golang/go/issues/14017).
Next go to the project folder and run.
@@ -30,7 +41,9 @@ Next go to the project folder and run.
go run .
```
-On the Windows client side, you need to import a custom CA certificate to the certificate store, and populate the `hosts` file before running the Windows Enrollment. The certificate to import is on the certs directory and it is called `dev_cert_mdmwindows_com.pfx`. You need to copy this certificate to the client machine and run the powershell command below. This is required because the project uses a local dev https endpoint.
+Note that the server binds to the standard and usually firewall-protected `443` port, so you may need to configure your firewall to allow connections to it for the duration of your test.
+
+On the Windows client side, you need to import the custom CA certificate to the certificate store, and populate the `hosts` file before running the Windows Enrollment. The certificate to import is on the certs directory and it is called `dev_cert_mdmwindows_com.pfx`. You need to copy this certificate to the client machine and run the powershell command below (in the console, not in a powershell terminal). This is required because the project uses a local dev https endpoint.
1) Import certificate to Trusted CAs repository (be sure to update the path to the pfx certificate)
@@ -42,6 +55,8 @@ On the Windows client side, you need to import a custom CA certificate to the ce
echo autodiscovery.mdmwindows.com >> %SystemRoot%\System32\drivers\etc\hosts
echo enterpriseenrollment.mdmwindows.com >> %SystemRoot%\System32\drivers\etc\hosts
+To enroll the device into this MDM server, go to `Settings > Accounts > Access work or school` and click the connect button, enter the email provided to the server when you ran `go run .` (default: `demo@mdmwindows.com`) and it should automatically detect the server and proceed with enrollment. This is why the server must run on port `:443`, because it uses automatic discovery and will not attempt a custom port.
+
## Protocol Details
Below is the raw https exchange of the MS-MDE and MS-MDM protocols when run using the -verbose mode: