-
+
+
{title}
@@ -89,7 +99,13 @@ const Modal = ({
-
{children}
+
+
+ {isContentDisabled && (
+
+ )}
+
{children}
+
);
diff --git a/frontend/components/Modal/_styles.scss b/frontend/components/Modal/_styles.scss
index 33fc9939eb..04dc539176 100644
--- a/frontend/components/Modal/_styles.scss
+++ b/frontend/components/Modal/_styles.scss
@@ -16,7 +16,7 @@
}
}
- &__content {
+ &__content-wrapper {
margin-top: $pad-large;
font-size: $x-small;
@@ -100,4 +100,21 @@
width: auto;
}
}
+
+ // these styles are for the modal content when it is disabled
+ &__content-wrapper-disabled {
+ position: relative;
+ }
+
+ &__content-disabled {
+ transition: opacity 150ms ease-in-out;
+ opacity: 0.5; // this adds a disabled effect to the modal content
+ }
+
+ &__disabled-overlay {
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ z-index: 1000;
+ }
}
diff --git a/frontend/pages/policies/ManagePoliciesPage/components/AddPolicyModal/_styles.scss b/frontend/pages/policies/ManagePoliciesPage/components/AddPolicyModal/_styles.scss
index 924a16b0d8..c1cfe99bb9 100644
--- a/frontend/pages/policies/ManagePoliciesPage/components/AddPolicyModal/_styles.scss
+++ b/frontend/pages/policies/ManagePoliciesPage/components/AddPolicyModal/_styles.scss
@@ -1,17 +1,25 @@
.add-policy-modal {
height: 90%;
overflow: hidden;
- min-height: 460px;
- max-height: fit-content;
+
+ // we have to reach into the modal component classes to style the content
+ // correctly. This is because this modal always has a fixed height and
+ // the content is scrollable.
+ .modal__content-wrapper {
+ height: 95%;
+ }
.modal__content {
- height: 90%;
- overflow: scroll;
+ height: 100%;
display: flex;
flex-direction: column;
gap: $pad-large;
}
+ &__policy-selection {
+ overflow-y: auto;
+ }
+
.Select-multi-value-wrapper {
display: flex;
}
diff --git a/frontend/pages/policies/ManagePoliciesPage/components/OtherWorkflowsModal/OtherWorkflowsModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/OtherWorkflowsModal/OtherWorkflowsModal.tsx
index 75c66b5fd7..d34ae56ff6 100644
--- a/frontend/pages/policies/ManagePoliciesPage/components/OtherWorkflowsModal/OtherWorkflowsModal.tsx
+++ b/frontend/pages/policies/ManagePoliciesPage/components/OtherWorkflowsModal/OtherWorkflowsModal.tsx
@@ -367,6 +367,7 @@ const OtherWorkflowsModal = ({
title="Other workflows"
className={baseClass}
width="large"
+ isContentDisabled={isUpdating}
>
Date: Fri, 2 Aug 2024 12:52:52 +0100
Subject: [PATCH 008/612] fix issue with stacking banners after VPP banner was
added (#20938)
relates to #20939
fix an issue with stacking banners on the host details page after VPP
warning banner was added.
This also moves up the banner states into the app config to cut down on
code duplication on the host details page and the main content
component.
- [x] Manual QA for all new/changed functionality
---
.../components/MainContent/MainContent.tsx | 22 +++++++---------
frontend/context/app.tsx | 25 ++++++++++++++++++
.../HostDetailsBanners/HostDetailsBanners.tsx | 26 ++++++++++---------
3 files changed, 48 insertions(+), 25 deletions(-)
diff --git a/frontend/components/MainContent/MainContent.tsx b/frontend/components/MainContent/MainContent.tsx
index 5c322637d6..ad31ba4711 100644
--- a/frontend/components/MainContent/MainContent.tsx
+++ b/frontend/components/MainContent/MainContent.tsx
@@ -37,9 +37,12 @@ const MainContent = ({
config,
isPremiumTier,
noSandboxHosts,
- apnsExpiry = "",
- abmExpiry = "",
- vppExpiry = "",
+ isApplePnsExpired,
+ isAppleBmExpired,
+ isVppExpired,
+ willAppleBmExpire,
+ willApplePnsExpire,
+ willVppExpire,
} = useContext(AppContext);
const sandboxExpiryTime =
@@ -49,29 +52,22 @@ const MainContent = ({
const renderAppWideBanner = () => {
const isAppleBmTermsExpired = config?.mdm?.apple_bm_terms_expired;
- const isApplePnsExpired = hasLicenseExpired(apnsExpiry);
- const willApplePnsExpireIn30Days = willExpireWithinXDays(apnsExpiry, 30);
- const isAppleBmExpired = hasLicenseExpired(abmExpiry); // NOTE: See Rachel's related FIXME added to App.tsx in https://github.com/fleetdm/fleet/pull/19571
- const willAppleBmExpireIn30Days = willExpireWithinXDays(abmExpiry, 30);
const isFleetLicenseExpired = hasLicenseExpired(
config?.license.expiration || ""
);
- const isVppExpired = hasLicenseExpired(vppExpiry);
- const willVppExpireIn30Days = willExpireWithinXDays(vppExpiry, 30);
-
let banner: JSX.Element | null = null;
if (isPremiumTier) {
- if (isApplePnsExpired || willApplePnsExpireIn30Days) {
+ if (isApplePnsExpired || willApplePnsExpire) {
banner = ;
- } else if (isAppleBmExpired || willAppleBmExpireIn30Days) {
+ } else if (isAppleBmExpired || willAppleBmExpire) {
banner = ;
} else if (isAppleBmTermsExpired) {
banner = ;
} else if (isFleetLicenseExpired) {
banner = ;
- } else if (isVppExpired || willVppExpireIn30Days) {
+ } else if (isVppExpired || willVppExpire) {
banner = ;
}
}
diff --git a/frontend/context/app.tsx b/frontend/context/app.tsx
index bf84e72aa3..da774708d9 100644
--- a/frontend/context/app.tsx
+++ b/frontend/context/app.tsx
@@ -11,6 +11,7 @@ import {
import { IUser } from "interfaces/user";
import permissions from "utilities/permissions";
import sort from "utilities/sort";
+import { hasLicenseExpired, willExpireWithinXDays } from "utilities/helpers";
enum ACTIONS {
SET_AVAILABLE_TEAMS = "SET_AVAILABLE_TEAMS",
@@ -144,6 +145,12 @@ type InitialStateType = {
isOnlyObserver?: boolean;
isObserverPlus?: boolean;
isNoAccess?: boolean;
+ isAppleBmExpired: boolean;
+ isApplePnsExpired: boolean;
+ isVppExpired: boolean;
+ willAppleBmExpire: boolean;
+ willApplePnsExpire: boolean;
+ willVppExpire: boolean;
abmExpiry?: string;
apnsExpiry?: string;
vppExpiry?: string;
@@ -206,6 +213,12 @@ export const initialState = {
filteredSoftwarePath: undefined,
filteredQueriesPath: undefined,
filteredPoliciesPath: undefined,
+ isAppleBmExpired: false,
+ isApplePnsExpired: false,
+ isVppExpired: false,
+ willAppleBmExpire: false,
+ willApplePnsExpire: false,
+ willVppExpire: false,
setAvailableTeams: () => null,
setCurrentUser: () => null,
setCurrentTeam: () => null,
@@ -339,6 +352,8 @@ const reducer = (state: InitialStateType, action: IAction) => {
return {
...state,
abmExpiry,
+ isAppleBmExpired: hasLicenseExpired(abmExpiry),
+ willAppleBmExpire: willExpireWithinXDays(abmExpiry, 30),
};
}
case ACTIONS.SET_APNS_EXPIRY: {
@@ -346,6 +361,8 @@ const reducer = (state: InitialStateType, action: IAction) => {
return {
...state,
apnsExpiry,
+ isApplePnsExpired: hasLicenseExpired(apnsExpiry),
+ willApplePnsExpire: willExpireWithinXDays(apnsExpiry, 30),
};
}
case ACTIONS.SET_VPP_EXPIRY: {
@@ -353,6 +370,8 @@ const reducer = (state: InitialStateType, action: IAction) => {
return {
...state,
vppExpiry,
+ isVppExpired: hasLicenseExpired(vppExpiry),
+ willVppExpire: willExpireWithinXDays(vppExpiry, 30),
};
}
case ACTIONS.SET_SANDBOX_EXPIRY: {
@@ -418,6 +437,12 @@ const AppProvider = ({ children }: Props): JSX.Element => {
abmExpiry: state.abmExpiry,
apnsExpiry: state.apnsExpiry,
vppExpiry: state.vppExpiry,
+ isAppleBmExpired: state.isAppleBmExpired,
+ isApplePnsExpired: state.isApplePnsExpired,
+ isVppExpired: state.isVppExpired,
+ willAppleBmExpire: state.willAppleBmExpire,
+ willApplePnsExpire: state.willApplePnsExpire,
+ willVppExpire: state.willVppExpire,
noSandboxHosts: state.noSandboxHosts,
filteredHostsPath: state.filteredHostsPath,
filteredSoftwarePath: state.filteredSoftwarePath,
diff --git a/frontend/pages/hosts/details/HostDetailsPage/components/HostDetailsBanners/HostDetailsBanners.tsx b/frontend/pages/hosts/details/HostDetailsPage/components/HostDetailsBanners/HostDetailsBanners.tsx
index 89632164b0..2a35d4a418 100644
--- a/frontend/pages/hosts/details/HostDetailsPage/components/HostDetailsBanners/HostDetailsBanners.tsx
+++ b/frontend/pages/hosts/details/HostDetailsPage/components/HostDetailsBanners/HostDetailsBanners.tsx
@@ -23,21 +23,21 @@ const HostDetailsBanners = ({
connectedToFleetMdm,
diskEncryptionStatus,
}: IHostDetailsBannersProps) => {
- const { config, isPremiumTier, apnsExpiry, abmExpiry } = useContext(
- AppContext
- );
+ const {
+ config,
+ isPremiumTier,
+ isAppleBmExpired,
+ isApplePnsExpired,
+ isVppExpired,
+ willAppleBmExpire,
+ willApplePnsExpire,
+ willVppExpire,
+ } = useContext(AppContext);
// Checks to see if an app-wide banner is being shown (the ABM terms, ABM expiry,
// or APNs expiry banner) in a parent component. App-wide banners found in parent
// component take priority over host details page-level banners.
const isAppleBmTermsExpired = config?.mdm?.apple_bm_terms_expired;
- const isApplePnsExpired = hasLicenseExpired(apnsExpiry || "");
- const willApplePnsExpireIn30Days = willExpireWithinXDays(
- apnsExpiry || "",
- 30
- );
- const isAppleBmExpired = hasLicenseExpired(abmExpiry || "");
- const willAppleBmExpireIn30Days = willExpireWithinXDays(abmExpiry || "", 30);
const isFleetLicenseExpired = hasLicenseExpired(
config?.license.expiration || ""
);
@@ -46,9 +46,11 @@ const HostDetailsBanners = ({
isPremiumTier &&
(isAppleBmTermsExpired ||
isApplePnsExpired ||
- willApplePnsExpireIn30Days ||
+ willApplePnsExpire ||
isAppleBmExpired ||
- willAppleBmExpireIn30Days ||
+ willAppleBmExpire ||
+ isVppExpired ||
+ willVppExpire ||
isFleetLicenseExpired);
const isMdmUnenrolled =
From 1b5380a5fbc7d334c2a719fb4e051f34b69becc6 Mon Sep 17 00:00:00 2001
From: Gabriel Hernandez
Date: Fri, 2 Aug 2024 12:54:00 +0100
Subject: [PATCH 009/612] copy update on the my device page for automatic
enrollment modal (#20874)
relates to #20310
quick copy update for the automatic enrollment modal on my device page.
- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
- [x] Manual QA for all new/changed functionality
---
changes/20310-update-my-device-copy | 1 +
.../AutoEnrollMdmModal/AutoEnrollMdmModal.tsx | 10 ++++++----
2 files changed, 7 insertions(+), 4 deletions(-)
create mode 100644 changes/20310-update-my-device-copy
diff --git a/changes/20310-update-my-device-copy b/changes/20310-update-my-device-copy
new file mode 100644
index 0000000000..9a91f6432a
--- /dev/null
+++ b/changes/20310-update-my-device-copy
@@ -0,0 +1 @@
+- update copy on for automica enrollment modal on my device page.
diff --git a/frontend/pages/hosts/details/DeviceUserPage/AutoEnrollMdmModal/AutoEnrollMdmModal.tsx b/frontend/pages/hosts/details/DeviceUserPage/AutoEnrollMdmModal/AutoEnrollMdmModal.tsx
index 2f40fa82b6..25ff5b8f89 100644
--- a/frontend/pages/hosts/details/DeviceUserPage/AutoEnrollMdmModal/AutoEnrollMdmModal.tsx
+++ b/frontend/pages/hosts/details/DeviceUserPage/AutoEnrollMdmModal/AutoEnrollMdmModal.tsx
@@ -23,6 +23,7 @@ const AutoEnrollMdmModal = ({
.map((s) => parseInt(s, 10));
isMacOsSonomaOrLater = major >= 14;
}
+
return (
From the Apple menu in the top left corner of your screen, select{" "}
- System Settings or System Preferences.
+ System Settings.
{isMacOsSonomaOrLater ? (
@@ -48,7 +49,8 @@ const AutoEnrollMdmModal = ({
) : (
<>
In the search bar, type “Profiles.” Select Profiles, find
- and select Enrollment Profile, and select Install.
+ and double-click the [Organization name] enrollment{" "}
+ profile.
>
)}
@@ -56,8 +58,8 @@ const AutoEnrollMdmModal = ({
Enter your password, and select Enroll.
- Close this window and select Refetch on your My device page
- to tell your organization that MDM is on.
+ Select Done to close this window and select Refetch on
+ your My Device page to tell your organization that MDM is on.
From 695801bff6f4456ab0eb16598f575cb0d9d9679e Mon Sep 17 00:00:00 2001
From: RachelElysia <71795832+RachelElysia@users.noreply.github.com>
Date: Fri, 2 Aug 2024 09:42:49 -0400
Subject: [PATCH 010/612] Fleet UI: Navigate between no teams tabs (#20969)
---
frontend/components/top_nav/SiteTopNav/SiteTopNav.tsx | 8 +++-----
frontend/components/top_nav/SiteTopNav/navItems.ts | 5 ++---
frontend/components/top_nav/UserMenu/UserMenu.tsx | 4 +---
3 files changed, 6 insertions(+), 11 deletions(-)
diff --git a/frontend/components/top_nav/SiteTopNav/SiteTopNav.tsx b/frontend/components/top_nav/SiteTopNav/SiteTopNav.tsx
index 86944020b6..7dbf45785d 100644
--- a/frontend/components/top_nav/SiteTopNav/SiteTopNav.tsx
+++ b/frontend/components/top_nav/SiteTopNav/SiteTopNav.tsx
@@ -56,6 +56,7 @@ const REGEX_GLOBAL_PAGES = {
const REGEX_EXCLUDE_NO_TEAM_PAGES = {
MANAGE_POLICIES: /\/policies\/manage/i,
+ MANAGE_QUERIES: /\/queries\/manage/i,
};
const testDetailPage = (path: string, re: RegExp) => {
@@ -96,7 +97,6 @@ const SiteTopNav = ({
isGlobalMaintainer,
isAnyTeamMaintainer,
isNoAccess,
- isSandboxMode,
} = useContext(AppContext);
const isActiveDetailPage = isDetailPage(currentPath);
@@ -187,7 +187,7 @@ const SiteTopNav = ({
{
@@ -238,7 +237,6 @@ const SiteTopNav = ({
currentUser={currentUser}
isAnyTeamAdmin={isAnyTeamAdmin}
isGlobalAdmin={isGlobalAdmin}
- isSandboxMode={isSandboxMode}
/>
);
diff --git a/frontend/components/top_nav/SiteTopNav/navItems.ts b/frontend/components/top_nav/SiteTopNav/navItems.ts
index cbf0654723..00e145ae4b 100644
--- a/frontend/components/top_nav/SiteTopNav/navItems.ts
+++ b/frontend/components/top_nav/SiteTopNav/navItems.ts
@@ -27,8 +27,7 @@ export default (
isAnyTeamAdmin = false,
isAnyTeamMaintainer = false,
isGlobalMaintainer = false,
- isNoAccess = false,
- isSandboxMode = false
+ isNoAccess = false
): INavItem[] => {
if (!user) {
return [];
@@ -67,7 +66,7 @@ export default (
regex: new RegExp(`^${URL_PREFIX}/controls/`),
pathname: PATHS.CONTROLS,
},
- exclude: isSandboxMode || !isMaintainerOrAdmin,
+ exclude: !isMaintainerOrAdmin,
withParams: { type: "query", names: ["team_id"] },
},
{
diff --git a/frontend/components/top_nav/UserMenu/UserMenu.tsx b/frontend/components/top_nav/UserMenu/UserMenu.tsx
index 4e08464120..38f7986a43 100644
--- a/frontend/components/top_nav/UserMenu/UserMenu.tsx
+++ b/frontend/components/top_nav/UserMenu/UserMenu.tsx
@@ -17,7 +17,6 @@ interface IUserMenuProps {
isAnyTeamAdmin: boolean | undefined;
isGlobalAdmin: boolean | undefined;
currentUser: IUser;
- isSandboxMode?: boolean;
}
const UserMenu = ({
@@ -26,7 +25,6 @@ const UserMenu = ({
isAnyTeamAdmin,
isGlobalAdmin,
currentUser,
- isSandboxMode = false,
}: IUserMenuProps): JSX.Element => {
const accountNavigate = onNavItemClick(PATHS.ACCOUNT);
const dropdownItems = [
@@ -44,7 +42,7 @@ const UserMenu = ({
},
];
- if (isGlobalAdmin && !isSandboxMode) {
+ if (isGlobalAdmin) {
const manageUsersNavigate = onNavItemClick(PATHS.ADMIN_USERS);
const manageUserNavItem = {
From 0a15647e108f0291598c78636f8e890872cf8310 Mon Sep 17 00:00:00 2001
From: Dante Catalfamo <43040593+dantecatalfamo@users.noreply.github.com>
Date: Fri, 2 Aug 2024 10:47:40 -0400
Subject: [PATCH 011/612] Host software deleted at remigration (#20996)
# Recreate out of order migration, replace `docker-compose` with `docker compose` in db test runner
---
.github/workflows/test-db-changes.yml | 2 +-
...=> 20240802101043_AddSoftwareInstallResultDeletedAt.go} | 7 ++++---
server/datastore/mysql/schema.sql | 2 +-
tools/dbutils/schema_generator.go | 2 +-
4 files changed, 7 insertions(+), 6 deletions(-)
rename server/datastore/mysql/migrations/tables/{20240729120947_AddSoftwareInstallResultDeletedAt.go => 20240802101043_AddSoftwareInstallResultDeletedAt.go} (75%)
diff --git a/.github/workflows/test-db-changes.yml b/.github/workflows/test-db-changes.yml
index 301645008e..ecfe464072 100644
--- a/.github/workflows/test-db-changes.yml
+++ b/.github/workflows/test-db-changes.yml
@@ -46,7 +46,7 @@ jobs:
- name: Start Infra Dependencies
# Use & to background this
- run: docker-compose up -d mysql_test &
+ run: docker compose up -d mysql_test &
- name: Verify test schema changes
run: |
diff --git a/server/datastore/mysql/migrations/tables/20240729120947_AddSoftwareInstallResultDeletedAt.go b/server/datastore/mysql/migrations/tables/20240802101043_AddSoftwareInstallResultDeletedAt.go
similarity index 75%
rename from server/datastore/mysql/migrations/tables/20240729120947_AddSoftwareInstallResultDeletedAt.go
rename to server/datastore/mysql/migrations/tables/20240802101043_AddSoftwareInstallResultDeletedAt.go
index bd4219364d..b5f7e79213 100644
--- a/server/datastore/mysql/migrations/tables/20240729120947_AddSoftwareInstallResultDeletedAt.go
+++ b/server/datastore/mysql/migrations/tables/20240802101043_AddSoftwareInstallResultDeletedAt.go
@@ -6,10 +6,11 @@ import (
)
func init() {
- MigrationClient.AddMigration(Up_20240729120947, Down_20240729120947)
+ MigrationClient.AddMigration(Up_20240802101043, Down_20240802101043)
}
-func Up_20240729120947(tx *sql.Tx) error {
+// This is a new copy of a previous out-of-order migration
+func Up_20240802101043(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE host_software_installs ADD COLUMN host_deleted_at timestamp NULL DEFAULT NULL")
if err != nil {
return fmt.Errorf("failed to create host_deleted_at column on host_software_installs table: %w", err)
@@ -33,6 +34,6 @@ AND
return nil
}
-func Down_20240729120947(tx *sql.Tx) error {
+func Down_20240802101043(tx *sql.Tx) error {
return nil
}
diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql
index 73e02ac322..22ac9295c1 100644
--- a/server/datastore/mysql/schema.sql
+++ b/server/datastore/mysql/schema.sql
@@ -972,7 +972,7 @@ CREATE TABLE `migration_status_tables` (
UNIQUE KEY `id` (`id`)
) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=294 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,20240729120947,1,'2020-01-01 01:01:01'),(289,20240730171504,1,'2020-01-01 01:01:01'),(290,20240730174056,1,'2020-01-01 01:01:01'),(291,20240730215453,1,'2020-01-01 01:01:01'),(292,20240730374423,1,'2020-01-01 01:01:01'),(293,20240801115359,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');
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `mobile_device_management_solutions` (
diff --git a/tools/dbutils/schema_generator.go b/tools/dbutils/schema_generator.go
index 2fecce8683..60c01869d3 100644
--- a/tools/dbutils/schema_generator.go
+++ b/tools/dbutils/schema_generator.go
@@ -67,7 +67,7 @@ func main() {
// Dump schema to dumpfile
cmd := exec.Command(
- "docker-compose", "exec", "-T", "mysql_test",
+ "docker", "compose", "exec", "-T", "mysql_test",
// Command run inside container
"mysqldump", "-u"+testUsername, "-p"+testPassword, "schemadb", "--compact", "--skip-comments",
)
From 4b2c8a6f02b0f848a9b276e0739c4270c8eba75c Mon Sep 17 00:00:00 2001
From: jacobshandling <61553566+jacobshandling@users.noreply.github.com>
Date: Fri, 2 Aug 2024 08:12:43 -0700
Subject: [PATCH 012/612] UI: Coordinate sibling refetches via parent state
(#20973)
## #20965
https://www.loom.com/share/52ff44615e2e41a99c129acd3d2427bf?sid=f90ead66-9730-4538-95db-a70bf971b5a8
- [x] Manual QA for all new/changed functionality)
---------
Co-authored-by: Jacob Shandling
---
frontend/pages/SoftwarePage/SoftwarePage.tsx | 5 +++++
.../SoftwareTitles/SoftwareTitles.tsx | 4 ++++
.../components/AddPackage/AddPackage.tsx | 11 +++++++++--
.../AddSoftwareModal/AddSoftwareModal.tsx | 16 ++++++++++++++--
.../components/AppStoreVpp/AppStoreVpp.tsx | 10 +++++++++-
frontend/services/entities/software.ts | 4 ++++
6 files changed, 45 insertions(+), 5 deletions(-)
diff --git a/frontend/pages/SoftwarePage/SoftwarePage.tsx b/frontend/pages/SoftwarePage/SoftwarePage.tsx
index 7def709f9a..5887d0dd11 100644
--- a/frontend/pages/SoftwarePage/SoftwarePage.tsx
+++ b/frontend/pages/SoftwarePage/SoftwarePage.tsx
@@ -151,6 +151,9 @@ const SoftwarePage = ({ children, router, location }: ISoftwarePageProps) => {
const [showPreviewTicketModal, setShowPreviewTicketModal] = useState(false);
const [showAddSoftwareModal, setShowAddSoftwareModal] = useState(false);
const [resetPageIndex, setResetPageIndex] = useState(false);
+ const [addedSoftwareToken, setAddedSoftwareToken] = useState(
+ null
+ );
const {
currentTeamId,
@@ -385,6 +388,7 @@ const SoftwarePage = ({ children, router, location }: ISoftwarePageProps) => {
showExploitedVulnerabilitiesOnly,
softwareFilter,
resetPageIndex,
+ addedSoftwareToken,
})}
);
@@ -424,6 +428,7 @@ const SoftwarePage = ({ children, router, location }: ISoftwarePageProps) => {
teamId={currentTeamId ?? 0}
router={router}
onExit={toggleAddSoftwareModal}
+ setAddedSoftwareToken={setAddedSoftwareToken}
/>
)}
diff --git a/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTitles.tsx b/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTitles.tsx
index 1a90c284f5..6456d616ea 100644
--- a/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTitles.tsx
+++ b/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTitles.tsx
@@ -43,6 +43,7 @@ interface ISoftwareTitlesProps {
currentPage: number;
teamId?: number;
resetPageIndex: boolean;
+ addedSoftwareToken: string | null;
}
const SoftwareTitles = ({
@@ -56,6 +57,7 @@ const SoftwareTitles = ({
currentPage,
teamId,
resetPageIndex,
+ addedSoftwareToken,
}: ISoftwareTitlesProps) => {
const showVersions = location.pathname === PATHS.SOFTWARE_VERSIONS;
@@ -80,6 +82,7 @@ const SoftwareTitles = ({
orderDirection,
orderKey,
teamId,
+ addedSoftwareToken,
...getSoftwareFilterForQueryKey(softwareFilter),
},
],
@@ -113,6 +116,7 @@ const SoftwareTitles = ({
orderKey,
teamId,
vulnerable: softwareFilter === "vulnerableSoftware",
+ addedSoftwareToken,
},
],
({ queryKey: [queryKey] }) =>
diff --git a/frontend/pages/SoftwarePage/components/AddPackage/AddPackage.tsx b/frontend/pages/SoftwarePage/components/AddPackage/AddPackage.tsx
index 78e559b750..2e7e4b17a5 100644
--- a/frontend/pages/SoftwarePage/components/AddPackage/AddPackage.tsx
+++ b/frontend/pages/SoftwarePage/components/AddPackage/AddPackage.tsx
@@ -21,9 +21,15 @@ interface IAddPackageProps {
teamId: number;
router: InjectedRouter;
onExit: () => void;
+ setAddedSoftwareToken: (token: string) => void;
}
-const AddPackage = ({ teamId, router, onExit }: IAddPackageProps) => {
+const AddPackage = ({
+ teamId,
+ router,
+ onExit,
+ setAddedSoftwareToken,
+}: IAddPackageProps) => {
const { renderFlash } = useContext(NotificationContext);
const [isUploading, setIsUploading] = useState(false);
@@ -86,7 +92,8 @@ const AddPackage = ({ teamId, router, onExit }: IAddPackageProps) => {
} else {
newQueryParams.available_for_install = true;
}
-
+ // any unique string - triggers SW refetch
+ setAddedSoftwareToken(`${Date.now()}`);
router.push(
`${PATHS.SOFTWARE_TITLES}?${buildQueryStringFromParams(newQueryParams)}`
);
diff --git a/frontend/pages/SoftwarePage/components/AddSoftwareModal/AddSoftwareModal.tsx b/frontend/pages/SoftwarePage/components/AddSoftwareModal/AddSoftwareModal.tsx
index f6af29e7b4..7f55adbc9a 100644
--- a/frontend/pages/SoftwarePage/components/AddSoftwareModal/AddSoftwareModal.tsx
+++ b/frontend/pages/SoftwarePage/components/AddSoftwareModal/AddSoftwareModal.tsx
@@ -37,12 +37,14 @@ interface IAddSoftwareModalProps {
teamId: number;
router: InjectedRouter;
onExit: () => void;
+ setAddedSoftwareToken: (token: string) => void;
}
const AddSoftwareModal = ({
teamId,
router,
onExit,
+ setAddedSoftwareToken,
}: IAddSoftwareModalProps) => {
return (
App Store (VPP)
-
+
-
+
diff --git a/frontend/pages/SoftwarePage/components/AppStoreVpp/AppStoreVpp.tsx b/frontend/pages/SoftwarePage/components/AppStoreVpp/AppStoreVpp.tsx
index 874d2d8849..4a210b93d5 100644
--- a/frontend/pages/SoftwarePage/components/AppStoreVpp/AppStoreVpp.tsx
+++ b/frontend/pages/SoftwarePage/components/AppStoreVpp/AppStoreVpp.tsx
@@ -130,9 +130,15 @@ interface IAppStoreVppProps {
teamId: number;
router: InjectedRouter;
onExit: () => void;
+ setAddedSoftwareToken: (token: string) => void;
}
-const AppStoreVpp = ({ teamId, router, onExit }: IAppStoreVppProps) => {
+const AppStoreVpp = ({
+ teamId,
+ router,
+ onExit,
+ setAddedSoftwareToken,
+}: IAppStoreVppProps) => {
const { renderFlash } = useContext(NotificationContext);
const [isSubmitDisabled, setIsSubmitDisabled] = useState(true);
const [selectedApp, setSelectedApp] = useState(null);
@@ -189,6 +195,8 @@ const AppStoreVpp = ({ teamId, router, onExit }: IAppStoreVppProps) => {
team_id: teamId,
available_for_install: true,
});
+ // any unique string - triggers SW refetch
+ setAddedSoftwareToken(`${Date.now()}`);
router.push(`${PATHS.SOFTWARE}?${queryParams}`);
} catch (e) {
renderFlash("error", getErrorMessage(e));
diff --git a/frontend/services/entities/software.ts b/frontend/services/entities/software.ts
index ecb4d98847..d3f85f9d16 100644
--- a/frontend/services/entities/software.ts
+++ b/frontend/services/entities/software.ts
@@ -56,10 +56,14 @@ export interface ISoftwareVersionResponse {
}
export interface ISoftwareVersionsQueryKey extends ISoftwareApiParams {
+ // used to trigger software refetches from sibling pages
+ addedSoftwareToken: string | null;
scope: "software-versions";
}
export interface ISoftwareTitlesQueryKey extends ISoftwareApiParams {
+ // used to trigger software refetches from sibling pages
+ addedSoftwareToken: string | null;
scope: "software-titles";
}
From 33fa8f2fe976cb9eaaa3ebfee6d37d7ad00912e5 Mon Sep 17 00:00:00 2001
From: jacobshandling <61553566+jacobshandling@users.noreply.github.com>
Date: Fri, 2 Aug 2024 08:24:10 -0700
Subject: [PATCH 013/612] =?UTF-8?q?UI=20=E2=80=93=20adjust=20disk=20encryp?=
=?UTF-8?q?tion=20table=20style=20(#20981)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## #20395
- [x] Changes file added for user-visible changes in `changes/`
- [x] Manual QA for all new/changed functionality
---------
Co-authored-by: Jacob Shandling
---
changes/20395-DE-table-style-fix | 1 +
.../OSSettings/_styles.scss | 1 +
.../DiskEncryptionTable/_styles.scss | 30 +++++++++++++++----
3 files changed, 26 insertions(+), 6 deletions(-)
create mode 100644 changes/20395-DE-table-style-fix
diff --git a/changes/20395-DE-table-style-fix b/changes/20395-DE-table-style-fix
new file mode 100644
index 0000000000..8907c36986
--- /dev/null
+++ b/changes/20395-DE-table-style-fix
@@ -0,0 +1 @@
+* Fix a styling issue in the Controls > OS Settings > disk encryption table
\ No newline at end of file
diff --git a/frontend/pages/ManageControlsPage/OSSettings/_styles.scss b/frontend/pages/ManageControlsPage/OSSettings/_styles.scss
index 986234a842..b9c2bf11a5 100644
--- a/frontend/pages/ManageControlsPage/OSSettings/_styles.scss
+++ b/frontend/pages/ManageControlsPage/OSSettings/_styles.scss
@@ -12,6 +12,7 @@
&__side-nav {
.side-nav__nav-list {
top: 0;
+ padding-right: 40px;
}
}
diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/DiskEncryption/components/DiskEncryptionTable/_styles.scss b/frontend/pages/ManageControlsPage/OSSettings/cards/DiskEncryption/components/DiskEncryptionTable/_styles.scss
index 74a24f3ac2..45790a5389 100644
--- a/frontend/pages/ManageControlsPage/OSSettings/cards/DiskEncryption/components/DiskEncryptionTable/_styles.scss
+++ b/frontend/pages/ManageControlsPage/OSSettings/cards/DiskEncryption/components/DiskEncryptionTable/_styles.scss
@@ -9,11 +9,29 @@
border-right: none;
}
- @media (max-width: 1120px) {
- .view-hosts-link {
- span {
- display: none;
- }
+ .linkToFilteredHosts__header {
+ width: auto;
+ max-width: 120px;
+ }
+
+}
+
+@media (max-width: 1120px) {
+ .view-hosts-link {
+ span {
+ display: none;
}
}
-}
+ .linkToFilteredHosts {
+ &__header {
+ width: 0;
+ .column-header {
+ width: 0;
+ }
+ }
+ &__cell {
+ width: min-content;
+ }
+
+ }
+}
\ No newline at end of file
From d1149a0a7d3d5ff68f6b842191a02c06000a48e4 Mon Sep 17 00:00:00 2001
From: Benjamin Edwards
Date: Fri, 2 Aug 2024 11:34:32 -0400
Subject: [PATCH 014/612] Fix decoding enrollment profiles (#20984)
---
server/service/apple_mdm.go | 6 +++++-
server/service/integration_mdm_lifecycle_test.go | 3 ++-
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/server/service/apple_mdm.go b/server/service/apple_mdm.go
index 3f1ef747a3..3ff24a0eb0 100644
--- a/server/service/apple_mdm.go
+++ b/server/service/apple_mdm.go
@@ -3463,9 +3463,13 @@ func RenewSCEPCertificates(
}
}
- migrationEnrollmentProfile := os.Getenv("FLEET_SILENT_MIGRATION_ENROLLMENT_PROFILE")
+ decodedMigrationEnrollmentProfile, err := base64.StdEncoding.DecodeString(os.Getenv("FLEET_SILENT_MIGRATION_ENROLLMENT_PROFILE"))
+ if err != nil {
+ return ctxerr.Wrap(ctx, err, "failed to decode silent migration enrollment profile")
+ }
hasAssocsFromMigration := len(assocsFromMigration) > 0
+ migrationEnrollmentProfile := string(decodedMigrationEnrollmentProfile)
if migrationEnrollmentProfile == "" && hasAssocsFromMigration {
level.Debug(logger).Log("msg", "found devices from migration that need SCEP renewals but FLEET_SILENT_MIGRATION_ENROLLMENT_PROFILE is empty")
}
diff --git a/server/service/integration_mdm_lifecycle_test.go b/server/service/integration_mdm_lifecycle_test.go
index 5dce43108d..bd467afe55 100644
--- a/server/service/integration_mdm_lifecycle_test.go
+++ b/server/service/integration_mdm_lifecycle_test.go
@@ -3,6 +3,7 @@ package service
import (
"context"
"crypto/x509"
+ "encoding/base64"
"encoding/json"
"encoding/xml"
"fmt"
@@ -772,7 +773,7 @@ func (s *integrationMDMTestSuite) TestLifecycleSCEPCertExpiration() {
require.NoError(t, err)
// set the env var, and run the cron
- t.Setenv("FLEET_SILENT_MIGRATION_ENROLLMENT_PROFILE", "")
+ t.Setenv("FLEET_SILENT_MIGRATION_ENROLLMENT_PROFILE", base64.StdEncoding.EncodeToString([]byte("")))
err = RenewSCEPCertificates(ctx, logger, s.ds, &fleetCfg, s.mdmCommander)
require.NoError(t, err)
checkRenewCertCommand(migratedDevice, "", "")
From b846f90e542f19446bc51616fab83c48c2147ab3 Mon Sep 17 00:00:00 2001
From: Dave Herder <27025660+dherder@users.noreply.github.com>
Date: Fri, 2 Aug 2024 08:40:29 -0700
Subject: [PATCH 015/612] Add reference to process_etw_events table (#20947)
---
articles/osquery-evented-tables-overview.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/articles/osquery-evented-tables-overview.md b/articles/osquery-evented-tables-overview.md
index 883f0bc8ab..f1316a85f1 100644
--- a/articles/osquery-evented-tables-overview.md
+++ b/articles/osquery-evented-tables-overview.md
@@ -121,7 +121,7 @@ On macOS, there are two utilities that enable osquery process auditing: [OpenBSM
To use the `es_process_events` tables, use the flag `--disable_endpointsecurity=false`. See the [EndpointSecurity instructions](https://osquery.readthedocs.io/en/latest/deployment/process-auditing/#auditing-processes-with-endpointsecurity) for more information. To use `process_events` and `socket_events` with OpenBSM, see the [OpenBSM instructions](https://osquery.readthedocs.io/en/latest/deployment/process-auditing/#auditing-processes-with-openbsm).
#### Windows
-Currently, osquery does not support process auditing for Windows. To learn more about process auditing on Windows, visit [Microsoft's security auditing overview](https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/security-auditing-overview). Fleet is tracking work to build process auditing for Windows in osquery. [Stay up to date on GitHub](https://github.com/fleetdm/fleet/issues/7732).
+Fleet supports auditing process events on Windows via the `process_etw_events` table. To learn more about process auditing on Windows, visit [Microsoft's security auditing overview](https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/security-auditing-overview). Fleet is tracking work to add file auditing for Windows in osquery. [Stay up to date on GitHub](https://github.com/fleetdm/fleet/issues/20946).
### YARA scanning
[YARA](https://virustotal.github.io/yara/) is a malware research and detection tool available on Linux and macOS that allows users to create descriptions of malware families based on patterns of text or binary code. Each potential piece of malware is matched against a YARA rule and triggers if the specified conditions are met.
From 209a9b29a5d32fe159113395687760987c0df5fa Mon Sep 17 00:00:00 2001
From: Gabriel Hernandez
Date: Fri, 2 Aug 2024 18:56:25 +0100
Subject: [PATCH 016/612] Remove vpp warning banner when it has been
successfuly reuploaded (#21003)
quick fix to remove the vpp warning banner when the user has
successfully uploaded a new one.
---
.../IntegrationsPage/cards/Vpp/VppSetupPage/VppSetupPage.tsx | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/frontend/pages/admin/IntegrationsPage/cards/Vpp/VppSetupPage/VppSetupPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/Vpp/VppSetupPage/VppSetupPage.tsx
index a4289664e6..84d75fd073 100644
--- a/frontend/pages/admin/IntegrationsPage/cards/Vpp/VppSetupPage/VppSetupPage.tsx
+++ b/frontend/pages/admin/IntegrationsPage/cards/Vpp/VppSetupPage/VppSetupPage.tsx
@@ -4,6 +4,7 @@ import { useQuery } from "react-query";
import { AxiosError } from "axios";
import PATHS from "router/paths";
+import { AppContext } from "context/app";
import { NotificationContext } from "context/notification";
import { getErrorReason } from "interfaces/errors";
import mdmAppleAPI, { IGetVppInfoResponse } from "services/entities/mdm_apple";
@@ -121,6 +122,7 @@ interface IVppSetupPageProps {
const VppSetupPage = ({ router }: IVppSetupPageProps) => {
const [showDisableModal, setShowDisableModal] = useState(false);
const [showRenewModal, setShowRenewModal] = useState(false);
+ const { setVppExpiry } = useContext(AppContext);
const {
data: vppData,
@@ -134,6 +136,9 @@ const VppSetupPage = ({ router }: IVppSetupPageProps) => {
{
...DEFAULT_USE_QUERY_OPTIONS,
retry: false,
+ onSuccess: (data) => {
+ setVppExpiry(data.renew_date);
+ },
}
);
From 2f479b3ba938759055af135504395b9498db08da Mon Sep 17 00:00:00 2001
From: Lucas Manuel Rodriguez
Date: Fri, 2 Aug 2024 14:57:50 -0300
Subject: [PATCH 017/612] Release osqueryd 5.13.0 (#20949)
---
.github/workflows/generate-osqueryd-targets.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/generate-osqueryd-targets.yml b/.github/workflows/generate-osqueryd-targets.yml
index 12e08bddac..b5518c995a 100644
--- a/.github/workflows/generate-osqueryd-targets.yml
+++ b/.github/workflows/generate-osqueryd-targets.yml
@@ -24,7 +24,7 @@ defaults:
shell: bash
env:
- OSQUERY_VERSION: 5.12.2
+ OSQUERY_VERSION: 5.13.0
permissions:
contents: read
From dc5ff724ecd5f7ef427d14765873fa4d39081094 Mon Sep 17 00:00:00 2001
From: Tim Lee
Date: Fri, 2 Aug 2024 12:17:47 -0600
Subject: [PATCH 018/612] Bugfix: delete team 0 app (#20987)
#20986
- [X] Added/updated tests
- [X] Manual QA for all new/changed functionality
---
ee/server/service/software_installers.go | 13 +++-
server/service/integration_enterprise_test.go | 74 ++++++++++++++++++-
2 files changed, 82 insertions(+), 5 deletions(-)
diff --git a/ee/server/service/software_installers.go b/ee/server/service/software_installers.go
index 8754879992..9871f9fd44 100644
--- a/ee/server/service/software_installers.go
+++ b/ee/server/service/software_installers.go
@@ -20,6 +20,7 @@ import (
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/mdm/apple/vpp"
+ "github.com/fleetdm/fleet/v4/server/ptr"
"github.com/go-kit/log/level"
"github.com/google/uuid"
"golang.org/x/sync/errgroup"
@@ -121,7 +122,7 @@ func (svc *Service) deleteVPPApp(ctx context.Context, teamID *uint, meta *fleet.
}
var teamName *string
- if teamID != nil {
+ if teamID != nil && *teamID != 0 {
t, err := svc.ds.Team(ctx, *teamID)
if err != nil {
return ctxerr.Wrap(ctx, err, "getting team name for deleted VPP app")
@@ -161,11 +162,19 @@ func (svc *Service) deleteSoftwareInstaller(ctx context.Context, meta *fleet.Sof
teamName = &t.Name
}
+ var teamID *uint
+ switch {
+ case meta.TeamID == nil:
+ teamID = ptr.Uint(0)
+ case meta.TeamID != nil:
+ teamID = meta.TeamID
+ }
+
if err := svc.NewActivity(ctx, vc.User, fleet.ActivityTypeDeletedSoftware{
SoftwareTitle: meta.SoftwareTitle,
SoftwarePackage: meta.Name,
TeamName: teamName,
- TeamID: meta.TeamID,
+ TeamID: teamID,
SelfService: meta.SelfService,
}); err != nil {
return ctxerr.Wrap(ctx, err, "creating activity for deleted software")
diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go
index 32420f39d6..f87d9b144a 100644
--- a/server/service/integration_enterprise_test.go
+++ b/server/service/integration_enterprise_test.go
@@ -9892,7 +9892,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerUploadDownloadAndD
meta, err := s.ds.GetSoftwareInstallerMetadataByID(context.Background(), id)
require.NoError(t, err)
- if payload.TeamID != nil {
+ if payload.TeamID != nil && *payload.TeamID > 0 {
require.Equal(t, *payload.TeamID, *meta.TeamID)
} else {
require.Nil(t, meta.TeamID)
@@ -9951,8 +9951,11 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerUploadDownloadAndD
// download the installer
s.Do("GET", fmt.Sprintf("/api/latest/fleet/software/titles/%d/package?alt=media", titleID), nil, http.StatusBadRequest)
- // delete the installer
+ // delete the installer from nil team fails
s.Do("DELETE", fmt.Sprintf("/api/latest/fleet/software/titles/%d/available_for_install", titleID), nil, http.StatusBadRequest)
+
+ // delete from team 0 succeeds
+ s.Do("DELETE", fmt.Sprintf("/api/latest/fleet/software/titles/%d/available_for_install", titleID), nil, http.StatusNoContent, "team_id", "0")
})
t.Run("create team software installer", func(t *testing.T) {
@@ -10027,6 +10030,72 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerUploadDownloadAndD
// download the installer, not found anymore
s.Do("GET", fmt.Sprintf("/api/latest/fleet/software/titles/%d/package?alt=media", titleID), nil, http.StatusNotFound, "team_id", fmt.Sprintf("%d", *payload.TeamID))
})
+
+ t.Run("create team 0 software installer", func(t *testing.T) {
+ payload := &fleet.UploadSoftwareInstallerPayload{
+ TeamID: ptr.Uint(0),
+ InstallScript: "another install script",
+ PreInstallQuery: "another pre install query",
+ PostInstallScript: "another post install script",
+ Filename: "ruby.deb",
+ // additional fields below are pre-populated so we can re-use the payload later for the test assertions
+ Title: "ruby",
+ Version: "1:2.5.1",
+ Source: "deb_packages",
+ StorageID: "df06d9ce9e2090d9cb2e8cd1f4d7754a803dc452bf93e3204e3acd3b95508628",
+ Platform: "linux",
+ SelfService: true,
+ }
+ s.uploadSoftwareInstaller(payload, http.StatusOK, "")
+
+ // check the software installer
+ installerID, titleID := checkSoftwareInstaller(t, payload)
+
+ // check activity
+ s.lastActivityOfTypeMatches(fleet.ActivityTypeAddedSoftware{}.ActivityName(), fmt.Sprintf(`{"software_title": "ruby", "software_package": "ruby.deb", "team_name": null, "team_id": 0, "self_service": true}`), 0)
+
+ // upload again fails
+ s.uploadSoftwareInstaller(payload, http.StatusConflict, "already exists")
+
+ // download the installer
+ r := s.Do("GET", fmt.Sprintf("/api/latest/fleet/software/titles/%d/package?alt=media", titleID), nil, http.StatusOK, "team_id", fmt.Sprintf("%d", 0))
+ checkDownloadResponse(t, r, payload.Filename)
+
+ // create an orbit host that is not in the team
+ hostNotInTeam := createOrbitEnrolledHost(t, "windows", "orbit-host-no-team", s.ds)
+ // downloading installer still works because we allow it explicitly
+ s.Do("POST", "/api/fleet/orbit/software_install/package?alt=media", orbitDownloadSoftwareInstallerRequest{
+ InstallerID: installerID,
+ OrbitNodeKey: *hostNotInTeam.OrbitNodeKey,
+ }, http.StatusOK)
+
+ // create an orbit host, assign to team
+ hostInTeam := createOrbitEnrolledHost(t, "windows", "orbit-host-team", s.ds)
+
+ // requesting download with alt != media fails
+ r = s.Do("POST", "/api/fleet/orbit/software_install/package?alt=FOOBAR", orbitDownloadSoftwareInstallerRequest{
+ InstallerID: installerID,
+ OrbitNodeKey: *hostInTeam.OrbitNodeKey,
+ }, http.StatusBadRequest)
+ errMsg := extractServerErrorText(r.Body)
+ require.Contains(t, errMsg, "only alt=media is supported")
+
+ // valid download
+ r = s.Do("POST", "/api/fleet/orbit/software_install/package?alt=media", orbitDownloadSoftwareInstallerRequest{
+ InstallerID: installerID,
+ OrbitNodeKey: *hostInTeam.OrbitNodeKey,
+ }, http.StatusOK)
+ checkDownloadResponse(t, r, payload.Filename)
+
+ // delete the installer
+ s.Do("DELETE", fmt.Sprintf("/api/latest/fleet/software/titles/%d/available_for_install", titleID), nil, http.StatusNoContent, "team_id", "0")
+
+ // check activity
+ s.lastActivityOfTypeMatches(fleet.ActivityTypeDeletedSoftware{}.ActivityName(), fmt.Sprintf(`{"software_title": "ruby", "software_package": "ruby.deb", "team_name": null, "team_id": 0, "self_service": true}`), 0)
+
+ // download the installer, not found anymore
+ s.Do("GET", fmt.Sprintf("/api/latest/fleet/software/titles/%d/package?alt=media", titleID), nil, http.StatusNotFound, "team_id", fmt.Sprintf("%d", 0))
+ })
}
func (s *integrationEnterpriseTestSuite) TestApplyTeamsSoftwareConfig() {
@@ -12243,7 +12312,6 @@ func (s *integrationEnterpriseTestSuite) TestCalendarEventBodyUpdate() {
require.Len(t, calEvents, 1)
assert.Contains(t, calEvents[0].Description, fleet.CalendarDefaultDescription)
assert.Contains(t, calEvents[0].Description, fleet.CalendarDefaultResolution)
-
}
func (s *integrationEnterpriseTestSuite) TestVPPAppsWithoutMDM() {
From 39a9ec1ad6725c68d1140559e9b6f43b6639abf1 Mon Sep 17 00:00:00 2001
From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com>
Date: Fri, 2 Aug 2024 14:28:43 -0400
Subject: [PATCH 019/612] Missing space before timestamp (#20811)
For the following bug: #20810
---
.../Software/SelfService/SelfServiceItem/SelfServiceItem.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/pages/hosts/details/cards/Software/SelfService/SelfServiceItem/SelfServiceItem.tsx b/frontend/pages/hosts/details/cards/Software/SelfService/SelfServiceItem/SelfServiceItem.tsx
index 9c4146da4d..76b7cd7f7f 100644
--- a/frontend/pages/hosts/details/cards/Software/SelfService/SelfServiceItem/SelfServiceItem.tsx
+++ b/frontend/pages/hosts/details/cards/Software/SelfService/SelfServiceItem/SelfServiceItem.tsx
@@ -41,7 +41,7 @@ const STATUS_CONFIG: Record = {
tooltip: ({ lastInstalledAt = "" }) => (
<>
Software failed to install
- {lastInstalledAt ? `(${dateAgo(lastInstalledAt)})` : ""}. Select{" "}
+ {lastInstalledAt ? ` (${dateAgo(lastInstalledAt)})` : ""}. Select{" "}
Retry to install again, or contact your IT department.
>
),
From e7d56e9149c180d32d5207842af2f602b3a210d5 Mon Sep 17 00:00:00 2001
From: Roberto Dip
Date: Fri, 2 Aug 2024 15:51:43 -0300
Subject: [PATCH 020/612] fix docker compose detection in fleetctl preview
(#21006)
for https://github.com/fleetdm/fleet/issues/21007
CI that uses this is passing now
https://github.com/fleetdm/fleet/actions/runs/10220076886/job/28279696099
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files)
for more information.
- [x] Manual QA for all new/changed functionality
---
changes/21006-fleetctl-preview | 1 +
cmd/fleetctl/preview.go | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
create mode 100644 changes/21006-fleetctl-preview
diff --git a/changes/21006-fleetctl-preview b/changes/21006-fleetctl-preview
new file mode 100644
index 0000000000..9fe2fd3286
--- /dev/null
+++ b/changes/21006-fleetctl-preview
@@ -0,0 +1 @@
+* Fixed a bug in `fleetctl preview` that was causing it to fail if Docker was installed without support for the deprecated `docker-compose` CLI
diff --git a/cmd/fleetctl/preview.go b/cmd/fleetctl/preview.go
index a48734871c..1aa78695f9 100644
--- a/cmd/fleetctl/preview.go
+++ b/cmd/fleetctl/preview.go
@@ -74,7 +74,7 @@ func (d dockerCompose) Command(arg ...string) *exec.Cmd {
func newDockerCompose() (dockerCompose, error) {
// first, check if `docker compose` is available
- if err := exec.Command("docker compose").Run(); err == nil {
+ if err := exec.Command("docker", "compose").Run(); err == nil {
return dockerCompose{dockerComposeV2}, nil
}
From 480e123ae53d8de5eb8f1e7c055ae9654d439429 Mon Sep 17 00:00:00 2001
From: Mike Thomas <78363703+mike-j-thomas@users.noreply.github.com>
Date: Sat, 3 Aug 2024 04:00:33 +0900
Subject: [PATCH 021/612] Create fleet-profile-image.png (#20985)
Uploading the Fleet's default profile image for use on brand fronts.
---
website/assets/images/fleet-profile-image.png | Bin 0 -> 11380 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 website/assets/images/fleet-profile-image.png
diff --git a/website/assets/images/fleet-profile-image.png b/website/assets/images/fleet-profile-image.png
new file mode 100644
index 0000000000000000000000000000000000000000..3d28053398fc86c3b0e03473bec0a13b46d8d8ef
GIT binary patch
literal 11380
zcmeHtXH-+&w&+esNajq=u>>(xlgf-ULCANJpBYsPqn@NEd=AU_p@bAyNbcqzM7(
zAWZ?0Djt~K}0+A~>m&b8vN8tc+gb5H{SK&P*#
zWd;D0=szDg1zCa|4toj!Ag!xL=GtdxXWfrTHpMeaq)(%FgOC2Y?bL~WP(6Qbvv(pa
z@wg(yzaP80dwleZbhfj$da!h`dHVUu#<*|Paj~~YuhHO(&4rHrvJq0&{*Tp~kEdU%
zP7Z$W9-VIgK0Z6z+dbaioIf62+3!Ez*!@2JdwKlZ<^b{Y!Q$!uRPAo<_swU$tI@mT
zr{9`>=9M-q6`ypz&YFxn-P`}w++H!C9n|pCeyjiZ!$aJV^T3NuxAGsZo~m
zius(`=DpRghugfq_nO1rEc)+}U1!yd<`(~Ud#I{__}k7_xCi9lwqjx7$UqPI
zV+VI)&x!f}u=6d70`$iY>TFEs>c4M?^?#@5UrpZs*!0*o+_=IGpO;jNc8qak;e%)?
zT-CDClKjSn>FN;U2dUSm@|RH;OuamA#%Wews4c2l_>?q266X0WA@S!e|G%{dw>K&h
zXUob-MiNWV#Df~6#`}W3^Y|o|qwLL-+1~pHv0vhnfGpWZ
z{nw|uUgZ@9ILvRn`N+BKq=00&%aonen5x*$49c-q%T>9m&ayBR8-Tv=_&h-UN&b%)
z0ceuvVlVI3`vY5m7`NQ<*1j?k#>qcly%(lGz;idwXiw+&<7bz(aP*n?z!tfD@?Qlu
z4P+K#f(|ORGyz@JkC3-uzsrdJ;Gpwb1SMx@|tV$QdW
zOB+t06E_mQ)fd`G$z+D!N`6rgqX7nw@VbQ(s)?L!=MjAf2^J~Xq1bsGau$5?{slQb
zN9NCJ?~l(y<73C4jknx+=3&Oi>EC(3aP?}}_o>Z^!%(q>i1`nM@PP29t)rx;`ynHH
zm#t!E}Z7ju_RS5tgY6Cokk(`J?8OiwSMI_=(&~o
z#=Nj6EeaAFZ{|*}nji}P3!pGKU5lkyzsOrZ{{}EB@QH=v*`(%q+k)`**J)+L>V;|a
zFYz{E@bhv10C*H(NPjI{VH}qF?d(A%1AoO?iyHg^+TwWWp5HKZZ(wRIpJpxleHy*W
zh)!6(U;s)>MqLA;g5ZD7E9HfPC_L{KG+e(wy^Z<<6E7q-2Symc0`{-8#RQH}2JDyq
z13x;q2u;2z{F2u_4}0y$$yLtw=QK;OJS-vg?&GRQR5xI^?=XAu7O1iVXRv%O+2KT9
zyw_Jfi7<46j?x1!w=_ZHLON+1sHc(#en=r9j}F|J=|hqKUWRd)4&UFZr6-S8vZlDY
zL>cG6spUtZRZhp>0Mg(jKi>O=$Tqny$y!teopgeDIUU&iH?I1!B#54;+%HI&&_Xe4
zJOg|^#Z_+)C3tV*gtkP|6JcU%?O>|_!sE;~uPvOARQ%5cP@=xrtWcP+d=u7jCXzF|
zq?~u?(E#P_jrGR@{@1^HJXjb_IO5?xbOA)ybNneU_m0`XK7M&&)wHNY%u)-#39|4X
zgdP#^DG|Y;KJwf?M@00~>{?#wXE$n)YM$!OveiZ&A#-6}nM{y33)J{F4b6>rEd{kA#~cv=Z2;*dwW5Pq!sf@^xpi
zm{La)kDDXqsTha67Xzfq-=EDGiYae@HrjxX&PwQgVk~)Wk53oChG1|+O=9*@q>NJZ
z?>Y&+7n($}h%#o-f6rxX5pmRC(8s~8F*DALBl03vT}0GSd%b2M=vpx-t+%bH?`eR<1-f<`ovmei4&WYxcMkE`deWm*!YhY!Ks^sTSB+qQWrg`)M6`;ZPS^26XS
z4<9Syo}AJppYKEhaK7N?c`x6WqJCnQ&ED;|@+cw-2R|UCZwFC_-Vsl%Av2?3L}R2&
zv7>2A*D4C}^wp9*x*XfbSZL^VbSe8$*mG(rL+T&+-{R}faH
zJz@AI#B&q@;a?Es@8y;D)?eWKhq|B-U_=mgn*voUOI#>Zv7z-nGo(ji$$g|o
zb;;0I6tHy_!acLG$p^$2=G0&hB!JgTgTZb?&op_Z6Z^T8-V@Q+_)6BPux4gqOo3#_RdbSY{an`-nkdObnh
zS_eH_wpBqb>TB>IgsCDZGwNO->)-cHH4SFDtc&@;0?=&~t>g>b7<)xnVr}Y*A*&|~
zZ1$66-y+f@5)Q8GvSs&?VxFvF_7UN$XVhvwv%Fx_#?sg4Ih4KrZ0V3mUEy7~wXpcJ
z#+CGVL*aYR(EV4nWCmFo>2|asC^RGvU_||mp7Em{(#Z_iIRS{c0oKC7@giV|D-{C7
zkNyjez}`&Sm*@!CPTE{NHxEsWMpEgDF)iK@gpk!_BN5@VH!u8<=1?FR9PrRh?hDm+
z&|IwTSbcdMjznpfZBipPuJ&ja&8R%-*}h8MShYLs*Je1OBH`gU9d^Nym+qyH&`T`}
zw@@lKkWmAVL2WW+Sv^x(ZUk>A)d0vSn8Vw_DOag$EJXuWY15vb9py9P28HRlOQ0FB@VQ!7Z%w+88A4W
z`y?F=y{JrVig%%}FHEUZCaz~H3r^9&Na7rI%kQx7)$hPrB$114@Q)T##r5wC``ora24CjNe|>eE2n8+5#6LM7ppS{QxkAN`6c
zNEg0(J95qGnXq0xG?ywD3SO-v{$`;PuWt^BgRo86t59AIWp-w!lEVyJqvSAmBM9!l
zbF+0Kn~4%?x(Ut1<`Al!6BJ3DiFRu-Hh^x6WA5Qo+O2#1EvWlVkuG>kD9xzZC#6S1
z?$~AObdGdtWJys>b)t(q_V6EUK}>Z#M7AV*Z3=UT*_5Sylk96(U3+lw0tB32oTFTe
z6JECR7hJ!diBZU!E$mDJWy)sTZ}`2hj&vGsKj*bxx9xgDnaGXg6hn2}GLJ^}uf3&S
zwR7{cjjd+Jc`jbTM)lvl{%~ZqZ_gK-0mdH(W9?<^mPwAGqqo|dW80OnXprpDbtluj
z`!Yz`BY9BQM;(%k)uwDvF^kI>(VY0Vnu_|}kUYn`9gZvjoSzw+gqq5s>eJA{*Nj2-
z_yoG3tV;NB$PM?ZIe{(>Cq~QxHTMNnBrQig{IS<3>ktSZ4mi}s7cjB#V31@ha18)N
z9fyk2xu_PoV{kR#pZdov+!&;Rsoy{ND&~?(0lgKf>)nje%wjn>xL2VTR2P4-KtwUp
zh6Y_9I0hNMZScN^I)CKJ9-A*JB$}SfZ30p`k}qe0;=HnA$qnayO>wB+bFA4ZvS~W~
zPRSrOWh;ygH&5#sw1Z(@o!=pT6!tq;s9T7EZIOmZGIf%wM^A;>{`w;@M>~oKboy
z(>-WBPZlNjh1mhKG>LNX=?(WS==B_R!wLe}cxw>iP)<;)eFv7rI&iP809Xgmc`Nxn
zF(ejN-1Dj(ygKXVkpe~H&xA(rBtowz8J3tJX-e2Lugl-RvaTZ+Hnyln
zjb4}Sib~2HL+wl7w2NAU%=&n*a^%10?dq$iyzM4k`JoIWt_DXh%IdjY!ySyFO0R(C
z?4vf~lygp+DFe@6?y!$)Og%UM4l_+QrCs06hT==`Ns~Zpuq=mwz>1QZpB{@jpi$Z|=`Y0I3G`BFx9U!t7a7e17Su4K*I|v*
z%+4;pgjH1xkK!!!)s(|tRl@{`bnWB#m-TzFMiblTs=xX{5*wDI%x!^TwO0^Jhd<;n
znY@JXb0!GV;SX6%<5KJx#4!wza4BELj4p0#u6P9DpB|nZe46I2lB14xHnWG3qh5#=J4RiEZfT~6;$;s(C%Vmk!v&M-
z89wK2drrOe{fEcZVdA@&FoH*DgCj^PkwRpdIhr9KM=!>G4P{cl@4wo2$u>vt49qn`UsmSH`nQXZNImfs;62%uU&jCATok7!>Qg
zcaCy|8}+P+%x{HF0b88j>&?z?-Z2Ehzw0D6lKierxr1YGbHt((%ViIGJMo;m
zSH|VaZgTtcN>`muQ9881N;*QfXu|TQVO+u2Ul%Kc+7|a%2Qzbp+OD|>#OOTK4v~?Wo@j$f0xtK$
zUf3*uY>A&%Me>NzuC
z)W27{OD=F-VcObVpl4pXZk0`ePyf
zo43QhtQa1p7lHwe-7W)xU}={<1Y^+8Y75nIrEn?$Zal1TsJo6BN9bQH{jJAF%Lrif
zIRunt77~N7Bd_^@+z%7%iojTj_QsxdF=b9Mxk5c7RKR5vh{PD)kR4%nY4
z!<03@Z%SPVb%_BsEMBCv)M?_WZf{E|*y0g@n)|j?@&?oJq7#=O1m_<{u6~4MGcbWs
z_dL{ee#-H)kWs-0Fc7locaDAu2>bpmY=I=YGi
z!aKiP$+E6vfpl?CFOB|H#>eime4pW<@>~9gAc2KugHQq-gh<)d59@$0mn5%T)`8>n
zoOSvM&aIkt7MIKG0Nl^vc)1hGv@4gy|sx?E)6;AstJ+mv0vfe2;f#B9R7m{9D
z&Ri+D{#kE(iUp_L?RCyOmOOO)?YJodFx*V}sC?byB0LwUx4t{C
zp$E4Fr}1km`HN1mC-vl0fi!zg&dx)0AA0aB2Z2_nLWBEFX#L$>-S!J856prdcu#;Q
zkvK?$=A#Eh2liFh8U8goClF|qZ#QpL^F(q~G(W7WMm`DJcQ4e|DL-YqtjvmbzL8*V
zSjM}|Dp(Zu21PLME#n<$6)gTkJ14GX?+@ymEN8FgQ!vi0=stkP7xRk3CG+A=Q;hd8
z{CnzaQ2-x!Z=hjZpW&POhcx;&cqwl+B@djcl($q70q&c<_K0;~p0tGQooOVDrz;VT
zhlTHE%6s7U3rZdBW?TJQb_G0GR)uTB*xSM4>lHe|VwiR7s$=km35^nJ#8e=3kHU;&
z8I9llE2j5$R7uUHmvZn2m?1iRo+cIJ6rlK>4Yz+OoI4^Za--s$0`*+(oK<0t_nyujoh1j
z`t0=9RKNv*ckRxvt^w&{QmH^Qd03wBU`m<4%Uh5#QP=W!8`$_#F6yf407=BwI4&aXj-X*C-%F0HPp-?0c&gn~as?Oh4Zb&4LNaxEK
z%=YMc7GtXsUULZuhoju=)0xmV>0q^f+h1R8>uO_1MY6ZR12M0DoPM4@EtWxQc>-$J
zjNZ3bR+h9=wrOZkxAe2=ITgzB$H_&K&fp2co1z^?o}^i$CXx{SOZDdW(uqvLO`jTW
zCebI6#AR0{MXeWI&*f2E)FHMz`3U$p`8aTN7CCmBj_BXZlv3JopLsVpChqhhv{}R6
znvy`nT{6d$PM^+$+%Sd}*Txn^6~sbmi4ITq%YDjs%5e-n<)gpb>6NgY&0q(u{UXnO
z)6Y)#CY^vJw^tnUDelJJ^Trft`s;x_>0DxgSb`wE**tyj^X58FWMBl`u(3U{F+JeY
z7MMZ@^-~iefY3xZFpU)jj+u6Q+?)0QYQZ8+5KrhF6C{%$=Iem8;QC2_LJwTtc@%q)
zeC}xQ8jv(E>FdB_aWcRysSWEJgdr;)ojY=K14$+)evL?cGpFJ|s{%~EW}D7!(DYBF
zzl&`>S}?imMAgfIlysmfRvDUNO4sst;9w}Cu{3SDw%&RL&}$X_Hvcr*C50`s!*-?B
z#k{=Fc7u(Zy_4V9k&*S~PjLkZs8K*H*nr6Wx-go+3C%xvd==!W55Fta_fc}tHnRck
zj|Q2dSrD_jME-7P|MYl4<{QF9SzcB`6z41Gva7;|8(
znvT52S<5tcaSbEX6v(>twN_G^l8q70*6~KNmU*HwPM-SNElkyswOgCY6Ce$xdIKX{5{UfF^{k@idG|8yQGvOXk~{yRRu=Rj4m+
z0o5qifC%}Z`xrmj8~c~yC?N`8Luo3@y6f*_WKaa_&=07F=dZVsTIA}@h(C?=eMm70
z*#<{$q`b1mzj$7-id--RaeZ;+;*cJFT*FozgVlf~{~8;_FIjyhS!jyArXVeEULfrs
zsjKq*JT(P6Ywn%bwI6F0pXeSEA`%*u`iAbidwyOZGWpC3a&!-MQV;4Si(_DF!&3uh
ziRuorJMW*s#Ada^=;`1AgrMCjcO02@Zld4h=P9@QWx1*10AlvJ9Pi7zi5uw<9IYm6
zqt{~}?6b~0vaY`d)P_uHw@M?UJ5eUk2L>;Q?9P+scn=?1uZl~!0bi!5Bq@+y80}n~
zvg8eh3DgHx;Q-EsGC+8lUyorNxXrKk0R-;zp4P1Zuj;OuitSQhyY9iOiuF>8(P0>v
ztJ11zNk-2Tg@kGeTz7v{WTO_{hN~t1BgoDHvs(UT{We8otoSG62F_XNq9TdRcZ&EYuM3{#_HzQaOKmE9nKCJ%X`vTrWd5uL6LIxot2$h=@`p_s2ZtoZ
zC5>L2y8emxyTyAY+g8os?@POuJA%EEVyPqLZMYt^9FiMeVen}
zhI%Uj2sj~tTUF~Eac6$=mU2kk_Zl|Apnm%G)7(bT#utud4z5efP3r1bdUosnEW4A>ZwS5FpD-
zGsfsBjYr;1PfQgje$>;mu(D$6Y9Gb({ikn`@9vxJDIllVb-e!8q{`@xfw!I6)7u`C
zwSjlNM<|F*W8*%j&urGXv$xk8D8hxN*MFV59H-i?aq}jIkltzKnAo|^jCHePTt+z9(m=VzxUJsZ|Mx-{(ssoIVst5f-(|F
zV9In&A;i#evX$KMz~ZNBBI!{Ub!T*s6|`@1mS2iO)_o1mILK@P{+w6&XCtJ)5-*fy
z=*=`sc2g{HIk)KbtAP@j4ysRg?AuIMd1ZYLG^-w}Sa^`{BOXctWK=HHn%eagfMz3h
zy%Qu1qA-wI%&vE`8wZ$B=E?&uKU}IFKqmz~oXf*S>JKH%xiF@wreb;&K1etU5L+QJc3gDo4pyKty6brh*
zV6tBp33%K!#sDi{=qe~QDbTj{Qhts;NzkiLZ=XMnW9N1PnL8U)aP|j+Rw@d^F2K>M
zdm{|c!}e+J2^oYI2C!W|e=o9472yb5VnWajhBL#@34+1)K9{$Guavkho!R1$BByo2
z+&M;*hd&^oH;9TeM_x#dG2wSzmJd$j^-^aNg9ebBbw2$=ASkkcZsWJFR38muRp{VE
zG69OX`{LFF7&ZKZS5IUZBx*Y&{E`ELGV=j>?bicw%;e~r5)|i`MO}jdhFNSJLX|H8
zx#l(wfh;sA<8CN9gvQD6UYoB0;x^T^)z|EO5rFU)v;Lcfu^-2%ej41toXu3VB;BG`%gkl|4OHKRxLtzdrkp1X8@Ce$ppO
z?5(rQN3h3%iF#ia;J3{RbEd_w7+AG|a^`-^=fElgL`L_B9}PpgFK^TB$nVmm%ZuUY
z8XW>Zf!SNWN?@PagV1alFDHcn;*ZR<1}N}($6@&snD|TLyjv=p1BGEDG7uzp87u_>
zu@e*v{po@4wO~&w@Zf|Aa)T3)k<<+Lp#%2zvY?r{6jfx-3l8knID-s&GcCWBr(7lj#Ps!4p2*6QuSZ{gX(rh4
zAm8LMUyH0A1Dp_|_5@7Gr~#6;M`|{>fh1oK>i`JgG&OhIb1>l`I+;fe$;?Ya1|YUD_Bn|#syB}0OI8G@$chyh_SwC5<`y{)b^*M
zacJ`Xn{j2E;!z^_{8Sg(;pRi9BeWpka@-c%6bXcWbp>IK0wgvijx
zw$b$ExLm1vFvOuI&zWjVSOb<>=1g^yhTEa(u}~aY41yXWpzsZH5%v3ZS_lSEU5+z^
zOC7e&Itj$GKQD~fnbFEO$LG9AoxXFkTDier@WNekyaX_=SRv@(=$IO08#NN0P>Eb{
z0`QJAu8g`wK(Wc~55gpn10fDzK_SS?he`?k3_xM;
z;@80tlnpTty8=Tu?$a)?`QSq0i?K@p%H9sDcxeM{uF(3*?*oBmkiR(c4KJYj_)kDG
zxDeNkG2W%fF);WOcD!NsWZ1f{4je%y^Bc%)Rb%7Vc+VUCgF}35^@FV4h?hkFVj7|BWhXe
zorne2!kJUe1h23N8Bsp!Q+!by6iE_)sjSgDV+owH58p_b$KcA6?kH*RlAlQ4FuySI
zloA~tqi144`GFS*
Date: Fri, 2 Aug 2024 16:06:21 -0300
Subject: [PATCH 022/612] disable FileVault rotation if the server talks to an
old fleetd (#21004)
for https://github.com/fleetdm/confidential/issues/7522 and part of
#13157, support map is defined as:
| | fleetd < v1.30 | fleetd >= v1.30 |
| -------------- | ---------------------------------------- |
---------------------------------------- |
| Server < 4.55 | OK/FileVault rotation uses system prompt |
OK/FileVault rotation uses system prompt |
| Server >= 4.55 | FileVault rotation disabled | Escrow Buddy |
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [x] Added/updated tests
- [x] Manual QA for all new/changed functionality
- For Orbit and Fleet Desktop changes:
- [ ] Orbit runs on macOS, Linux and Windows. Check if the orbit
feature/bugfix should only apply to one platform (`runtime.GOOS`).
- [ ] Manual QA must be performed in the three main OSs, macOS, Windows
and Linux.
- [ ] Auto-update manual QA, from released version of component to new
version (see [tools/tuf/test](../tools/tuf/test/README.md)).
---
server/fleet/capabilities.go | 6 +++++
server/service/hosts_test.go | 31 +++++++++++++++++++++-
server/service/integration_mdm_test.go | 36 ++++++++++++++++++++++++++
server/service/orbit.go | 12 +++++++++
server/service/orbit_client.go | 2 +-
5 files changed, 85 insertions(+), 2 deletions(-)
diff --git a/server/fleet/capabilities.go b/server/fleet/capabilities.go
index be397bcc32..2e12810be8 100644
--- a/server/fleet/capabilities.go
+++ b/server/fleet/capabilities.go
@@ -99,5 +99,11 @@ func GetServerDeviceCapabilities() CapabilityMap {
return capabilities
}
+func GetOrbitClientCapabilities() CapabilityMap {
+ return CapabilityMap{
+ CapabilityEscrowBuddy: {},
+ }
+}
+
// CapabilitiesHeader is the header name used to communicate the capabilities.
const CapabilitiesHeader = "X-Fleet-Capabilities"
diff --git a/server/service/hosts_test.go b/server/service/hosts_test.go
index b2765516e2..1e9e663ed6 100644
--- a/server/service/hosts_test.go
+++ b/server/service/hosts_test.go
@@ -6,6 +6,7 @@ import (
"encoding/base64"
"errors"
"fmt"
+ "net/http"
"strconv"
"testing"
"time"
@@ -13,6 +14,7 @@ import (
"github.com/WatchBeam/clock"
"github.com/fleetdm/fleet/v4/server/authz"
"github.com/fleetdm/fleet/v4/server/config"
+ "github.com/fleetdm/fleet/v4/server/contexts/capabilities"
"github.com/fleetdm/fleet/v4/server/contexts/license"
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
@@ -23,6 +25,7 @@ import (
"github.com/fleetdm/fleet/v4/server/mock"
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/fleetdm/fleet/v4/server/test"
+ kitlog "github.com/go-kit/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.mozilla.org/pkcs7"
@@ -1861,7 +1864,7 @@ func TestBulkOperationFilterValidation(t *testing.T) {
func TestSetDiskEncryptionNotifications(t *testing.T) {
ds := new(mock.Store)
ctx := context.Background()
- svc := &Service{ds: ds}
+ svc := &Service{ds: ds, logger: kitlog.NewNopLogger()}
tests := []struct {
name string
@@ -1873,6 +1876,7 @@ func TestSetDiskEncryptionNotifications(t *testing.T) {
getHostDiskEncryptionKey func(context.Context, uint) (*fleet.HostDiskEncryptionKey, error)
expectedNotifications *fleet.OrbitConfigNotifications
expectedError bool
+ disableCapability bool
}{
{
name: "no MDM configured",
@@ -1943,6 +1947,24 @@ func TestSetDiskEncryptionNotifications(t *testing.T) {
},
expectedError: false,
},
+ {
+ name: "darwin needs rotation but client is old",
+ host: &fleet.Host{ID: 1, Platform: "darwin", OsqueryHostID: ptr.String("foo")},
+ appConfig: &fleet.AppConfig{
+ MDM: fleet.MDM{EnabledAndConfigured: true},
+ },
+ diskEncryptionConfigured: true,
+ isConnectedToFleetMDM: true,
+ mdmInfo: nil,
+ getHostDiskEncryptionKey: func(ctx context.Context, id uint) (*fleet.HostDiskEncryptionKey, error) {
+ return &fleet.HostDiskEncryptionKey{Decryptable: ptr.Bool(false)}, nil
+ },
+ expectedNotifications: &fleet.OrbitConfigNotifications{
+ RotateDiskEncryptionKey: true,
+ },
+ expectedError: false,
+ disableCapability: true,
+ },
{
name: "darwin needs rotation",
host: &fleet.Host{ID: 1, Platform: "darwin", OsqueryHostID: ptr.String("foo")},
@@ -2056,6 +2078,13 @@ func TestSetDiskEncryptionNotifications(t *testing.T) {
return tt.appConfig, nil
}
+ if !tt.disableCapability {
+ r := http.Request{
+ Header: http.Header{fleet.CapabilitiesHeader: []string{string(fleet.CapabilityEscrowBuddy)}},
+ }
+ ctx = capabilities.NewContext(ctx, &r)
+ }
+
notifs := &fleet.OrbitConfigNotifications{}
err := svc.setDiskEncryptionNotifications(ctx, notifs, tt.host, tt.appConfig, tt.diskEncryptionConfigured, tt.isConnectedToFleetMDM, tt.mdmInfo)
if tt.expectedError {
diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go
index b4180fb2b2..4ccd1cba50 100644
--- a/server/service/integration_mdm_test.go
+++ b/server/service/integration_mdm_test.go
@@ -1624,6 +1624,42 @@ func (s *integrationMDMTestSuite) TestDiskEncryptionSharedSetting() {
checkConfigSetSucceeds()
}
+func (s *integrationMDMTestSuite) TestEscrowBuddyBackwardsCompat() {
+ t := s.T()
+ ctx := context.Background()
+
+ // create a host
+ host, _ := createHostThenEnrollMDM(s.ds, s.server.URL, t)
+ orbitKey := setOrbitEnrollment(t, host, s.ds)
+ host.OrbitNodeKey = &orbitKey
+
+ // install a filevault profile for that host
+ acResp := appConfigResponse{}
+ s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
+ "mdm": { "enable_disk_encryption": true }
+ }`), http.StatusOK, &acResp)
+ assert.True(t, acResp.MDM.EnableDiskEncryption.Value)
+
+ // set the status as non-decryptable so a notification should be sent
+ err := s.ds.SetOrUpdateHostDiskEncryptionKey(ctx, host.ID, "", "", ptr.Bool(false))
+ require.NoError(t, err)
+
+ // notification is false because the escrow buddy capability is not set
+ orbitConfigResp := orbitGetConfigResponse{}
+ s.DoJSON("POST", "/api/fleet/orbit/config", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *host.OrbitNodeKey)), http.StatusOK, &orbitConfigResp)
+ require.False(t, orbitConfigResp.Notifications.RotateDiskEncryptionKey)
+
+ // send the request again, this time with the right header
+ orbitConfigResp = orbitGetConfigResponse{}
+ res := s.DoRawWithHeaders("POST", "/api/fleet/orbit/config", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *host.OrbitNodeKey)), http.StatusOK, map[string]string{
+ "Authorization": fmt.Sprintf("Bearer %s", s.token),
+ fleet.CapabilitiesHeader: string(fleet.CapabilityEscrowBuddy),
+ })
+ err = json.NewDecoder(res.Body).Decode(&orbitConfigResp)
+ require.NoError(t, err)
+ require.True(t, orbitConfigResp.Notifications.RotateDiskEncryptionKey)
+}
+
func (s *integrationMDMTestSuite) TestMDMAppleHostDiskEncryption() {
t := s.T()
ctx := context.Background()
diff --git a/server/service/orbit.go b/server/service/orbit.go
index 1ee8725a08..e6241a0640 100644
--- a/server/service/orbit.go
+++ b/server/service/orbit.go
@@ -10,6 +10,7 @@ import (
"net/url"
"github.com/fleetdm/fleet/v4/server"
+ "github.com/fleetdm/fleet/v4/server/contexts/capabilities"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
hostctx "github.com/fleetdm/fleet/v4/server/contexts/host"
"github.com/fleetdm/fleet/v4/server/contexts/license"
@@ -428,6 +429,17 @@ func (svc *Service) setDiskEncryptionNotifications(
switch host.FleetPlatform() {
case "darwin":
+ mp, ok := capabilities.FromContext(ctx)
+ if !ok {
+ level.Debug(svc.logger).Log("msg", "no capabilities in context, skipping disk encryption notification")
+ return nil
+ }
+
+ if !mp.Has(fleet.CapabilityEscrowBuddy) {
+ level.Debug(svc.logger).Log("msg", "host doesn't support Escrow Buddy, skipping disk encryption notification", "host_uuid", host.UUID)
+ return nil
+ }
+
notifs.RotateDiskEncryptionKey = encryptionKey != nil && encryptionKey.Decryptable != nil && !*encryptionKey.Decryptable
case "windows":
isServer := mdmInfo != nil && mdmInfo.IsServer
diff --git a/server/service/orbit_client.go b/server/service/orbit_client.go
index fb4fe0aa2f..5d4c5ce60c 100644
--- a/server/service/orbit_client.go
+++ b/server/service/orbit_client.go
@@ -146,7 +146,7 @@ func NewOrbitClient(
orbitHostInfo fleet.OrbitHostInfo,
onGetConfigErrFns *OnGetConfigErrFuncs,
) (*OrbitClient, error) {
- orbitCapabilities := fleet.CapabilityMap{}
+ orbitCapabilities := fleet.GetOrbitClientCapabilities()
bc, err := newBaseClient(addr, insecureSkipVerify, rootCA, "", fleetClientCert, orbitCapabilities)
if err != nil {
return nil, err
From 3230a9aa543d19fc2d74347230659e4ddbd0981a Mon Sep 17 00:00:00 2001
From: jacobshandling <61553566+jacobshandling@users.noreply.github.com>
Date: Fri, 2 Aug 2024 12:44:38 -0700
Subject: [PATCH 023/612] =?UTF-8?q?UI=20=E2=80=93=20Only=20modify=20nav=20?=
=?UTF-8?q?padding=20at=20low=20viewport=20width=20(#21005)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Follow-up for #20395
Wider:

Narrower:

If some of the following don't apply, delete the relevant line.
- [x] Changes file added for user-visible changes in `changes/` -
covered by previous PR
- [x] Manual QA for all new/changed functionality
Co-authored-by: Jacob Shandling
---
frontend/pages/ManageControlsPage/OSSettings/_styles.scss | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/frontend/pages/ManageControlsPage/OSSettings/_styles.scss b/frontend/pages/ManageControlsPage/OSSettings/_styles.scss
index b9c2bf11a5..37228609c6 100644
--- a/frontend/pages/ManageControlsPage/OSSettings/_styles.scss
+++ b/frontend/pages/ManageControlsPage/OSSettings/_styles.scss
@@ -12,11 +12,15 @@
&__side-nav {
.side-nav__nav-list {
top: 0;
- padding-right: 40px;
}
}
.side-nav__card-container > .custom-settings {
max-width: none;
}
+ @media (max-width: 1120px) {
+ .side-nav__nav-list {
+ padding-right: 0;
+ }
+ }
}
From 0f41fc1a8d7d14b08a5332f1cdff281f9ae0dcf3 Mon Sep 17 00:00:00 2001
From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com>
Date: Fri, 2 Aug 2024 15:49:20 -0400
Subject: [PATCH 024/612] API design: Hide "Self-service" if there's no
self-service software (#20908)
---
docs/Contributing/API-for-contributors.md | 1 +
docs/REST API/rest-api.md | 1 +
2 files changed, 2 insertions(+)
diff --git a/docs/Contributing/API-for-contributors.md b/docs/Contributing/API-for-contributors.md
index 170ff1c8ea..0f1f3a2ffb 100644
--- a/docs/Contributing/API-for-contributors.md
+++ b/docs/Contributing/API-for-contributors.md
@@ -2481,6 +2481,7 @@ Gets all information required by Fleet Desktop, this includes things like the nu
```json
{
"failing_policies_count": 3,
+ "self_service": true,
"notifications": {
"needs_mdm_migration": true,
"renew_enrollment_profile": false,
diff --git a/docs/REST API/rest-api.md b/docs/REST API/rest-api.md
index e07c86e33f..a447e15a12 100644
--- a/docs/REST API/rest-api.md
+++ b/docs/REST API/rest-api.md
@@ -3504,6 +3504,7 @@ This is the API route used by the **My device** page in Fleet desktop to display
]
}
},
+ "self_service": true,
"org_logo_url": "https://example.com/logo.jpg",
"license": {
"tier": "free",
From 13f68fc387a4b82b0efbd41fe1d3f157009b8f40 Mon Sep 17 00:00:00 2001
From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com>
Date: Fri, 2 Aug 2024 15:58:16 -0400
Subject: [PATCH 025/612] =?UTF-8?q?Update=20=F0=9F=8E=81=F0=9F=97=A3=20Fea?=
=?UTF-8?q?ture=20Fest=20(#20962)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
handbook/company/product-groups.md | 40 ++++++++----------------------
1 file changed, 11 insertions(+), 29 deletions(-)
diff --git a/handbook/company/product-groups.md b/handbook/company/product-groups.md
index 17b3acbd15..f084beaf7e 100644
--- a/handbook/company/product-groups.md
+++ b/handbook/company/product-groups.md
@@ -296,33 +296,21 @@ To make a feature request or advocate for a feature request from a customer or c
Requests are weighed from top to bottom while prioritizing attendee requests. This means that if the individual that added a feature request is not in attendance, the feature request will be discussed towards the end of the call if there's time.
-To be acceptable for consideration, a request must:
-- Have a clear proposed change
-- Have a well-articulated underlying user need
-- Specify the requestor (either internal stakeholder or customer or community user)
-
-To help the product team, other pieces of information can be optionally included:
-- How would they solve the problem without any changes if pressed?
-- How does this change fit into the requester's overall usage of Fleet?
-- What other potential changes to the product have you considered?
-
-To ensure your request appears on the ["Feature Fest" board](https://app.zenhub.com/workspaces/-feature-fest-651b2962605ba29209324c57/board):
-- Add the `~feature fest` label to your issue
-- Add the relevant customer label (if applicable)
-
-To maximize your chances of having a feature accepted, requesters can visit the [🗣 Product office hours](#rituals) meeting to get feedback on requests prior to being accepted.
-
### How feature requests are evaluated
Digestion of these new product ideas (requests) happens at the **🎁🗣 Feature Fest** meeting.
-At the **🎁🗣 Feature Fest** meeting, the DRI (Head of Product) weighs all requests on the board. When the team weighs a request, it is immediately prioritized or put to the side.
+Before the **🎁🗣 Feature Fest** meeting, the [Customer renewals DRI](https://fleetdm.com/handbook/company/communications#directly-responsible-individuals-dris) goes through the "Inbox" column and removes customer requests that are not a high priority for the business. Stakeholders will be notified by the Customer renewals DRI.
-Product Managers prioritize all potential product improvements worked on by Fleeties. Anyone (Fleeties, customers, and community members) are invited to suggest improvements.
+All community and contributor requests (non-customer) are left in the inbox. A high priority customer request may be a request that's blocking a customer from getting their job done or a request that's critical for customer renewal.
-- A _request is prioritized_ when the DRI decides it is a priority. When this happens, the team sets the request to be estimated within five business days.
+Before the meeting, the Feature prioritization DRI adds requests from Fleet's roadmap that are planned for the next design sprint. The quarterly roadmap is in the [OKRs spreadsheet](https://docs.google.com/spreadsheets/d/1Hso0LxqwrRVINCyW_n436bNHmoqhoLhC8bcbvLPOs9A/edit?gid=0#gid=0).
+
+At the **🎁🗣 Feature Fest** meeting, the Feature prioritization DRI weighs all requests in the inbox. When the team weighs a request, it is immediately prioritized or put to the side (not prioritized).
+
+- A _request is prioritized_ when the Feature prioritization DRI decides it is a priority.
- A _request is put to the side_ when the business perceives competing priorities as more pressing in the immediate moment.
-If a feature is not prioritized during a 🎁🗣 Feature Fest meeting, it only means the feature has been rejected _at that time_. Requestors will be notified by the Head of Product, and they can resubmit their request at a future meeting.
+If a feature is not prioritized during a 🎁🗣 Feature Fest meeting, it only means the feature has been rejected _at that time_. Requestors will be notified by the Feature prioritization DRI, and they can resubmit their request at a future meeting.
Requests are weighed by:
- The completeness of the request (see [making a request](#making-a-request))
@@ -331,16 +319,10 @@ Requests are weighed by:
- How well the request fits within Fleet's product vision and roadmap
- Whether the feature seems like it can be designed, estimated, and developed in 6 weeks, given its individual complexity and when combined with other work already accepted
-### Customer feature requests
-The product team's goal is to prioritize 16 customer feature requests at Feature Fest, then take them from settled to shipped. The customer success team is responsible for providing the Head of Product a live count during the Feature Fest meeting. Product Operations is responsible for monitoring this KPI and raising alarms throughout the design and engineering sprints.
-> Customer stories should be estimated at 1-3 points each to count as 1 request. If a feature request spans across multiple customers, it will be counted as the number of customers involved.
-
### After the feature is accepted
-After the 🎁🗣 Feature Fest meeting, Product Operations will clear the Feature Fest board as follows:
-**Prioritized features:** Remove `feature fest` label, add `:product` label, and assign the group Product Manager.
-**Put to the side features:** Remove `feature fest` label and close the issue.
-
-Group Product Managers will then develop user stories for the prioritized features.
+After the 🎁🗣 Feature Fest meeting, the Feature prioritization DRI will clear the Feature Fest board as follows:
+**Prioritized features:** Remove `feature fest` label, add `:product` label, and move the issue to the "Ready" column in the drafting board. The request will then be assigned to a [Product Designer](https://fleetdm.com/handbook/company/product-groups#current-product-groups) during the "Design sprint kick-off" ritual.
+**Put to the side features:** Remove `feature fest` label and notify the requestor.
> The product team's commitment to the requester is that a prioritized feature will be delivered within 6 weeks or the requester will be notified within 1 business day of the decision to de-prioritize the feature.
From 611aeb311a7d5d5250f6671822c07336290346b3 Mon Sep 17 00:00:00 2001
From: Mike Thomas <78363703+mike-j-thomas@users.noreply.github.com>
Date: Sat, 3 Aug 2024 05:33:23 +0900
Subject: [PATCH 026/612] website-vulnerabilty-management-fix (#20989)
Fixes for https://github.com/fleetdm/fleet/pull/20988
- Increased width of header to fix widowed text
- Reworded caption to fix widowed text and typo
---
website/assets/styles/pages/vulnerability-management.less | 2 +-
website/views/pages/vulnerability-management.ejs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/website/assets/styles/pages/vulnerability-management.less b/website/assets/styles/pages/vulnerability-management.less
index 17a1504178..87998dd3d2 100644
--- a/website/assets/styles/pages/vulnerability-management.less
+++ b/website/assets/styles/pages/vulnerability-management.less
@@ -46,7 +46,7 @@
[purpose='page-headline'] {
padding-bottom: 80px;
- width: 680px;
+ max-width: 780px;
h2 {
font-size: 48px;
font-style: normal;
diff --git a/website/views/pages/vulnerability-management.ejs b/website/views/pages/vulnerability-management.ejs
index adca60393d..e1f3982eb7 100644
--- a/website/views/pages/vulnerability-management.ejs
+++ b/website/views/pages/vulnerability-management.ejs
@@ -12,7 +12,7 @@
Report what matters
-
Report exactly when CVEs got fixed or mitigated, down to the hour
+
Report exactly when CVEs were fixed or mitigated, down to the hour.
Deep context from the environment
Fleet gives you data down to the chip level on every endpoint to help you make sense of which vulnerabilities to prioritize.
Untangle your security stack
From ab7df5155dfe4fbdf3e9bf5e02abb198ea52f88c Mon Sep 17 00:00:00 2001
From: Lucas Manuel Rodriguez
Date: Fri, 2 Aug 2024 18:12:36 -0300
Subject: [PATCH 027/612] Use docker compose on CI instead of docker-compose
(#21017)
After this is merged I'll cherry pick to `minor-fleet-4.55.0`.
---
.github/workflows/fleet-and-orbit.yml | 2 +-
.github/workflows/test-go.yaml | 6 +++---
Makefile | 6 +++---
server/datastore/mysql/migrations_test.go | 2 +-
server/datastore/mysql/testing_utils.go | 8 ++++----
5 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/.github/workflows/fleet-and-orbit.yml b/.github/workflows/fleet-and-orbit.yml
index 4cab7da482..571d59d067 100644
--- a/.github/workflows/fleet-and-orbit.yml
+++ b/.github/workflows/fleet-and-orbit.yml
@@ -111,7 +111,7 @@ jobs:
done
- name: Start Infra Dependencies
- run: FLEET_MYSQL_IMAGE=${{ matrix.mysql }} docker-compose up -d mysql redis &
+ run: FLEET_MYSQL_IMAGE=${{ matrix.mysql }} docker compose up -d mysql redis &
- name: Install JS Dependencies
run: make deps-js
diff --git a/.github/workflows/test-go.yaml b/.github/workflows/test-go.yaml
index 0f128b8025..5256806b5f 100644
--- a/.github/workflows/test-go.yaml
+++ b/.github/workflows/test-go.yaml
@@ -70,7 +70,7 @@ jobs:
# Pre-starting dependencies here means they are ready to go when we need them.
- name: Start Infra Dependencies
# Use & to background this
- run: FLEET_MYSQL_IMAGE=${{ matrix.mysql }} docker-compose -f docker-compose.yml -f docker-compose-redis-cluster.yml up -d mysql_test mysql_replica_test redis redis-cluster-1 redis-cluster-2 redis-cluster-3 redis-cluster-4 redis-cluster-5 redis-cluster-6 redis-cluster-setup minio saml_idp mailhog mailpit smtp4dev_test &
+ run: FLEET_MYSQL_IMAGE=${{ matrix.mysql }} docker compose -f docker-compose.yml -f docker-compose-redis-cluster.yml up -d mysql_test mysql_replica_test redis redis-cluster-1 redis-cluster-2 redis-cluster-3 redis-cluster-4 redis-cluster-5 redis-cluster-6 redis-cluster-setup minio saml_idp mailhog mailpit smtp4dev_test &
- name: Add TLS certificate for SMTP Tests
run: |
@@ -98,13 +98,13 @@ jobs:
- name: Wait for mysql
run: |
echo "waiting for mysql..."
- until docker-compose exec -T mysql_test sh -c "mysql -uroot -p\"\${MYSQL_ROOT_PASSWORD}\" -e \"SELECT 1=1\" fleet" &> /dev/null; do
+ until docker compose exec -T mysql_test sh -c "mysql -uroot -p\"\${MYSQL_ROOT_PASSWORD}\" -e \"SELECT 1=1\" fleet" &> /dev/null; do
echo "."
sleep 1
done
echo "mysql is ready"
echo "waiting for mysql replica..."
- until docker-compose exec -T mysql_replica_test sh -c "mysql -uroot -p\"\${MYSQL_ROOT_PASSWORD}\" -e \"SELECT 1=1\" fleet" &> /dev/null; do
+ until docker compose exec -T mysql_replica_test sh -c "mysql -uroot -p\"\${MYSQL_ROOT_PASSWORD}\" -e \"SELECT 1=1\" fleet" &> /dev/null; do
echo "."
sleep 1
done
diff --git a/Makefile b/Makefile
index 2e7c317baa..c7eaac214c 100644
--- a/Makefile
+++ b/Makefile
@@ -281,7 +281,7 @@ binary-arch: .pre-binary-arch .pre-binary-bundle .pre-fleet
# Drop, create, and migrate the e2e test database
e2e-reset-db:
- docker-compose exec -T mysql_test bash -c 'echo "drop database if exists e2e; create database e2e;" | MYSQL_PWD=toor mysql -uroot'
+ docker compose exec -T mysql_test bash -c 'echo "drop database if exists e2e; create database e2e;" | MYSQL_PWD=toor mysql -uroot'
./build/fleet prepare db --mysql_address=localhost:3307 --mysql_username=root --mysql_password=toor --mysql_database=e2e
e2e-setup:
@@ -312,7 +312,7 @@ e2e-serve-premium: e2e-reset-db
# Usage:
# make e2e-set-desktop-token host_id=1 token=foo
e2e-set-desktop-token:
- docker-compose exec -T mysql_test bash -c 'echo "INSERT INTO e2e.host_device_auth (host_id, token) VALUES ($(host_id), \"$(token)\") ON DUPLICATE KEY UPDATE token=VALUES(token)" | MYSQL_PWD=toor mysql -uroot'
+ docker compose exec -T mysql_test bash -c 'echo "INSERT INTO e2e.host_device_auth (host_id, token) VALUES ($(host_id), \"$(token)\") ON DUPLICATE KEY UPDATE token=VALUES(token)" | MYSQL_PWD=toor mysql -uroot'
changelog:
sh -c "find changes -type f | grep -v .keep | xargs -I {} sh -c 'grep \"\S\" {}; echo' > new-CHANGELOG.md"
@@ -347,7 +347,7 @@ fleetd-tuf:
# Reset the development DB
db-reset:
- docker-compose exec -T mysql bash -c 'echo "drop database if exists fleet; create database fleet;" | MYSQL_PWD=toor mysql -uroot'
+ docker compose exec -T mysql bash -c 'echo "drop database if exists fleet; create database fleet;" | MYSQL_PWD=toor mysql -uroot'
./build/fleet prepare db --dev
# Back up the development DB to file
diff --git a/server/datastore/mysql/migrations_test.go b/server/datastore/mysql/migrations_test.go
index a8a010dc8f..e782e0d2b2 100644
--- a/server/datastore/mysql/migrations_test.go
+++ b/server/datastore/mysql/migrations_test.go
@@ -64,7 +64,7 @@ func TestMigrations(t *testing.T) {
// Dump schema to dumpfile
cmd := exec.Command(
- "docker-compose", "exec", "-T", "mysql_test",
+ "docker", "compose", "exec", "-T", "mysql_test",
// Command run inside container
"mysqldump", "-u"+testUsername, "-p"+testPassword, "TestMigrations", "--compact", "--skip-comments",
)
diff --git a/server/datastore/mysql/testing_utils.go b/server/datastore/mysql/testing_utils.go
index 5fdc6a9082..f36ed5f961 100644
--- a/server/datastore/mysql/testing_utils.go
+++ b/server/datastore/mysql/testing_utils.go
@@ -237,7 +237,7 @@ func setupRealReplica(t testing.TB, testName string, ds *Datastore, options *dbO
func() {
// Stop slave
if out, err := exec.Command(
- "docker-compose", "exec", "-T", "mysql_replica_test",
+ "docker", "compose", "exec", "-T", "mysql_replica_test",
// Command run inside container
"mysql",
"-u"+testUsername, "-p"+testPassword,
@@ -282,7 +282,7 @@ func setupRealReplica(t testing.TB, testName string, ds *Datastore, options *dbO
// Configure slave and start replication
if out, err := exec.Command(
- "docker-compose", "exec", "-T", "mysql_replica_test",
+ "docker", "compose", "exec", "-T", "mysql_replica_test",
// Command run inside container
"mysql",
"-u"+testUsername, "-p"+testPassword,
@@ -348,7 +348,7 @@ func initializeDatabase(t testing.TB, testName string, opts *DatastoreTestOption
)
cmd := exec.Command(
- "docker-compose", "exec", "-T", "mysql_test",
+ "docker", "compose", "exec", "-T", "mysql_test",
// Command run inside container
"mysql",
"-u"+testUsername, "-p"+testPassword,
@@ -369,7 +369,7 @@ func initializeDatabase(t testing.TB, testName string, opts *DatastoreTestOption
)
cmd := exec.Command(
- "docker-compose", "exec", "-T", "mysql_replica_test",
+ "docker", "compose", "exec", "-T", "mysql_replica_test",
// Command run inside container
"mysql",
"-u"+testUsername, "-p"+testPassword,
From c83458e26f1bd05c50ca87d78e14093f98aa8b0b Mon Sep 17 00:00:00 2001
From: RachelElysia <71795832+RachelElysia@users.noreply.github.com>
Date: Fri, 2 Aug 2024 17:15:20 -0400
Subject: [PATCH 028/612] Fleet UI: No team header text and button fix (#21018)
---
frontend/hooks/useTeamIdParam.ts | 2 ++
frontend/pages/SoftwarePage/SoftwarePage.tsx | 6 +++---
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/frontend/hooks/useTeamIdParam.ts b/frontend/hooks/useTeamIdParam.ts
index f1683aa204..530ec3bb9f 100644
--- a/frontend/hooks/useTeamIdParam.ts
+++ b/frontend/hooks/useTeamIdParam.ts
@@ -395,6 +395,8 @@ export const useTeamIdParam = ({
currentTeamName: currentTeam?.name,
currentTeamSummary: currentTeam,
isAnyTeamSelected: isAnyTeamSelected(currentTeam?.id),
+ isAllTeamsSelected:
+ !isAnyTeamSelected(currentTeam?.id) && currentTeam?.id !== 0,
isRouteOk,
isTeamAdmin:
!!currentTeam?.id && permissions.isTeamAdmin(currentUser, currentTeam.id),
diff --git a/frontend/pages/SoftwarePage/SoftwarePage.tsx b/frontend/pages/SoftwarePage/SoftwarePage.tsx
index 5887d0dd11..1cf063cc2d 100644
--- a/frontend/pages/SoftwarePage/SoftwarePage.tsx
+++ b/frontend/pages/SoftwarePage/SoftwarePage.tsx
@@ -157,7 +157,7 @@ const SoftwarePage = ({ children, router, location }: ISoftwarePageProps) => {
const {
currentTeamId,
- isAnyTeamSelected,
+ isAllTeamsSelected,
isRouteOk,
teamIdForApi,
userTeams,
@@ -318,7 +318,7 @@ const SoftwarePage = ({ children, router, location }: ISoftwarePageProps) => {
const renderPageActions = () => {
const canManageAutomations =
- isGlobalAdmin && (!isPremiumTier || !isAnyTeamSelected);
+ isGlobalAdmin && (!isPremiumTier || isAllTeamsSelected);
const canAddSoftware =
isGlobalAdmin || isGlobalMaintainer || isTeamAdmin || isTeamMaintainer;
@@ -349,7 +349,7 @@ const SoftwarePage = ({ children, router, location }: ISoftwarePageProps) => {
return (
Manage software and search for installed software, OS and
- vulnerabilities {isAnyTeamSelected ? "on this team" : "for all hosts"}.
+ vulnerabilities {isAllTeamsSelected ? "for all hosts" : "on this team"}.
);
};
From 1d3c6f3a960c2cf9fc760680490eed01fbec5a58 Mon Sep 17 00:00:00 2001
From: Eric
Date: Fri, 2 Aug 2024 16:51:49 -0500
Subject: [PATCH 029/612] Fix typo in pricing-features-table.yml key (#20825)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Changes:
Fixed a typo in the pricing features table yaml (description »
description)
---
handbook/company/pricing-features-table.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/handbook/company/pricing-features-table.yml b/handbook/company/pricing-features-table.yml
index f29ddac991..4514c3c95e 100644
--- a/handbook/company/pricing-features-table.yml
+++ b/handbook/company/pricing-features-table.yml
@@ -1102,7 +1102,7 @@
# ╚╗╔╝╠═╣╠╦╝║╠═╣╠╩╗║ ║╣ ╠═╣║ ╦║╣ ║║║ ║ ╚╗╔╝║╣ ╠╦╝╚═╗║║ ║║║║╚═╗
# ╚╝ ╩ ╩╩╚═╩╩ ╩╚═╝╩═╝╚═╝ ╩ ╩╚═╝╚═╝╝╚╝ ╩ ╚╝ ╚═╝╩╚═╚═╝╩╚═╝╝╚╝╚═╝
- industryName: Variable agent versions
- descrption: Manage agents remotely by setting different versions per-baseline.
+ description: Manage agents remotely by setting different versions per-baseline.
documentationUrl: https://fleetdm.com/docs/configuration/agent-configuration#configure-fleetd-update-channels
tier: Premium
jamfProHasFeature: no
From e790e56a06f690d270f4b3f686401393040e247a Mon Sep 17 00:00:00 2001
From: Eric
Date: Fri, 2 Aug 2024 17:05:12 -0500
Subject: [PATCH 030/612] Website: Validate keys in pricing table configuration
(#20826)
Closes: #20776
Changes:
- updated the build-static-content script to throw an error if a feature
in the pricing-features-table.yml contains an unrecognized key.
Note: The "Test Fleet website" GH action will fail until
https://github.com/fleetdm/fleet/pull/20825 is merged and this PR is
updated.
---
website/scripts/build-static-content.js | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/website/scripts/build-static-content.js b/website/scripts/build-static-content.js
index 03050e00f0..042a43cfe1 100644
--- a/website/scripts/build-static-content.js
+++ b/website/scripts/build-static-content.js
@@ -804,7 +804,14 @@ module.exports = {
let pricingTableFeatures = YAML.parse(yaml, {prettyErrors: true});
let VALID_PRODUCT_CATEGORIES = ['Endpoint operations', 'Device management', 'Vulnerability management'];
let VALID_PRICING_TABLE_CATEGORIES = ['Support', 'Deployment', 'Integrations', 'Endpoint operations', 'Device management', 'Vulnerability management'];
+ let VALID_PRICING_TABLE_KEYS = ['industryName', 'description', 'documentationUrl', 'tier', 'jamfProHasFeature', 'jamfProtectHasFeature', 'usualDepartment', 'productCategories', 'pricingTableCategories', 'waysToUse', 'buzzwords', 'demos', 'dri', 'friendlyName', 'moreInfoUrl', 'comingSoonOn', 'screenshotSrc', 'isExperimental'];
for(let feature of pricingTableFeatures){
+ // Throw an error if a feature contains an unrecognized key.
+ for(let key of _.keys(feature)){
+ if(!VALID_PRICING_TABLE_KEYS.includes(key)){
+ throw new Error(`Unrecognized key. Could not build pricing table config from pricing-features-table.yml. The "${feature.industryName}" feature contains an unrecognized key (${key}). To resolve, fix any typos or remove this key and try running this script again.`);
+ }
+ }
if(feature.name) {// Compatibility check
throw new Error(`Could not build pricing table config from pricing-features-table.yml. A feature has a "name" (${feature.name}) which is no longer supported. To resolve, add a "industryName" to this feature: ${feature}`);
}
From c0cc609127d5b9d8934f6a6728832993cea184cf Mon Sep 17 00:00:00 2001
From: Eric
Date: Fri, 2 Aug 2024 17:10:56 -0500
Subject: [PATCH 031/612] Website: Use GitHub API to get `lastmodifiedAt`
timestamps (#20859)
Closes: #20823
Changes:
- Updated the build-static-content script to use the GitHub API to get
timestamps of when files were last changed.
- Updated the get-extended-osquery schema helper to use a GitHub token
to authenticate GitHub requests (if it is provided)
---
.../helpers/get-extended-osquery-schema.js | 20 ++++--
website/scripts/build-static-content.js | 67 +++++++++++++++----
2 files changed, 68 insertions(+), 19 deletions(-)
diff --git a/website/api/helpers/get-extended-osquery-schema.js b/website/api/helpers/get-extended-osquery-schema.js
index d19b090b7b..1a697a5847 100644
--- a/website/api/helpers/get-extended-osquery-schema.js
+++ b/website/api/helpers/get-extended-osquery-schema.js
@@ -11,6 +11,10 @@ module.exports = {
type: 'boolean',
defaultsTo: false,
description: 'Whether or not to include a lastModifiedAt value for each table.',
+ },
+ githubAccessToken: {
+ type: 'string',
+ description: 'A github token used to authenticate requests to the GitHub API'
}
},
@@ -25,11 +29,10 @@ module.exports = {
},
- fn: async function ({includeLastModifiedAtValue}) {
+ fn: async function ({includeLastModifiedAtValue, githubAccessToken}) {
let path = require('path');
let YAML = require('yaml');
let util = require('util');
-
let topLvlRepoPath = path.resolve(sails.config.appPath, '../');
require('assert')(sails.config.custom.versionOfOsquerySchemaToUseWhenGeneratingDocumentation, 'Please set sails.config.custom.sails.config.custom.versionOfOsquerySchemaToUseWhenGeneratingDocumentation to the version of osquery to use, for example \'5.8.1\'.');
let VERSION_OF_OSQUERY_SCHEMA_TO_USE = sails.config.custom.versionOfOsquerySchemaToUseWhenGeneratingDocumentation;
@@ -40,6 +43,14 @@ module.exports = {
let rawOsqueryTablesLastModifiedAt;
if(includeLastModifiedAtValue) {
// If we're including a lastModifiedAt value for schema tables, we'll send a request to the GitHub API to get a timestamp of when the last commit
+ let baseHeadersForGithubRequests = {
+ 'User-Agent': 'fleet-schema-builder',
+ 'Accept': 'application/vnd.github.v3+json',
+ };
+ // If a GitHub access token was provided, add it to the headers.
+ if(githubAccessToken){
+ baseHeadersForGithubRequests['Authorization'] = `token ${githubAccessToken}`;
+ }
let responseData = await sails.helpers.http.get.with({// [?]: https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#list-commits
url: 'https://api.github.com/repos/osquery/osquery-site/commits',
data: {
@@ -47,10 +58,7 @@ module.exports = {
page: 1,
per_page: 1,//eslint-disable-line camelcase
},
- headers: {
- 'User-Agent': 'fleet-schema-builder',
- 'Accept': 'application/vnd.github.v3+json',
- },
+ headers: baseHeadersForGithubRequests
}).intercept((err)=>{
return new Error(`When trying to send a request to GitHub get a timestamp of the last commit to the osqeury schema JSON, an error occurred. Full error: ${util.inspect(err)}`);
});
diff --git a/website/scripts/build-static-content.js b/website/scripts/build-static-content.js
index 042a43cfe1..a34e2b5640 100644
--- a/website/scripts/build-static-content.js
+++ b/website/scripts/build-static-content.js
@@ -16,7 +16,7 @@ module.exports = {
fn: async function ({ dry, githubAccessToken }) {
let path = require('path');
let YAML = require('yaml');
-
+ let util = require('util');
// FUTURE: If we ever need to gather source files from other places or branches, etc, see git history of this file circa 2021-05-19 for an example of a different strategy we might use to do that.
let topLvlRepoPath = path.resolve(sails.config.appPath, '../');
@@ -390,11 +390,29 @@ module.exports = {
}//fi
// Get last modified timestamp using git, and represent it as a JS timestamp.
- // > Inspired by https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/compile-markdown-tree-from-remote-git-repo.js#L265-L273
- let lastModifiedAt = (new Date((await sails.helpers.process.executeCommand.with({
- command: `git log -1 --format="%ai" '${path.relative(topLvlRepoPath, pageSourcePath)}'`,
- dir: topLvlRepoPath,
- })).stdout)).getTime();
+ let lastModifiedAt;
+ if(!githubAccessToken) {
+ lastModifiedAt = Date.now();
+ } else {
+ let responseData = await sails.helpers.http.get.with({// [?]: https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#list-commits
+ url: 'https://api.github.com/repos/fleetdm/fleet/commits',
+ data: {
+ path: path.join(sectionRepoPath, pageRelSourcePath),
+ page: 1,
+ per_page: 1,//eslint-disable-line camelcase
+ },
+ headers: baseHeadersForGithubRequests,
+ }).intercept((err)=>{
+ return new Error(`When getting the commit history for ${path.join(sectionRepoPath, pageRelSourcePath)} to get a lastModifiedAt timestamp, an error occured.`, err);
+ });
+ // The value we'll use for the lastModifiedAt timestamp will be date value of the `commiter` property of the `commit` we got in the API response from github.
+ let mostRecentCommitToOsquerySchema = responseData[0];
+ if(!mostRecentCommitToOsquerySchema.commit || !mostRecentCommitToOsquerySchema.commit.committer) {
+ // Throw an error if the the response from GitHub is missing a commit or commiter.
+ throw new Error(`When getting the commit history for ${path.join(sectionRepoPath, pageRelSourcePath)} to get a lastModifiedAt timestamp, the response from the GitHub API did not include information about the most recent commit. Response from GitHub: ${util.inspect(responseData, {depth:null})}`);
+ }
+ lastModifiedAt = (new Date(mostRecentCommitToOsquerySchema.commit.committer.date)).getTime(); // Convert the UTC timestamp from GitHub to a JS timestamp.
+ }
// Determine display title (human-readable title) to use for this page.
let pageTitle;
@@ -560,11 +578,30 @@ module.exports = {
let RELATIVE_PATH_TO_OPEN_POSITIONS_YML_IN_FLEET_REPO = 'handbook/company/open-positions.yml';
// Get last modified timestamp using git, and represent it as a JS timestamp.
- // > Inspired by https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/compile-markdown-tree-from-remote-git-repo.js#L265-L273
- let lastModifiedAt = (new Date((await sails.helpers.process.executeCommand.with({
- command: `git log -1 --format="%ai" '${path.join(topLvlRepoPath, RELATIVE_PATH_TO_OPEN_POSITIONS_YML_IN_FLEET_REPO)}'`,
- dir: topLvlRepoPath,
- })).stdout)).getTime();
+ let lastModifiedAt;
+ if(!githubAccessToken) {
+ lastModifiedAt = Date.now();
+ } else {
+ // If we're including a lastModifiedAt value for schema tables, we'll send a request to the GitHub API to get a timestamp of when the last commit
+ let responseData = await sails.helpers.http.get.with({// [?]: https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#list-commits
+ url: 'https://api.github.com/repos/fleetdm/fleet/commits',
+ data: {
+ path: RELATIVE_PATH_TO_OPEN_POSITIONS_YML_IN_FLEET_REPO,
+ page: 1,
+ per_page: 1,//eslint-disable-line camelcase
+ },
+ headers: baseHeadersForGithubRequests,
+ }).intercept((err)=>{
+ return new Error(`When getting the commit history for the open positions YAML to get a lastModifiedAt timestamp, an error occured.`, err);
+ });
+ // The value we'll use for the lastModifiedAt timestamp will be date value of the `commiter` property of the `commit` we got in the API response from github.
+ let mostRecentCommitToOsquerySchema = responseData[0];
+ if(!mostRecentCommitToOsquerySchema.commit || !mostRecentCommitToOsquerySchema.commit.committer) {
+ // Throw an error if the the response from GitHub is missing a commit or commiter.
+ throw new Error(`When trying to get a lastModifiedAt timestamp for the open positions YAML, the response from the GitHub API did not include information about the most recent commit. Response from GitHub: ${util.inspect(responseData, {depth:null})}`);
+ }
+ lastModifiedAt = (new Date(mostRecentCommitToOsquerySchema.commit.committer.date)).getTime(); // Convert the UTC timestamp from GitHub to a JS timestamp.
+ }
let openPositionsYaml = await sails.helpers.fs.read(path.join(topLvlRepoPath, RELATIVE_PATH_TO_OPEN_POSITIONS_YML_IN_FLEET_REPO)).intercept('doesNotExist', (err)=>new Error(`Could not find open positions YAML file at "${RELATIVE_PATH_TO_OPEN_POSITIONS_YML_IN_FLEET_REPO}". Was it accidentally moved? Raw error: `+err.message));
let openPositionsToCreatePartialsFor = YAML.parse(openPositionsYaml, {prettyErrors: true});
@@ -673,7 +710,12 @@ module.exports = {
}
// After we build the Markdown pages, we'll merge the osquery schema with the Fleet schema overrides, then create EJS partials for each table in the merged schema.
- let expandedTables = await sails.helpers.getExtendedOsquerySchema.with({includeLastModifiedAtValue: true});
+ let expandedTables;
+ if(githubAccessToken){
+ expandedTables = await sails.helpers.getExtendedOsquerySchema.with({includeLastModifiedAtValue: true, githubAccessToken,});
+ } else {
+ expandedTables = await sails.helpers.getExtendedOsquerySchema();
+ }
// Once we have our merged schema, we'll create ejs partials for each table.
for(let table of expandedTables) {
@@ -1098,7 +1140,6 @@ module.exports = {
}
});
}
-
}
From 5f7a644e3cb2b6f737d77b177daceafdb2f6ac6a Mon Sep 17 00:00:00 2001
From: Jahziel Villasana-Espinoza
Date: Fri, 2 Aug 2024 18:50:03 -0400
Subject: [PATCH 032/612] fix software array migration (#21010)
> Related issue: #20978
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files)
for more information.
- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
- [x] Added/updated tests
- [x] If database migrations are included, checked table schema to
confirm autoupdate
- For database migrations:
- [x] Checked schema for all modified table for columns that will
auto-update timestamps during migration.
- [x] Manual QA for all new/changed functionality
---
changes/fix-software-array-migration | 2 +
...240802113716_UpdateSoftwareGitopsConfig.go | 59 ++++++
...2113716_UpdateSoftwareGitopsConfig_test.go | 194 ++++++++++++++++++
server/datastore/mysql/schema.sql | 4 +-
4 files changed, 257 insertions(+), 2 deletions(-)
create mode 100644 changes/fix-software-array-migration
create mode 100644 server/datastore/mysql/migrations/tables/20240802113716_UpdateSoftwareGitopsConfig.go
create mode 100644 server/datastore/mysql/migrations/tables/20240802113716_UpdateSoftwareGitopsConfig_test.go
diff --git a/changes/fix-software-array-migration b/changes/fix-software-array-migration
new file mode 100644
index 0000000000..27536ccc1e
--- /dev/null
+++ b/changes/fix-software-array-migration
@@ -0,0 +1,2 @@
+- Adds a migration to migrate older team configurations to the new version that includes both
+ installers and App Store apps.
\ No newline at end of file
diff --git a/server/datastore/mysql/migrations/tables/20240802113716_UpdateSoftwareGitopsConfig.go b/server/datastore/mysql/migrations/tables/20240802113716_UpdateSoftwareGitopsConfig.go
new file mode 100644
index 0000000000..96c9912287
--- /dev/null
+++ b/server/datastore/mysql/migrations/tables/20240802113716_UpdateSoftwareGitopsConfig.go
@@ -0,0 +1,59 @@
+package tables
+
+import (
+ "database/sql"
+ "encoding/json"
+ "fmt"
+ "reflect"
+
+ "github.com/jmoiron/sqlx"
+ "github.com/jmoiron/sqlx/reflectx"
+)
+
+func init() {
+ MigrationClient.AddMigration(Up_20240802113716, Down_20240802113716)
+}
+
+func Up_20240802113716(tx *sql.Tx) error {
+ txx := sqlx.Tx{Tx: tx, Mapper: reflectx.NewMapperFunc("db", sqlx.NameMapper)}
+
+ type row struct {
+ Config json.RawMessage `db:"config"`
+ ID uint `db:"id"`
+ }
+
+ var rows []row
+ if err := txx.Select(&rows, "SELECT config, id FROM teams"); err != nil {
+ return fmt.Errorf("selecting team configs: %w", err)
+ }
+
+ for _, r := range rows {
+
+ config := make(map[string]any)
+ if err := json.Unmarshal(r.Config, &config); err != nil {
+ return fmt.Errorf("unmarshal team config: %w", err)
+ }
+ softwareData := config["software"]
+ rt := reflect.TypeOf(config["software"])
+ if rt.Kind() == reflect.Slice {
+ // then we have an older config without the new fields
+ // Note: we are setting the new key to be whatever the old key was (if it was null, then
+ // it's set to null, if it was empty array, then it's set to empty array)
+ config["software"] = map[string]any{"packages": softwareData}
+ b, err := json.Marshal(config)
+ if err != nil {
+ return fmt.Errorf("marshal updated team config: %w", err)
+ }
+ if _, err := tx.Exec(`UPDATE teams SET config = ? WHERE id = ?`, b, r.ID); err != nil {
+ return fmt.Errorf("updating config for team %d: %w", r.ID, err)
+ }
+ }
+
+ }
+
+ return nil
+}
+
+func Down_20240802113716(tx *sql.Tx) error {
+ return nil
+}
diff --git a/server/datastore/mysql/migrations/tables/20240802113716_UpdateSoftwareGitopsConfig_test.go b/server/datastore/mysql/migrations/tables/20240802113716_UpdateSoftwareGitopsConfig_test.go
new file mode 100644
index 0000000000..020c071075
--- /dev/null
+++ b/server/datastore/mysql/migrations/tables/20240802113716_UpdateSoftwareGitopsConfig_test.go
@@ -0,0 +1,194 @@
+package tables
+
+import (
+ "testing"
+
+ "github.com/fleetdm/fleet/v4/server/fleet"
+ "github.com/stretchr/testify/require"
+)
+
+func TestUp_20240802113716(t *testing.T) {
+ db := applyUpToPrev(t)
+
+ badCfg := `
+{
+ "mdm": {
+ "ios_updates": {
+ "deadline": "",
+ "minimum_version": ""
+ },
+ "macos_setup": {
+ "bootstrap_package": "",
+ "macos_setup_assistant": "",
+ "enable_end_user_authentication": false,
+ "enable_release_device_manually": false
+ },
+ "macos_updates": {
+ "deadline": "",
+ "minimum_version": ""
+ },
+ "ipados_updates": {
+ "deadline": "",
+ "minimum_version": ""
+ },
+ "macos_settings": {
+ "custom_settings": []
+ },
+ "windows_updates": {
+ "deadline_days": null,
+ "grace_period_days": null
+ },
+ "windows_settings": {
+ "custom_settings": []
+ },
+ "enable_disk_encryption": false
+ },
+ "scripts": [],
+ "features": {
+ "enable_host_users": true,
+ "enable_software_inventory": true
+ },
+ "software": [
+ {
+ "url": "http://localhost:8100/1Password.pkg",
+ "self_service": true,
+ "install_script": {
+ "path": ""
+ },
+ "pre_install_query": {
+ "path": ""
+ },
+ "post_install_script": {
+ "path": ""
+ }
+ }
+ ],
+ "integrations": {
+ "jira": null,
+ "zendesk": null,
+ "google_calendar": {
+ "webhook_url": "",
+ "enable_calendar_events": false
+ }
+ },
+ "webhook_settings": {
+ "host_status_webhook": {
+ "days_count": 0,
+ "destination_url": "",
+ "host_percentage": 0,
+ "enable_host_status_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": 30,
+ "host_expiry_enabled": true
+ }
+}
+
+`
+
+ badCfgEmptyArr := `
+{
+ "mdm": {
+ "ios_updates": {
+ "deadline": "",
+ "minimum_version": ""
+ },
+ "macos_setup": {
+ "bootstrap_package": "",
+ "macos_setup_assistant": "",
+ "enable_end_user_authentication": false,
+ "enable_release_device_manually": false
+ },
+ "macos_updates": {
+ "deadline": "",
+ "minimum_version": ""
+ },
+ "ipados_updates": {
+ "deadline": "",
+ "minimum_version": ""
+ },
+ "macos_settings": {
+ "custom_settings": []
+ },
+ "windows_updates": {
+ "deadline_days": null,
+ "grace_period_days": null
+ },
+ "windows_settings": {
+ "custom_settings": []
+ },
+ "enable_disk_encryption": false
+ },
+ "scripts": [],
+ "features": {
+ "enable_host_users": true,
+ "enable_software_inventory": true
+ },
+ "software": [],
+ "integrations": {
+ "jira": null,
+ "zendesk": null,
+ "google_calendar": {
+ "webhook_url": "",
+ "enable_calendar_events": false
+ }
+ },
+ "webhook_settings": {
+ "host_status_webhook": {
+ "days_count": 0,
+ "destination_url": "",
+ "host_percentage": 0,
+ "enable_host_status_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": 30,
+ "host_expiry_enabled": true
+ }
+}
+
+`
+ tid1 := execNoErrLastID(t, db, `INSERT INTO teams (name, config) VALUES (?,?)`, "team 1", badCfg)
+ tid2 := execNoErrLastID(t, db, `INSERT INTO teams (name, config) VALUES (?,?)`, "team 2", badCfgEmptyArr)
+
+ // Apply current migration.
+ applyNext(t, db)
+
+ var team fleet.Team
+ require.NoError(t, db.Get(&team, "SELECT id, config FROM teams WHERE id = ?", tid1))
+
+ // Team with a package should see it in the new field
+ require.NotNil(t, team.Config.Software)
+ require.True(t, team.Config.Software.Packages.Set)
+ require.True(t, team.Config.Software.Packages.Valid)
+ require.Len(t, team.Config.Software.Packages.Value, 1)
+
+ require.False(t, team.Config.Software.AppStoreApps.Set)
+ require.False(t, team.Config.Software.AppStoreApps.Valid)
+ require.Len(t, team.Config.Software.AppStoreApps.Value, 0)
+
+ require.NoError(t, db.Get(&team, "SELECT id, config FROM teams WHERE id = ?", tid2))
+
+ // Team with an empty array originally should have JSON null set for packages
+ require.NotNil(t, team.Config.Software)
+ require.True(t, team.Config.Software.Packages.Set)
+ require.True(t, team.Config.Software.Packages.Valid)
+ require.Len(t, team.Config.Software.Packages.Value, 0)
+
+ require.False(t, team.Config.Software.AppStoreApps.Set)
+ require.False(t, team.Config.Software.AppStoreApps.Valid)
+ require.Len(t, team.Config.Software.AppStoreApps.Value, 0)
+}
diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql
index 22ac9295c1..9efa895008 100644
--- a/server/datastore/mysql/schema.sql
+++ b/server/datastore/mysql/schema.sql
@@ -970,9 +970,9 @@ CREATE TABLE `migration_status_tables` (
`tstamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`)
-) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=294 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=295 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');
+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');
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `mobile_device_management_solutions` (
From 1517b285ec0e917bbbc433f90e079382a03891d2 Mon Sep 17 00:00:00 2001
From: Eric
Date: Fri, 2 Aug 2024 18:35:23 -0500
Subject: [PATCH 033/612] Docs: Add fleetdm.com/guides link to
tutorials-and-guides docs page. (#21026)
Related to: https://github.com/fleetdm/confidential/issues/7343
Changes:
- Added a fleetdm.com/guides link to the tutorials and guides docs page.
---
docs/Get started/tutorials-and-guides.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/docs/Get started/tutorials-and-guides.md b/docs/Get started/tutorials-and-guides.md
index 0b1584f6a3..612f7c1b23 100644
--- a/docs/Get started/tutorials-and-guides.md
+++ b/docs/Get started/tutorials-and-guides.md
@@ -67,5 +67,7 @@ A collection of guides to help you get up and running with Fleet.
- [Generate process trees with osquery](https://fleetdm.com/guides/generate-process-trees-with-osquery)
+See all guides
+
From 20e9ebf04182e701df2412e2bfc449cb0537114c Mon Sep 17 00:00:00 2001
From: Drew Baker <89049099+Drew-P-drawers@users.noreply.github.com>
Date: Fri, 2 Aug 2024 19:51:36 -0400
Subject: [PATCH 034/612] Update README.md (#21000)
Updating readme for alignment.
---
README.md | 2 --
1 file changed, 2 deletions(-)
diff --git a/README.md b/README.md
index 7e8651a618..3c23f067c3 100644
--- a/README.md
+++ b/README.md
@@ -43,8 +43,6 @@ Fleet has no ambition to replace all of your other tools. (Though it might repl
Fleet plays well with Munki, Chef, Puppet, and Ansible, as well as with security tools like Crowdstrike and SentinelOne. For example, you can use the free version of Fleet to quickly report on what hosts are _actually_ running your EDR agent.
-While most folks prefer to use one or the other, Fleet can also coexist peacefully with Rapid7 and other agent-based vulnerability scanners. This can be useful during migrations.
-
#### Free as in free
The free version of Fleet will [always be free](https://fleetdm.com/pricing). Fleet is [independently backed](https://linkedin.com/company/fleetdm) and actively maintained with the help of many amazing [contributors](https://github.com/fleetdm/fleet/graphs/contributors).
From 18977f324bcfab7ea1a8a9bcd282183f5d8f41ab Mon Sep 17 00:00:00 2001
From: Jahziel Villasana-Espinoza
Date: Mon, 5 Aug 2024 09:23:15 -0400
Subject: [PATCH 035/612] fix: panic during migration (#21031)
> Related issue: #21030
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
- [x] Added/updated tests
- [x] Manual QA for all new/changed functionality
---------
Co-authored-by: Sarah Gillespie <73313222+gillespi314@users.noreply.github.com>
---
...240802113716_UpdateSoftwareGitopsConfig.go | 10 ++-
...2113716_UpdateSoftwareGitopsConfig_test.go | 76 ++++++++++++++++++-
2 files changed, 84 insertions(+), 2 deletions(-)
diff --git a/server/datastore/mysql/migrations/tables/20240802113716_UpdateSoftwareGitopsConfig.go b/server/datastore/mysql/migrations/tables/20240802113716_UpdateSoftwareGitopsConfig.go
index 96c9912287..c91e99099e 100644
--- a/server/datastore/mysql/migrations/tables/20240802113716_UpdateSoftwareGitopsConfig.go
+++ b/server/datastore/mysql/migrations/tables/20240802113716_UpdateSoftwareGitopsConfig.go
@@ -33,8 +33,16 @@ func Up_20240802113716(tx *sql.Tx) error {
if err := json.Unmarshal(r.Config, &config); err != nil {
return fmt.Errorf("unmarshal team config: %w", err)
}
- softwareData := config["software"]
+ softwareData, ok := config["software"]
+ if !ok {
+ continue
+ }
+
rt := reflect.TypeOf(config["software"])
+ if rt == nil {
+ continue
+ }
+
if rt.Kind() == reflect.Slice {
// then we have an older config without the new fields
// Note: we are setting the new key to be whatever the old key was (if it was null, then
diff --git a/server/datastore/mysql/migrations/tables/20240802113716_UpdateSoftwareGitopsConfig_test.go b/server/datastore/mysql/migrations/tables/20240802113716_UpdateSoftwareGitopsConfig_test.go
index 020c071075..0bd2edcab4 100644
--- a/server/datastore/mysql/migrations/tables/20240802113716_UpdateSoftwareGitopsConfig_test.go
+++ b/server/datastore/mysql/migrations/tables/20240802113716_UpdateSoftwareGitopsConfig_test.go
@@ -159,10 +159,78 @@ func TestUp_20240802113716(t *testing.T) {
"host_expiry_enabled": true
}
}
-
`
+
+ badCfgNoSoftwareField := `
+{
+ "mdm": {
+ "ios_updates": {
+ "deadline": "",
+ "minimum_version": ""
+ },
+ "macos_setup": {
+ "bootstrap_package": "",
+ "macos_setup_assistant": "",
+ "enable_end_user_authentication": false,
+ "enable_release_device_manually": false
+ },
+ "macos_updates": {
+ "deadline": "",
+ "minimum_version": ""
+ },
+ "ipados_updates": {
+ "deadline": "",
+ "minimum_version": ""
+ },
+ "macos_settings": {
+ "custom_settings": []
+ },
+ "windows_updates": {
+ "deadline_days": null,
+ "grace_period_days": null
+ },
+ "windows_settings": {
+ "custom_settings": []
+ },
+ "enable_disk_encryption": false
+ },
+ "scripts": [],
+ "features": {
+ "enable_host_users": true,
+ "enable_software_inventory": true
+ },
+ "integrations": {
+ "jira": null,
+ "zendesk": null,
+ "google_calendar": {
+ "webhook_url": "",
+ "enable_calendar_events": false
+ }
+ },
+ "webhook_settings": {
+ "host_status_webhook": {
+ "days_count": 0,
+ "destination_url": "",
+ "host_percentage": 0,
+ "enable_host_status_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": 30,
+ "host_expiry_enabled": true
+ }
+}
+`
+
tid1 := execNoErrLastID(t, db, `INSERT INTO teams (name, config) VALUES (?,?)`, "team 1", badCfg)
tid2 := execNoErrLastID(t, db, `INSERT INTO teams (name, config) VALUES (?,?)`, "team 2", badCfgEmptyArr)
+ tid3 := execNoErrLastID(t, db, `INSERT INTO teams (name, config) VALUES (?,?)`, "team 3", badCfgNoSoftwareField)
// Apply current migration.
applyNext(t, db)
@@ -180,6 +248,7 @@ func TestUp_20240802113716(t *testing.T) {
require.False(t, team.Config.Software.AppStoreApps.Valid)
require.Len(t, team.Config.Software.AppStoreApps.Value, 0)
+ team = fleet.Team{}
require.NoError(t, db.Get(&team, "SELECT id, config FROM teams WHERE id = ?", tid2))
// Team with an empty array originally should have JSON null set for packages
@@ -191,4 +260,9 @@ func TestUp_20240802113716(t *testing.T) {
require.False(t, team.Config.Software.AppStoreApps.Set)
require.False(t, team.Config.Software.AppStoreApps.Valid)
require.Len(t, team.Config.Software.AppStoreApps.Value, 0)
+
+ team = fleet.Team{}
+ require.NoError(t, db.Get(&team, "SELECT id, config FROM teams WHERE id = ?", tid3))
+
+ require.Nil(t, team.Config.Software)
}
From f836722fb4164f9d9047c6f1d99f26b93750c6d8 Mon Sep 17 00:00:00 2001
From: Martin Angers
Date: Mon, 5 Aug 2024 11:53:15 -0400
Subject: [PATCH 036/612] Initial implementation of decrypt tool (#21044)
---
tools/mdm/decrypt-disk-encryption-key/main.go | 56 +++++++++++++++++++
1 file changed, 56 insertions(+)
create mode 100644 tools/mdm/decrypt-disk-encryption-key/main.go
diff --git a/tools/mdm/decrypt-disk-encryption-key/main.go b/tools/mdm/decrypt-disk-encryption-key/main.go
new file mode 100644
index 0000000000..22bf6ef714
--- /dev/null
+++ b/tools/mdm/decrypt-disk-encryption-key/main.go
@@ -0,0 +1,56 @@
+// Command decrypt-disk-encryption-key decrypts a base64-encoded encrypted key
+// using the provided X509 certificate and private key. This is typically used
+// to manually decrypt a disk encryption key, e.g. BitLocker on Windows or
+// FileVault on macOS. The certificate and private key used are the SCEP files
+// for a macOS host and the WSTEP files for a Windows host.
+//
+// Example usage (running from the root of this repository):
+//
+// go run ./tools/mdm/decrypt-disk-encryption-key/main.go -cert path/to/file.crt \
+// -key path/to/file.key -value-to-decrypt base64-encoded-value
+package main
+
+import (
+ "errors"
+ "flag"
+ "fmt"
+
+ "github.com/apex/log"
+ "github.com/fleetdm/fleet/v4/server/config"
+ "github.com/fleetdm/fleet/v4/server/mdm"
+)
+
+func main() {
+ var (
+ certFile = flag.String("cert", "", "The path to the X509 certificate file (required).")
+ keyFile = flag.String("key", "", "The path to the X509 private key file (required).")
+ valueToDecrypt = flag.String("value-to-decrypt", "", "The base64-encoded value to decrypt (required).")
+ )
+ flag.Parse()
+
+ if *certFile == "" || *keyFile == "" || *valueToDecrypt == "" {
+ flag.Usage()
+ return
+ }
+
+ cfg := config.MDMConfig{
+ WindowsWSTEPIdentityCert: *certFile,
+ WindowsWSTEPIdentityKey: *keyFile,
+ }
+ cert, _, _, err := cfg.MicrosoftWSTEP()
+ if err != nil {
+ // unwrap the error once to remove "Microsoft WSTEP" from the error
+ // message, as we don't know in this tool if the cert is for WSTEP or SCEP
+ // (it doesn't matter)
+ if uerr := errors.Unwrap(err); uerr != nil {
+ err = uerr
+ }
+ log.Fatalf("Error loading certificate: %v", err)
+ }
+
+ decrypted, err := mdm.DecryptBase64CMS(*valueToDecrypt, cert.Leaf, cert.PrivateKey)
+ if err != nil {
+ log.Fatalf("Error decrypting value: %v", err)
+ }
+ fmt.Printf("Decrypted value: %s\n", string(decrypted))
+}
From 8362c328a1b010c7fcd8f1f4ff10ec7e0b703ddb Mon Sep 17 00:00:00 2001
From: Ian Littman
Date: Mon, 5 Aug 2024 11:23:26 -0500
Subject: [PATCH 037/612] Fix grammar on load balancer documentation in deploy
docs (#21032)
Checklist deleted as items are irrlevant for docs updates.
---
docs/Deploy/Reference-Architectures.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/Deploy/Reference-Architectures.md b/docs/Deploy/Reference-Architectures.md
index 06a7d8dd5a..630b8561b5 100644
--- a/docs/Deploy/Reference-Architectures.md
+++ b/docs/Deploy/Reference-Architectures.md
@@ -150,7 +150,7 @@ In some cases adding a read replica can increase database performance for specif
#### Traffic load balancing
Load balancing enables distributing request traffic over many instances of the backend application. Using AWS Application
-Load Balancer can also [offload SSL termination](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html), freeing Fleet to spend the majority of it's allocated compute dedicated
+Load Balancer can also [offload SSL termination](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html), freeing Fleet to spend the majority of its allocated compute dedicated
to its core functionality. More details about ALB can be found [here](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html).
_**Note if using [terraform reference architecture](https://github.com/fleetdm/fleet/tree/main/infrastructure/dogfood/terraform/aws#terraform) all configurations can dynamically scale based on load(cpu/memory) and all configurations
From 40b0349d132ef72ba0acb2fc45dc873efeff7967 Mon Sep 17 00:00:00 2001
From: Victor Lyuboslavsky
Date: Mon, 5 Aug 2024 18:44:30 +0200
Subject: [PATCH 038/612] Fixed issue where callback was clearing event body
tag. (#20997)
#20994 unreleased bug -- callback was clearing event body tag
# Checklist for submitter
- [x] Added/updated tests
- [x] Manual QA for all new/changed functionality
---
ee/server/calendar/google_calendar.go | 27 ++++++++-------
ee/server/service/calendar.go | 4 ++-
server/cron/calendar_cron.go | 9 ++---
server/fleet/calendar.go | 4 +--
server/fleet/calendar_events.go | 12 +++++--
server/fleet/calendar_events_test.go | 8 ++---
server/service/integration_enterprise_test.go | 34 +++++++++++++++++++
7 files changed, 72 insertions(+), 26 deletions(-)
diff --git a/ee/server/calendar/google_calendar.go b/ee/server/calendar/google_calendar.go
index 73c47d5cde..39c31d4807 100644
--- a/ee/server/calendar/google_calendar.go
+++ b/ee/server/calendar/google_calendar.go
@@ -298,30 +298,30 @@ func (c *GoogleCalendar) Configure(userEmail string) error {
}
func (c *GoogleCalendar) UpdateEventBody(event *fleet.CalendarEvent,
- genBodyFn fleet.CalendarGenBodyFn) error {
+ genBodyFn fleet.CalendarGenBodyFn) (string, error) {
details, err := c.unmarshalDetails(event)
if err != nil {
- return err
+ return "", err
}
gEvent, err := c.config.API.GetEvent(details.ID, "")
if err != nil {
- return ctxerr.Wrap(c.config.Context, err, "retrieving Google calendar event")
+ return "", ctxerr.Wrap(c.config.Context, err, "retrieving Google calendar event")
}
// Check if the current description contains the conflict text
conflict := strings.Contains(gEvent.Description, fleet.CalendarEventConflictText)
var ok bool
gEvent.Description, ok, err = genBodyFn(conflict)
if err != nil {
- return ctxerr.Wrap(c.config.Context, err, "generating calendar event body")
+ return "", ctxerr.Wrap(c.config.Context, err, "generating calendar event body")
}
if !ok {
- return nil
+ return "", nil
}
- _, err = c.config.API.UpdateEvent(gEvent)
+ updatedEvent, err := c.config.API.UpdateEvent(gEvent)
if err != nil {
- return ctxerr.Wrap(c.config.Context, err, "updating Google calendar event")
+ return "", ctxerr.Wrap(c.config.Context, err, "updating Google calendar event")
}
- return nil
+ return updatedEvent.Etag, nil
}
func (c *GoogleCalendar) GetAndUpdateEvent(event *fleet.CalendarEvent, genBodyFn fleet.CalendarGenBodyFn,
@@ -441,7 +441,8 @@ func (c *GoogleCalendar) GetAndUpdateEvent(event *fleet.CalendarEvent, genBodyFn
if err != nil {
return nil, false, err
}
- fleetEvent, err := c.googleEventToFleetEvent(*startTime, *endTime, gEvent, event.UUID, details.ChannelID, details.ResourceID)
+ fleetEvent, err := c.googleEventToFleetEvent(*startTime, *endTime, gEvent, event.UUID, details.ChannelID, details.ResourceID,
+ details.BodyTag)
if err != nil {
return nil, false, err
}
@@ -674,8 +675,8 @@ func (c *GoogleCalendar) createEvent(
resourceID = opts.ResourceID
}
- // Convert Google event to Fleet event
- fleetEvent, err := c.googleEventToFleetEvent(eventStart, eventEnd, event, eventUUID, channelID, resourceID)
+ // Convert Google event to Fleet event. Body tag will be updated by the calling function.
+ fleetEvent, err := c.googleEventToFleetEvent(eventStart, eventEnd, event, eventUUID, channelID, resourceID, "body_tag")
if err != nil {
return nil, err
}
@@ -726,8 +727,7 @@ func getLocation(tz string, config *GoogleCalendarConfig) *time.Location {
}
func (c *GoogleCalendar) googleEventToFleetEvent(startTime time.Time, endTime time.Time, event *calendar.Event, eventUUID string,
- channelID string,
- resourceID string) (
+ channelID string, resourceID string, bodyTag string) (
*fleet.CalendarEvent, error,
) {
tzName := c.location.String()
@@ -742,6 +742,7 @@ func (c *GoogleCalendar) googleEventToFleetEvent(startTime time.Time, endTime ti
ETag: event.Etag,
ChannelID: channelID,
ResourceID: resourceID,
+ BodyTag: bodyTag,
}
detailsJson, err := json.Marshal(details)
if err != nil {
diff --git a/ee/server/service/calendar.go b/ee/server/service/calendar.go
index 4f5f971b56..75886f1053 100644
--- a/ee/server/service/calendar.go
+++ b/ee/server/service/calendar.go
@@ -235,7 +235,9 @@ func (svc *Service) processCalendarEvent(ctx context.Context, eventDetails *flee
return ctxerr.Wrap(ctx, err, "set recent update flag")
}
// Event was updated, so we need to save it
- err = event.SaveBodyTag(generatedTag)
+ if generatedTag != "" {
+ err = event.SaveDataItems("body_tag", generatedTag)
+ }
if err != nil {
return ctxerr.Wrap(ctx, err, "save calendar event body tag")
}
diff --git a/server/cron/calendar_cron.go b/server/cron/calendar_cron.go
index 4e717a84b6..ed943caa52 100644
--- a/server/cron/calendar_cron.go
+++ b/server/cron/calendar_cron.go
@@ -396,6 +396,7 @@ func processFailingHostExistingCalendarEvent(
// Function to generate calendar event body.
var generatedTag string
+ var newETag string
var genBodyFn fleet.CalendarGenBodyFn = func(conflict bool) (string, bool, error) {
var body string
body, generatedTag = calendar.GenerateCalendarEventBody(ctx, ds, orgName, host, policyIDtoPolicy, conflict, logger)
@@ -409,7 +410,7 @@ func processFailingHostExistingCalendarEvent(
updatedBodyTag := getBodyTag(ctx, ds, host, policyIDtoPolicy, logger)
if currentBodyTag != updatedBodyTag && updatedBodyTag != "" {
- err = userCalendar.UpdateEventBody(calendarEvent, genBodyFn)
+ newETag, err = userCalendar.UpdateEventBody(calendarEvent, genBodyFn)
if err != nil {
return fmt.Errorf("update event body: %w", err)
}
@@ -440,8 +441,8 @@ func processFailingHostExistingCalendarEvent(
}
if updated {
- if generatedTag != "" {
- err = updatedEvent.SaveBodyTag(generatedTag)
+ if generatedTag != "" && newETag != "" {
+ err = updatedEvent.SaveDataItems("body_tag", generatedTag, "etag", newETag)
if err != nil {
return fmt.Errorf("save calendar event body tag: %w", err)
}
@@ -623,7 +624,7 @@ func attemptCreatingEventOnUserCalendar(
var dee fleet.DayEndedError
switch {
case err == nil:
- err = calendarEvent.SaveBodyTag(generatedTag)
+ err = calendarEvent.SaveDataItems("body_tag", generatedTag)
if err != nil {
return nil, err
}
diff --git a/server/fleet/calendar.go b/server/fleet/calendar.go
index 72c3e9e0ba..fa9de8f07a 100644
--- a/server/fleet/calendar.go
+++ b/server/fleet/calendar.go
@@ -42,8 +42,8 @@ type UserCalendar interface {
GetAndUpdateEvent(event *CalendarEvent, genBodyFn CalendarGenBodyFn,
opts CalendarGetAndUpdateEventOpts) (updatedEvent *CalendarEvent,
updated bool, err error)
- // UpdateEventBody updates the body of the calendar event.
- UpdateEventBody(event *CalendarEvent, genBodyFn CalendarGenBodyFn) error
+ // UpdateEventBody updates the body of the calendar event and returns new ETag
+ UpdateEventBody(event *CalendarEvent, genBodyFn CalendarGenBodyFn) (string, error)
// DeleteEvent deletes the event with the given ID.
DeleteEvent(event *CalendarEvent) error
// StopEventChannel stops the event's callback channel.
diff --git a/server/fleet/calendar_events.go b/server/fleet/calendar_events.go
index 44d11306fc..5e730e283b 100644
--- a/server/fleet/calendar_events.go
+++ b/server/fleet/calendar_events.go
@@ -2,6 +2,7 @@ package fleet
import (
"encoding/json"
+ "errors"
"fmt"
"time"
)
@@ -30,7 +31,10 @@ func (ce *CalendarEvent) GetBodyTag() string {
return d.BodyTag
}
-func (ce *CalendarEvent) SaveBodyTag(bodyTag string) error {
+func (ce *CalendarEvent) SaveDataItems(keysAndValues ...string) error {
+ if len(keysAndValues)%2 != 0 {
+ return errors.New("SaveDataItem requires an even number of arguments")
+ }
var result map[string]any
if len(ce.Data) > 0 {
err := json.Unmarshal(ce.Data, &result)
@@ -40,7 +44,11 @@ func (ce *CalendarEvent) SaveBodyTag(bodyTag string) error {
} else {
result = make(map[string]any, 1)
}
- result["body_tag"] = bodyTag
+ for i := 0; i < len(keysAndValues); i += 2 {
+ key := keysAndValues[i]
+ value := keysAndValues[i+1]
+ result[key] = value
+ }
data, err := json.Marshal(result)
if err != nil {
return fmt.Errorf("could not marshal event data: %w", err)
diff --git a/server/fleet/calendar_events_test.go b/server/fleet/calendar_events_test.go
index 3e79bd6bb4..931cd3889f 100644
--- a/server/fleet/calendar_events_test.go
+++ b/server/fleet/calendar_events_test.go
@@ -8,7 +8,7 @@ import (
"github.com/stretchr/testify/require"
)
-func TestBodyTag(t *testing.T) {
+func TestSaveDataItems(t *testing.T) {
t.Parallel()
var event CalendarEvent
@@ -16,7 +16,7 @@ func TestBodyTag(t *testing.T) {
assert.Equal(t, "", event.GetBodyTag())
bodyTag := "bodyTag"
- require.NoError(t, event.SaveBodyTag(bodyTag))
+ require.NoError(t, event.SaveDataItems("body_tag", bodyTag))
assert.Equal(t, bodyTag, event.GetBodyTag())
testMap := make(map[string]any, 5)
@@ -29,11 +29,11 @@ func TestBodyTag(t *testing.T) {
event.Data = data
assert.Equal(t, oldBodyTag, event.GetBodyTag())
- require.NoError(t, event.SaveBodyTag(bodyTag))
+ require.NoError(t, event.SaveDataItems("body_tag", bodyTag))
assert.Equal(t, bodyTag, event.GetBodyTag())
// Make sure data was not modified
- require.NoError(t, event.SaveBodyTag(oldBodyTag))
+ require.NoError(t, event.SaveDataItems("body_tag", oldBodyTag))
var result map[string]any
require.NoError(t, json.Unmarshal(event.Data, &result))
assert.Equal(t, testMap, result)
diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go
index f87d9b144a..438138dc67 100644
--- a/server/service/integration_enterprise_test.go
+++ b/server/service/integration_enterprise_test.go
@@ -11728,11 +11728,15 @@ func (s *integrationEnterpriseTestSuite) TestCalendarCallback() {
require.NotZero(t, event.StartTime)
require.NotZero(t, event.EndTime)
require.NotEmpty(t, event.UUID)
+ bodyTag := event.GetBodyTag()
+ assert.NotEmpty(t, bodyTag)
assert.Equal(t, 1, calendar.MockChannelsCount())
// Get channel ID
type eventDetails struct {
ChannelID string `json:"channel_id"`
+ BodyTag string `json:"body_tag"`
+ ETag string `json:"etag"`
}
var details eventDetails
err = json.Unmarshal(event.Data, &details)
@@ -11855,6 +11859,8 @@ func (s *integrationEnterpriseTestSuite) TestCalendarCallback() {
err = json.Unmarshal(eventRecreated.Data, &details)
require.NoError(t, err)
+ assert.NotEmpty(t, details.BodyTag)
+ bodyTag = details.BodyTag
// New event callback should work
_ = s.DoRawWithHeaders("POST", "/api/v1/fleet/calendar/webhook/"+eventRecreated.UUID, []byte(""), http.StatusOK,
@@ -11891,6 +11897,30 @@ func (s *integrationEnterpriseTestSuite) TestCalendarCallback() {
assert.Greater(t, eventUpdated.StartTime, eventRecreated.StartTime)
assert.Equal(t, eventRecreated.EndTime, eventUpdated.EndTime)
assert.Equal(t, 1, calendar.MockChannelsCount())
+ assert.Equal(t, bodyTag, eventRecreated.GetBodyTag())
+
+ // Change the body contents of event.
+ events = calendar.ListGoogleMockEvents()
+ require.Len(t, events, 1)
+ eTag := "description change etag"
+ for _, e := range events {
+ e.Etag = eTag
+ e.Description = "new description"
+ }
+ // New event callback should cause Etag to update but Body tag to remain the same
+ _ = s.DoRawWithHeaders("POST", "/api/v1/fleet/calendar/webhook/"+eventRecreated.UUID, []byte(""), http.StatusOK,
+ map[string]string{
+ "X-Goog-Channel-Id": details.ChannelID,
+ "X-Goog-Resource-State": "exists",
+ })
+ team1CalendarEvents, err = s.ds.ListCalendarEvents(ctx, &team1.ID)
+ require.NoError(t, err)
+ require.Len(t, team1CalendarEvents, 1)
+ eventDescUpdated := team1CalendarEvents[0]
+ err = json.Unmarshal(eventDescUpdated.Data, &details)
+ require.NoError(t, err)
+ assert.Equal(t, bodyTag, details.BodyTag)
+ assert.Equal(t, eTag, details.ETag)
// Update the time of the event again
events = calendar.ListGoogleMockEvents()
@@ -11900,6 +11930,7 @@ func (s *integrationEnterpriseTestSuite) TestCalendarCallback() {
require.NoError(t, err)
newStartTime := st.Add(5 * time.Minute).Format(time.RFC3339)
e.Start.DateTime = newStartTime
+ e.Etag = e.Etag + "1"
}
// Grab the lock
@@ -11950,6 +11981,9 @@ func (s *integrationEnterpriseTestSuite) TestCalendarCallback() {
require.NoError(t, err)
if len(team1CalendarEvents) == 1 && team1CalendarEvents[0].UUID == event.UUID &&
team1CalendarEvents[0].StartTime.After(event.StartTime) {
+ err = json.Unmarshal(team1CalendarEvents[0].Data, &details)
+ require.NoError(t, err)
+ assert.NotEqual(t, eTag, details.ETag, "ETag should have updated")
done <- struct{}{}
return
}
From 7f95ae46a35e99228ee4457f1735584406f97a53 Mon Sep 17 00:00:00 2001
From: Sarah Gillespie <73313222+gillespi314@users.noreply.github.com>
Date: Mon, 5 Aug 2024 12:30:57 -0500
Subject: [PATCH 039/612] Update content of "Turn on MDM" banner in UI (#21046)
---
changes/20882-ui-update-turn-on-mdm-banner | 1 +
.../components/DeviceUserBanners/DeviceUserBanners.tsx | 5 +++--
.../components/HostDetailsBanners/HostDetailsBanners.tsx | 6 +++---
3 files changed, 7 insertions(+), 5 deletions(-)
create mode 100644 changes/20882-ui-update-turn-on-mdm-banner
diff --git a/changes/20882-ui-update-turn-on-mdm-banner b/changes/20882-ui-update-turn-on-mdm-banner
new file mode 100644
index 0000000000..eca36625ce
--- /dev/null
+++ b/changes/20882-ui-update-turn-on-mdm-banner
@@ -0,0 +1 @@
+- Updated text for "Turn on MDM" banners in UI.
\ No newline at end of file
diff --git a/frontend/pages/hosts/details/DeviceUserPage/components/DeviceUserBanners/DeviceUserBanners.tsx b/frontend/pages/hosts/details/DeviceUserPage/components/DeviceUserBanners/DeviceUserBanners.tsx
index 028165648f..286101a026 100644
--- a/frontend/pages/hosts/details/DeviceUserPage/components/DeviceUserBanners/DeviceUserBanners.tsx
+++ b/frontend/pages/hosts/details/DeviceUserPage/components/DeviceUserBanners/DeviceUserBanners.tsx
@@ -51,8 +51,9 @@ const DeviceUserBanners = ({
return (
Mobile device management (MDM) is off. MDM allows your organization to
- change settings and install software. This lets your organization keep
- your device up to date so you don't have to.
+ enforce settings, OS updates, disk encryption, and more. This lets
+ your organization keep your device up to date so you don't have
+ to.
);
}
diff --git a/frontend/pages/hosts/details/HostDetailsPage/components/HostDetailsBanners/HostDetailsBanners.tsx b/frontend/pages/hosts/details/HostDetailsPage/components/HostDetailsBanners/HostDetailsBanners.tsx
index 2a35d4a418..fbd7e94c24 100644
--- a/frontend/pages/hosts/details/HostDetailsPage/components/HostDetailsBanners/HostDetailsBanners.tsx
+++ b/frontend/pages/hosts/details/HostDetailsPage/components/HostDetailsBanners/HostDetailsBanners.tsx
@@ -73,9 +73,9 @@ const HostDetailsBanners = ({
{showTurnOnMdmInfoBanner && (
- To change settings and install software, ask the end user to follow
- the Turn on MDM instructions on their{" "}
- My device page.
+ To enforce settings, OS updates, disk encryption, and more, ask the
+ end user to follow the Turn on MDM instructions on
+ their My device page.
)}
{showDiskEncryptionUserActionRequired && (
From a6a9a2e1c26cbea6ecdb40e9501045b5a8d58d05 Mon Sep 17 00:00:00 2001
From: Tim Lee
Date: Mon, 5 Aug 2024 11:39:10 -0600
Subject: [PATCH 040/612] no team software gitops (#20847)
#20464
Adding gitops support for a top level `software` key to be used to
manage installable software into "no team".
- [ ] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files)
for more information.
- [X] Added/updated tests
- [X] Manual QA for all new/changed functionality
---------
Co-authored-by: Victor Lyuboslavsky
---
cmd/fleetctl/apply.go | 2 +-
cmd/fleetctl/gitops.go | 2 +-
.../gitops_enterprise_integration_test.go | 2 +-
cmd/fleetctl/gitops_test.go | 47 +++-
cmd/fleetctl/preview.go | 2 +-
.../gitops/global_config_no_paths.yml | 1 +
...macos_custom_settings_valid_deprecated.yml | 1 +
...al_macos_windows_custom_settings_valid.yml | 1 +
...dows_custom_settings_invalid_label_mix.yml | 1 +
..._windows_custom_settings_unknown_label.yml | 1 +
...m_software_installer_install_not_found.yml | 19 ++
...e_installer_invalid_self_service_value.yml | 18 ++
.../no_team_software_installer_no_url.yml | 22 ++
.../no_team_software_installer_not_found.yml | 17 ++
...tware_installer_post_install_not_found.yml | 21 ++
...staller_pre_condition_multiple_queries.yml | 23 ++
...ware_installer_pre_condition_not_found.yml | 21 ++
.../no_team_software_installer_too_large.yml | 17 ++
...no_team_software_installer_unsupported.yml | 17 ++
.../no_team_software_installer_valid.yml | 25 ++
docs/Contributing/API-for-contributors.md | 4 +-
ee/server/service/software_installers.go | 27 +--
ee/server/service/teams.go | 2 +-
pkg/spec/gitops.go | 24 +-
pkg/spec/gitops_test.go | 16 +-
pkg/spec/spec.go | 1 +
server/fleet/software_installer.go | 14 ++
server/fleet/teams.go | 17 +-
server/service/client.go | 224 +++++++++++-------
server/service/client_software.go | 11 +
server/service/integration_enterprise_test.go | 41 +++-
server/service/software_installers.go | 2 +-
32 files changed, 492 insertions(+), 151 deletions(-)
create mode 100644 cmd/fleetctl/testdata/gitops/no_team_software_installer_install_not_found.yml
create mode 100644 cmd/fleetctl/testdata/gitops/no_team_software_installer_invalid_self_service_value.yml
create mode 100644 cmd/fleetctl/testdata/gitops/no_team_software_installer_no_url.yml
create mode 100644 cmd/fleetctl/testdata/gitops/no_team_software_installer_not_found.yml
create mode 100644 cmd/fleetctl/testdata/gitops/no_team_software_installer_post_install_not_found.yml
create mode 100644 cmd/fleetctl/testdata/gitops/no_team_software_installer_pre_condition_multiple_queries.yml
create mode 100644 cmd/fleetctl/testdata/gitops/no_team_software_installer_pre_condition_not_found.yml
create mode 100644 cmd/fleetctl/testdata/gitops/no_team_software_installer_too_large.yml
create mode 100644 cmd/fleetctl/testdata/gitops/no_team_software_installer_unsupported.yml
create mode 100644 cmd/fleetctl/testdata/gitops/no_team_software_installer_valid.yml
diff --git a/cmd/fleetctl/apply.go b/cmd/fleetctl/apply.go
index 751c74709f..a84ade90f0 100644
--- a/cmd/fleetctl/apply.go
+++ b/cmd/fleetctl/apply.go
@@ -90,7 +90,7 @@ func applyCommand() *cli.Command {
opts.TeamForPolicies = policiesTeamName
}
baseDir := filepath.Dir(flFilename)
- _, err = fleetClient.ApplyGroup(c.Context, specs, baseDir, logf, opts)
+ _, err = fleetClient.ApplyGroup(c.Context, specs, baseDir, logf, nil, opts)
if err != nil {
return err
}
diff --git a/cmd/fleetctl/gitops.go b/cmd/fleetctl/gitops.go
index c56016eca3..6f13fd29e9 100644
--- a/cmd/fleetctl/gitops.go
+++ b/cmd/fleetctl/gitops.go
@@ -90,7 +90,7 @@ func gitopsCommand() *cli.Command {
secrets := make(map[string]struct{})
for _, flFilename := range flFilenames.Value() {
baseDir := filepath.Dir(flFilename)
- config, err := spec.GitOpsFromFile(flFilename, baseDir)
+ config, err := spec.GitOpsFromFile(flFilename, baseDir, appConfig)
if err != nil {
return err
}
diff --git a/cmd/fleetctl/gitops_enterprise_integration_test.go b/cmd/fleetctl/gitops_enterprise_integration_test.go
index ab3f8bf835..7089ef6893 100644
--- a/cmd/fleetctl/gitops_enterprise_integration_test.go
+++ b/cmd/fleetctl/gitops_enterprise_integration_test.go
@@ -176,6 +176,7 @@ contexts:
fmt.Sprintf(
`
controls:
+software:
queries:
policies:
agent_options:
@@ -230,5 +231,4 @@ team_settings:
for _, fileName := range teamFileNames {
_ = runAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", fileName})
}
-
}
diff --git a/cmd/fleetctl/gitops_test.go b/cmd/fleetctl/gitops_test.go
index 826dd43374..22bd28845a 100644
--- a/cmd/fleetctl/gitops_test.go
+++ b/cmd/fleetctl/gitops_test.go
@@ -36,7 +36,7 @@ const (
orgName = "GitOps Test"
)
-func TestFilenameValidation(t *testing.T) {
+func TestFilenameGitOpsValidation(t *testing.T) {
filename := strings.Repeat("a", filenameMaxLength+1)
_, err := runAppNoChecks([]string{"gitops", "-f", filename})
assert.ErrorContains(t, err, "file name must be less than")
@@ -207,6 +207,9 @@ func TestBasicGlobalPremiumGitOps(t *testing.T) {
ds.NewJobFunc = func(ctx context.Context, job *fleet.Job) (*fleet.Job, error) {
return &fleet.Job{}, nil
}
+ ds.BatchSetSoftwareInstallersFunc = func(ctx context.Context, teamID *uint, installers []*fleet.UploadSoftwareInstallerPayload) error {
+ return nil
+ }
tmpFile, err := os.CreateTemp(t.TempDir(), "*.yml")
require.NoError(t, err)
@@ -238,6 +241,7 @@ org_settings:
org_logo_url_light_background: ""
org_name: ${ORG_NAME}
secrets:
+software:
`,
)
require.NoError(t, err)
@@ -381,6 +385,7 @@ agent_options:
name: ${TEST_TEAM_NAME}
team_settings:
secrets: ${TEST_SECRET}
+software:
`,
)
require.NoError(t, err)
@@ -538,6 +543,7 @@ func TestFullGlobalGitOps(t *testing.T) {
t.Setenv("FLEET_SERVER_URL", fleetServerURL)
t.Setenv("ORG_NAME", orgName)
t.Setenv("APPLE_BM_DEFAULT_TEAM", teamName)
+ t.Setenv("SOFTWARE_INSTALLER_URL", fleetServerURL)
file := "./testdata/gitops/global_config_no_paths.yml"
// Dry run should fail because Apple BM Default Team does not exist and premium license is not set
@@ -834,6 +840,7 @@ agent_options:
name: ${TEST_TEAM_NAME}
team_settings:
secrets: [{"secret":"${TEST_SECRET}"}]
+software:
`,
)
require.NoError(t, err)
@@ -1011,6 +1018,7 @@ org_settings:
org_logo_url_light_background: ""
org_name: ${ORG_NAME}
secrets: [{"secret":"globalSecret"}]
+software:
`,
)
require.NoError(t, err)
@@ -1030,6 +1038,7 @@ agent_options:
name: ${TEST_TEAM_NAME}
team_settings:
secrets: [{"secret":"${TEST_SECRET}"}]
+software:
`,
)
require.NoError(t, err)
@@ -1045,6 +1054,7 @@ agent_options:
name: ${TEST_TEAM_NAME}
team_settings:
secrets: [{"secret":"${TEST_SECRET}"},{"secret":"globalSecret"}]
+software:
`,
)
require.NoError(t, err)
@@ -1222,7 +1232,7 @@ func TestTeamSofwareInstallersGitOps(t *testing.T) {
{"testdata/gitops/team_software_installer_install_not_found.yml", "no such file or directory"},
{"testdata/gitops/team_software_installer_post_install_not_found.yml", "no such file or directory"},
{"testdata/gitops/team_software_installer_no_url.yml", "software URL is required"},
- {"testdata/gitops/team_software_installer_invalid_self_service_value.yml", "cannot unmarshal string into Go struct field TeamSpecSoftware.packages of type bool"},
+ {"testdata/gitops/team_software_installer_invalid_self_service_value.yml", "cannot unmarshal string into Go struct field SoftwareSpec.packages of type bool"},
}
for _, c := range cases {
t.Run(filepath.Base(c.file), func(t *testing.T) {
@@ -1255,6 +1265,39 @@ func TestTeamSoftwareInstallersGitopsQueryEnv(t *testing.T) {
require.NoError(t, err)
}
+func TestNoTeamSoftwareInstallersGitOps(t *testing.T) {
+ startSoftwareInstallerServer(t)
+
+ cases := []struct {
+ file string
+ wantErr string
+ }{
+ {"testdata/gitops/no_team_software_installer_not_found.yml", "Please make sure that URLs are publicy accessible to the internet."},
+ {"testdata/gitops/no_team_software_installer_unsupported.yml", "The file should be .pkg, .msi, .exe or .deb."},
+ {"testdata/gitops/no_team_software_installer_too_large.yml", "The maximum file size is 500 MB"},
+ {"testdata/gitops/no_team_software_installer_valid.yml", ""},
+ {"testdata/gitops/no_team_software_installer_pre_condition_multiple_queries.yml", "should have only one query."},
+ {"testdata/gitops/no_team_software_installer_pre_condition_not_found.yml", "no such file or directory"},
+ {"testdata/gitops/no_team_software_installer_install_not_found.yml", "no such file or directory"},
+ {"testdata/gitops/no_team_software_installer_post_install_not_found.yml", "no such file or directory"},
+ {"testdata/gitops/no_team_software_installer_no_url.yml", "software URL is required"},
+ {"testdata/gitops/no_team_software_installer_invalid_self_service_value.yml", "cannot unmarshal string into Go struct field SoftwareSpec.packages of type bool"},
+ }
+ for _, c := range cases {
+ t.Run(filepath.Base(c.file), func(t *testing.T) {
+ setupFullGitOpsPremiumServer(t)
+
+ t.Setenv("APPLE_BM_DEFAULT_TEAM", "")
+ _, err := runAppNoChecks([]string{"gitops", "-f", c.file})
+ if c.wantErr == "" {
+ require.NoError(t, err)
+ } else {
+ require.ErrorContains(t, err, c.wantErr)
+ }
+ })
+ }
+}
+
func TestTeamVPPAppsGitOps(t *testing.T) {
config := &appleVPPConfigSrvConf{
Assets: []vpp.Asset{
diff --git a/cmd/fleetctl/preview.go b/cmd/fleetctl/preview.go
index 1aa78695f9..e11dc8bf9e 100644
--- a/cmd/fleetctl/preview.go
+++ b/cmd/fleetctl/preview.go
@@ -387,7 +387,7 @@ Use the stop and reset subcommands to manage the server and dependencies once st
}
// this only applies standard queries, the base directory is not used,
// so pass in the current working directory.
- _, err = client.ApplyGroup(c.Context, specs, ".", logf, fleet.ApplyClientSpecOptions{})
+ _, err = client.ApplyGroup(c.Context, specs, ".", logf, nil, fleet.ApplyClientSpecOptions{})
if err != nil {
return err
}
diff --git a/cmd/fleetctl/testdata/gitops/global_config_no_paths.yml b/cmd/fleetctl/testdata/gitops/global_config_no_paths.yml
index 76936e3ad5..7d3dfe40f2 100644
--- a/cmd/fleetctl/testdata/gitops/global_config_no_paths.yml
+++ b/cmd/fleetctl/testdata/gitops/global_config_no_paths.yml
@@ -187,3 +187,4 @@ org_settings:
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_macos_custom_settings_valid_deprecated.yml b/cmd/fleetctl/testdata/gitops/global_macos_custom_settings_valid_deprecated.yml
index 177c1c80cf..b098442585 100644
--- a/cmd/fleetctl/testdata/gitops/global_macos_custom_settings_valid_deprecated.yml
+++ b/cmd/fleetctl/testdata/gitops/global_macos_custom_settings_valid_deprecated.yml
@@ -91,3 +91,4 @@ org_settings:
databases_path: ""
secrets:
- secret: ABC
+software:
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/global_macos_windows_custom_settings_valid.yml b/cmd/fleetctl/testdata/gitops/global_macos_windows_custom_settings_valid.yml
index da75847cd5..e6231bf030 100644
--- a/cmd/fleetctl/testdata/gitops/global_macos_windows_custom_settings_valid.yml
+++ b/cmd/fleetctl/testdata/gitops/global_macos_windows_custom_settings_valid.yml
@@ -97,3 +97,4 @@ org_settings:
databases_path: ""
secrets:
- secret: ABC
+software:
diff --git a/cmd/fleetctl/testdata/gitops/global_windows_custom_settings_invalid_label_mix.yml b/cmd/fleetctl/testdata/gitops/global_windows_custom_settings_invalid_label_mix.yml
index 9d2ac6e69f..1a100de6f3 100644
--- a/cmd/fleetctl/testdata/gitops/global_windows_custom_settings_invalid_label_mix.yml
+++ b/cmd/fleetctl/testdata/gitops/global_windows_custom_settings_invalid_label_mix.yml
@@ -93,3 +93,4 @@ org_settings:
databases_path: ""
secrets:
- secret: ABC
+software:
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/global_windows_custom_settings_unknown_label.yml b/cmd/fleetctl/testdata/gitops/global_windows_custom_settings_unknown_label.yml
index ba1d06f784..5208ba7248 100644
--- a/cmd/fleetctl/testdata/gitops/global_windows_custom_settings_unknown_label.yml
+++ b/cmd/fleetctl/testdata/gitops/global_windows_custom_settings_unknown_label.yml
@@ -91,3 +91,4 @@ org_settings:
databases_path: ""
secrets:
- secret: ABC
+software:
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/no_team_software_installer_install_not_found.yml b/cmd/fleetctl/testdata/gitops/no_team_software_installer_install_not_found.yml
new file mode 100644
index 0000000000..d3bcada54e
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/no_team_software_installer_install_not_found.yml
@@ -0,0 +1,19 @@
+# Test config
+controls:
+queries:
+policies:
+agent_options:
+org_settings:
+ server_settings:
+ server_url: $FLEET_SERVER_URL
+ org_info:
+ contact_url: https://example.com/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: ${ORG_NAME}
+ secrets: [{"secret":"globalSecret"}]
+software:
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
+ install_script:
+ path: lib/notfound.sh
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/no_team_software_installer_invalid_self_service_value.yml b/cmd/fleetctl/testdata/gitops/no_team_software_installer_invalid_self_service_value.yml
new file mode 100644
index 0000000000..acee06d683
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/no_team_software_installer_invalid_self_service_value.yml
@@ -0,0 +1,18 @@
+# Test config
+controls:
+queries:
+policies:
+agent_options:
+org_settings:
+ server_settings:
+ server_url: $FLEET_SERVER_URL
+ org_info:
+ contact_url: https://example.com/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: ${ORG_NAME}
+ secrets: [{"secret":"globalSecret"}]
+software:
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/invalidtype.txt
+ self_service: "not a boolean"
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/no_team_software_installer_no_url.yml b/cmd/fleetctl/testdata/gitops/no_team_software_installer_no_url.yml
new file mode 100644
index 0000000000..6d83a9daed
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/no_team_software_installer_no_url.yml
@@ -0,0 +1,22 @@
+# Test config
+controls:
+queries:
+policies:
+agent_options:
+org_settings:
+ server_settings:
+ server_url: $FLEET_SERVER_URL
+ org_info:
+ contact_url: https://example.com/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: ${ORG_NAME}
+ secrets: [{"secret":"globalSecret"}]
+software:
+ packages:
+ - install_script:
+ path: lib/install_ruby.sh
+ pre_install_query:
+ path: lib/query_ruby.yml
+ post_install_script:
+ path: lib/post_install_ruby.sh
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/no_team_software_installer_not_found.yml b/cmd/fleetctl/testdata/gitops/no_team_software_installer_not_found.yml
new file mode 100644
index 0000000000..cd7332f91e
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/no_team_software_installer_not_found.yml
@@ -0,0 +1,17 @@
+# Test config
+controls:
+queries:
+policies:
+agent_options:
+org_settings:
+ server_settings:
+ server_url: $FLEET_SERVER_URL
+ org_info:
+ contact_url: https://example.com/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: ${ORG_NAME}
+ secrets: [{"secret":"globalSecret"}]
+software:
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/notfound.deb
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/no_team_software_installer_post_install_not_found.yml b/cmd/fleetctl/testdata/gitops/no_team_software_installer_post_install_not_found.yml
new file mode 100644
index 0000000000..ac0a436360
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/no_team_software_installer_post_install_not_found.yml
@@ -0,0 +1,21 @@
+# Test config
+controls:
+queries:
+policies:
+agent_options:
+org_settings:
+ server_settings:
+ server_url: $FLEET_SERVER_URL
+ org_info:
+ contact_url: https://example.com/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: ${ORG_NAME}
+ secrets: [{"secret":"globalSecret"}]
+software:
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
+ install_script:
+ path: lib/install_ruby.sh
+ post_install_script:
+ path: lib/notfound.sh
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/no_team_software_installer_pre_condition_multiple_queries.yml b/cmd/fleetctl/testdata/gitops/no_team_software_installer_pre_condition_multiple_queries.yml
new file mode 100644
index 0000000000..a2b5419c05
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/no_team_software_installer_pre_condition_multiple_queries.yml
@@ -0,0 +1,23 @@
+# Test config
+controls:
+queries:
+policies:
+agent_options:
+org_settings:
+ server_settings:
+ server_url: $FLEET_SERVER_URL
+ org_info:
+ contact_url: https://example.com/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: ${ORG_NAME}
+ secrets: [{"secret":"globalSecret"}]
+software:
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
+ install_script:
+ path: lib/install_ruby.sh
+ pre_install_query:
+ path: lib/query_multiple.yml
+ post_install_script:
+ path: lib/post_install_ruby.sh
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/no_team_software_installer_pre_condition_not_found.yml b/cmd/fleetctl/testdata/gitops/no_team_software_installer_pre_condition_not_found.yml
new file mode 100644
index 0000000000..bafde42691
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/no_team_software_installer_pre_condition_not_found.yml
@@ -0,0 +1,21 @@
+# Test config
+controls:
+queries:
+policies:
+agent_options:
+org_settings:
+ server_settings:
+ server_url: $FLEET_SERVER_URL
+ org_info:
+ contact_url: https://example.com/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: ${ORG_NAME}
+ secrets: [{"secret":"globalSecret"}]
+software:
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
+ install_script:
+ path: lib/install_ruby.sh
+ pre_install_query:
+ path: lib/notfound.yml
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/no_team_software_installer_too_large.yml b/cmd/fleetctl/testdata/gitops/no_team_software_installer_too_large.yml
new file mode 100644
index 0000000000..db4ffd3211
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/no_team_software_installer_too_large.yml
@@ -0,0 +1,17 @@
+# Test config
+controls:
+queries:
+policies:
+agent_options:
+org_settings:
+ server_settings:
+ server_url: $FLEET_SERVER_URL
+ org_info:
+ contact_url: https://example.com/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: ${ORG_NAME}
+ secrets: [{"secret":"globalSecret"}]
+software:
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/toolarge.deb
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/no_team_software_installer_unsupported.yml b/cmd/fleetctl/testdata/gitops/no_team_software_installer_unsupported.yml
new file mode 100644
index 0000000000..2bc609b931
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/no_team_software_installer_unsupported.yml
@@ -0,0 +1,17 @@
+# Test config
+controls:
+queries:
+policies:
+agent_options:
+org_settings:
+ server_settings:
+ server_url: $FLEET_SERVER_URL
+ org_info:
+ contact_url: https://example.com/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: ${ORG_NAME}
+ secrets: [{"secret":"globalSecret"}]
+software:
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/invalidtype.txt
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/no_team_software_installer_valid.yml b/cmd/fleetctl/testdata/gitops/no_team_software_installer_valid.yml
new file mode 100644
index 0000000000..e0fcaa490e
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/no_team_software_installer_valid.yml
@@ -0,0 +1,25 @@
+# Test config
+controls:
+queries:
+policies:
+agent_options:
+org_settings:
+ server_settings:
+ server_url: $FLEET_SERVER_URL
+ org_info:
+ contact_url: https://example.com/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: ${ORG_NAME}
+ secrets: [{"secret":"globalSecret"}]
+software:
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
+ install_script:
+ path: lib/install_ruby.sh
+ pre_install_query:
+ path: lib/query_ruby.yml
+ post_install_script:
+ path: lib/post_install_ruby.sh
+ - url: ${SOFTWARE_INSTALLER_URL}/other.deb
+ self_service: true
\ No newline at end of file
diff --git a/docs/Contributing/API-for-contributors.md b/docs/Contributing/API-for-contributors.md
index 0f1f3a2ffb..4699b76d3d 100644
--- a/docs/Contributing/API-for-contributors.md
+++ b/docs/Contributing/API-for-contributors.md
@@ -2957,8 +2957,8 @@ _Available in Fleet Premium._
| Name | Type | In | Description |
| --------- | ------ | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| team_id | number | query | The ID of the team to add the software package to. Only one team identifier (`team_id` or `team_name`) can be included in the request, omit this parameter if using `team_name`. |
-| team_name | string | query | The name of the team to add the software package to. Only one team identifier (`team_id` or `team_name`) can be included in the request, omit this parameter if using `team_id`. |
+| team_id | number | query | The ID of the team to add the software package to. Only one team identifier (`team_id` or `team_name`) can be included in the request, omit this parameter if using `team_name`. Ommitting these parameters will add software to 'No Team'. |
+| team_name | string | query | The name of the team to add the software package to. Only one team identifier (`team_id` or `team_name`) can be included in the request, omit this parameter if using `team_id`. Ommitting these parameters will add software to 'No Team'. |
| dry_run | bool | query | If `true`, will validate the provided software packages and return any validation errors, but will not apply the changes. |
| software | list | body | An array of software objects. Each object consists of:`url`- URL to the software package (PKG, MSI, EXE or DEB),`install_script` - command that Fleet runs to install software, `pre_install_query` - condition query that determines if the install will proceed, and `post_install_script` - script that runs after software install. |
diff --git a/ee/server/service/software_installers.go b/ee/server/service/software_installers.go
index 9871f9fd44..ad0dbe0a81 100644
--- a/ee/server/service/software_installers.go
+++ b/ee/server/service/software_installers.go
@@ -580,25 +580,24 @@ func (svc *Service) addMetadataToSoftwarePayload(ctx context.Context, payload *f
const maxInstallerSizeBytes int64 = 1024 * 1024 * 500
func (svc *Service) BatchSetSoftwareInstallers(ctx context.Context, tmName string, payloads []fleet.SoftwareInstallerPayload, dryRun bool) error {
- if tmName == "" {
- svc.authz.SkipAuthorization(ctx) // so that the error message is not replaced by "forbidden"
- return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("team_name", "must not be empty"))
- }
-
if err := svc.authz.Authorize(ctx, &fleet.Team{}, fleet.ActionRead); err != nil {
return err
}
- tm, err := svc.ds.TeamByName(ctx, tmName)
- if err != nil {
- // If this is a dry run, the team may not have been created yet
- if dryRun && fleet.IsNotFound(err) {
- return nil
+ var teamID *uint
+ if tmName != "" {
+ tm, err := svc.ds.TeamByName(ctx, tmName)
+ if err != nil {
+ // If this is a dry run, the team may not have been created yet
+ if dryRun && fleet.IsNotFound(err) {
+ return nil
+ }
+ return err
}
- return err
+ teamID = &tm.ID
}
- if err := svc.authz.Authorize(ctx, &fleet.SoftwareInstaller{TeamID: &tm.ID}, fleet.ActionWrite); err != nil {
+ if err := svc.authz.Authorize(ctx, &fleet.SoftwareInstaller{TeamID: teamID}, fleet.ActionWrite); err != nil {
return ctxerr.Wrap(ctx, err, "validating authorization")
}
@@ -672,7 +671,7 @@ func (svc *Service) BatchSetSoftwareInstallers(ctx context.Context, tmName strin
}
installer := &fleet.UploadSoftwareInstallerPayload{
- TeamID: &tm.ID,
+ TeamID: teamID,
InstallScript: p.InstallScript,
PreInstallQuery: p.PreInstallQuery,
PostInstallScript: p.PostInstallScript,
@@ -732,7 +731,7 @@ func (svc *Service) BatchSetSoftwareInstallers(ctx context.Context, tmName strin
}
}
- if err := svc.ds.BatchSetSoftwareInstallers(ctx, &tm.ID, installers); err != nil {
+ if err := svc.ds.BatchSetSoftwareInstallers(ctx, teamID, installers); err != nil {
return ctxerr.Wrap(ctx, err, "batch set software installers")
}
diff --git a/ee/server/service/teams.go b/ee/server/service/teams.go
index 4b8df03123..3d25abafe9 100644
--- a/ee/server/service/teams.go
+++ b/ee/server/service/teams.go
@@ -1246,7 +1246,7 @@ func (svc *Service) editTeamFromSpec(
if spec.Software != nil {
if team.Config.Software == nil {
- team.Config.Software = &fleet.TeamSpecSoftware{}
+ team.Config.Software = &fleet.SoftwareSpec{}
}
if spec.Software.Packages.Set {
diff --git a/pkg/spec/gitops.go b/pkg/spec/gitops.go
index b6eea43708..e29e9138dc 100644
--- a/pkg/spec/gitops.go
+++ b/pkg/spec/gitops.go
@@ -61,12 +61,12 @@ type GitOps struct {
}
type GitOpsSoftware struct {
- Packages []*fleet.TeamSpecSoftwarePackage
+ Packages []*fleet.SoftwarePackageSpec
AppStoreApps []*fleet.TeamSpecAppStoreApp
}
// GitOpsFromFile parses a GitOps yaml file.
-func GitOpsFromFile(filePath, baseDir string) (*GitOps, error) {
+func GitOpsFromFile(filePath, baseDir string, appConfig *fleet.EnrichedAppConfig) (*GitOps, error) {
b, err := os.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("failed to read file: %s: %w", filePath, err)
@@ -96,18 +96,16 @@ func GitOpsFromFile(filePath, baseDir string) (*GitOps, error) {
// Figure out if this is an org or team settings file
teamRaw, teamOk := top["name"]
teamSettingsRaw, teamSettingsOk := top["team_settings"]
- teamSoftware, teamSoftwareOk := top["software"]
orgSettingsRaw, orgOk := top["org_settings"]
if orgOk {
- if teamOk || teamSettingsOk || teamSoftwareOk {
- multiError = multierror.Append(multiError, errors.New("'org_settings' cannot be used with 'name', 'team_settings' or 'software'"))
+ if teamOk || teamSettingsOk {
+ multiError = multierror.Append(multiError, errors.New("'org_settings' cannot be used with 'name', 'team_settings'"))
} else {
multiError = parseOrgSettings(orgSettingsRaw, result, baseDir, multiError)
}
} else if teamOk && teamSettingsOk {
multiError = parseName(teamRaw, result, multiError)
multiError = parseTeamSettings(teamSettingsRaw, result, baseDir, multiError)
- multiError = parseSoftware(teamSoftware, result, baseDir, multiError)
} else {
multiError = multierror.Append(multiError, errors.New("either 'org_settings' or 'name' and 'team_settings' is required"))
}
@@ -118,6 +116,10 @@ func GitOpsFromFile(filePath, baseDir string) (*GitOps, error) {
multiError = parsePolicies(top, result, baseDir, multiError)
multiError = parseQueries(top, result, baseDir, multiError)
+ if appConfig != nil && appConfig.License.IsPremium() {
+ multiError = parseSoftware(top, result, baseDir, multiError)
+ }
+
return result, multiError.ErrorOrNil()
}
@@ -523,11 +525,15 @@ func parseQueries(top map[string]json.RawMessage, result *GitOps, baseDir string
return multiError
}
-func parseSoftware(softwareRaw json.RawMessage, result *GitOps, baseDir string, multiError *multierror.Error) *multierror.Error {
- var software fleet.TeamSpecSoftware
+func parseSoftware(top map[string]json.RawMessage, result *GitOps, baseDir string, multiError *multierror.Error) *multierror.Error {
+ softwareRaw, ok := top["software"]
+ if !ok {
+ return multierror.Append(multiError, errors.New("'software' is required"))
+ }
+ var software fleet.SoftwareSpec
if len(softwareRaw) > 0 {
if err := json.Unmarshal(softwareRaw, &software); err != nil {
- return multierror.Append(multiError, fmt.Errorf("failed to unmarshall software: %v", err))
+ return multierror.Append(multiError, fmt.Errorf("failed to unmarshall softwarespec: %v", err))
}
}
if software.AppStoreApps.Set {
diff --git a/pkg/spec/gitops_test.go b/pkg/spec/gitops_test.go
index 644c3e453e..58e12553b9 100644
--- a/pkg/spec/gitops_test.go
+++ b/pkg/spec/gitops_test.go
@@ -53,7 +53,7 @@ func createTempFile(t *testing.T, pattern, contents string) (filePath string, ba
func gitOpsFromString(t *testing.T, s string) (*GitOps, error) {
path, basePath := createTempFile(t, "", s)
- return GitOpsFromFile(path, basePath)
+ return GitOpsFromFile(path, basePath, nil)
}
func TestValidGitOpsYaml(t *testing.T) {
@@ -108,7 +108,7 @@ func TestValidGitOpsYaml(t *testing.T) {
t.Parallel()
}
- gitops, err := GitOpsFromFile(test.filePath, "./testdata")
+ gitops, err := GitOpsFromFile(test.filePath, "./testdata", nil)
require.NoError(t, err)
if test.isTeam {
@@ -336,20 +336,20 @@ func TestMixingGlobalAndTeamConfig(t *testing.T) {
config := getGlobalConfig(nil)
config += "name: TeamName\n"
_, err := gitOpsFromString(t, config)
- assert.ErrorContains(t, err, "'org_settings' cannot be used with 'name', 'team_settings' or 'software'")
+ assert.ErrorContains(t, err, "'org_settings' cannot be used with 'name', 'team_settings'")
// Mixing org_settings and team_settings
config = getGlobalConfig(nil)
config += "team_settings:\n secrets: []\n"
_, err = gitOpsFromString(t, config)
- assert.ErrorContains(t, err, "'org_settings' cannot be used with 'name', 'team_settings' or 'software'")
+ assert.ErrorContains(t, err, "'org_settings' cannot be used with 'name', 'team_settings'")
// Mixing org_settings and team name and team_settings
config = getGlobalConfig(nil)
config += "name: TeamName\n"
config += "team_settings:\n secrets: []\n"
_, err = gitOpsFromString(t, config)
- assert.ErrorContains(t, err, "'org_settings' cannot be used with 'name', 'team_settings' or 'software'")
+ assert.ErrorContains(t, err, "'org_settings' cannot be used with 'name', 'team_settings'")
}
func TestInvalidGitOpsYaml(t *testing.T) {
@@ -696,7 +696,7 @@ func TestGitOpsPaths(t *testing.T) {
err = os.WriteFile(mainTmpFile.Name(), []byte(config), 0o644)
require.NoError(t, err)
- _, err = GitOpsFromFile(mainTmpFile.Name(), dir)
+ _, err = GitOpsFromFile(mainTmpFile.Name(), dir, nil)
assert.NoError(t, err)
// Test a bad path
@@ -709,7 +709,7 @@ func TestGitOpsPaths(t *testing.T) {
err = os.WriteFile(mainTmpFile.Name(), []byte(config), 0o644)
require.NoError(t, err)
- _, err = GitOpsFromFile(mainTmpFile.Name(), dir)
+ _, err = GitOpsFromFile(mainTmpFile.Name(), dir, nil)
assert.ErrorContains(t, err, "no such file or directory")
// Test a bad file -- cannot be unmarshalled
@@ -744,7 +744,7 @@ func TestGitOpsPaths(t *testing.T) {
}
err = os.WriteFile(mainTmpFile.Name(), []byte(config), 0o644)
require.NoError(t, err)
- _, err = GitOpsFromFile(mainTmpFile.Name(), dir)
+ _, err = GitOpsFromFile(mainTmpFile.Name(), dir, nil)
assert.ErrorContains(t, err, "nested paths are not supported")
},
)
diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go
index d760f8b1d3..10bafd76da 100644
--- a/pkg/spec/spec.go
+++ b/pkg/spec/spec.go
@@ -26,6 +26,7 @@ type Group struct {
Packs []*fleet.PackSpec
Labels []*fleet.LabelSpec
Policies []*fleet.PolicySpec
+ Software []*fleet.SoftwarePackageSpec
// This needs to be interface{} to allow for the patch logic. Otherwise we send a request that looks to the
// server like the user explicitly set the zero values.
AppConfig interface{}
diff --git a/server/fleet/software_installer.go b/server/fleet/software_installer.go
index d542840989..70a8f2ca36 100644
--- a/server/fleet/software_installer.go
+++ b/server/fleet/software_installer.go
@@ -9,6 +9,7 @@ import (
"strings"
"time"
+ "github.com/fleetdm/fleet/v4/pkg/optjson"
"github.com/fleetdm/fleet/v4/server/ptr"
)
@@ -334,6 +335,19 @@ type SoftwarePackageOrApp struct {
LastInstall *HostSoftwareInstall `json:"last_install"`
}
+type SoftwarePackageSpec struct {
+ URL string `json:"url"`
+ SelfService bool `json:"self_service"`
+ PreInstallQuery TeamSpecSoftwareAsset `json:"pre_install_query"`
+ InstallScript TeamSpecSoftwareAsset `json:"install_script"`
+ PostInstallScript TeamSpecSoftwareAsset `json:"post_install_script"`
+}
+
+type SoftwareSpec struct {
+ Packages optjson.Slice[SoftwarePackageSpec] `json:"packages,omitempty"`
+ AppStoreApps optjson.Slice[TeamSpecAppStoreApp] `json:"app_store_apps,omitempty"`
+}
+
// HostSoftwareInstall represents installation of software on a host from a
// Fleet software installer.
type HostSoftwareInstall struct {
diff --git a/server/fleet/teams.go b/server/fleet/teams.go
index d5eaa71f8b..fa9734f6c1 100644
--- a/server/fleet/teams.go
+++ b/server/fleet/teams.go
@@ -155,7 +155,7 @@ type TeamConfig struct {
Features Features `json:"features"`
MDM TeamMDM `json:"mdm"`
Scripts optjson.Slice[string] `json:"scripts,omitempty"`
- Software *TeamSpecSoftware `json:"software,omitempty"`
+ Software *SoftwareSpec `json:"software,omitempty"`
}
type TeamWebhookSettings struct {
@@ -168,23 +168,10 @@ type TeamSpecSoftwareAsset struct {
Path string `json:"path"`
}
-type TeamSpecSoftware struct {
- Packages optjson.Slice[TeamSpecSoftwarePackage] `json:"packages,omitempty"`
- AppStoreApps optjson.Slice[TeamSpecAppStoreApp] `json:"app_store_apps,omitempty"`
-}
-
type TeamSpecAppStoreApp struct {
AppStoreID string `json:"app_store_id"`
}
-type TeamSpecSoftwarePackage struct {
- URL string `json:"url"`
- SelfService bool `json:"self_service"`
- PreInstallQuery TeamSpecSoftwareAsset `json:"pre_install_query"`
- InstallScript TeamSpecSoftwareAsset `json:"install_script"`
- PostInstallScript TeamSpecSoftwareAsset `json:"post_install_script"`
-}
-
type TeamMDM struct {
EnableDiskEncryption bool `json:"enable_disk_encryption"`
MacOSUpdates AppleOSUpdateSettings `json:"macos_updates"`
@@ -450,7 +437,7 @@ type TeamSpec struct {
Scripts optjson.Slice[string] `json:"scripts"`
WebhookSettings TeamSpecWebhookSettings `json:"webhook_settings"`
Integrations TeamSpecIntegrations `json:"integrations"`
- Software *TeamSpecSoftware `json:"software,omitempty"`
+ Software *SoftwareSpec `json:"software,omitempty"`
}
type TeamSpecWebhookSettings struct {
diff --git a/server/service/client.go b/server/service/client.go
index 09ab08e031..740ac19578 100644
--- a/server/service/client.go
+++ b/server/service/client.go
@@ -394,6 +394,7 @@ func (c *Client) ApplyGroup(
specs *spec.Group,
baseDir string,
logf func(format string, args ...interface{}),
+ appconfig *fleet.EnrichedAppConfig,
opts fleet.ApplyClientSpecOptions,
) (map[string]uint, error) {
logfn := func(format string, args ...interface{}) {
@@ -610,90 +611,10 @@ func (c *Client) ApplyGroup(
tmSoftwarePackages := extractTmSpecsSoftwarePackages(specs.Teams)
tmSoftwarePackagesPayloads := make(map[string][]fleet.SoftwareInstallerPayload, len(tmScripts))
for tmName, software := range tmSoftwarePackages {
- softwarePayloads := make([]fleet.SoftwareInstallerPayload, len(software))
- for i, si := range software {
- var qc string
- var err error
- if si.PreInstallQuery.Path != "" {
- queryFile := resolveApplyRelativePath(baseDir, si.PreInstallQuery.Path)
- rawSpec, err := os.ReadFile(queryFile)
- if err != nil {
- return nil, fmt.Errorf("reading pre-install query: %w", err)
- }
-
- rawSpecExpanded, err := spec.ExpandEnvBytes(rawSpec)
- if err != nil {
- return nil, fmt.Errorf("Couldn't exit software (%s). Unable to expand environment variable in YAML file %s: %w", si.URL, queryFile, err)
- }
-
- var top any
-
- if err := yaml.Unmarshal(rawSpecExpanded, &top); err != nil {
- return nil, fmt.Errorf("Couldn't exit software (%s). Unable to expand environment variable in YAML file %s: %w", si.URL, queryFile, err)
- }
-
- if _, ok := top.(map[any]any); ok {
- // Old apply format
- group, err := spec.GroupFromBytes(rawSpecExpanded)
- if err != nil {
- return nil, fmt.Errorf("Couldn't edit software (%s). Unable to parse pre-install apply format query YAML file %s: %w", si.URL, queryFile, err)
- }
-
- if len(group.Queries) > 1 {
- return nil, fmt.Errorf("Couldn't edit software (%s). Pre-install query YAML file %s should have only one query.", si.URL, queryFile)
- }
-
- if len(group.Queries) == 0 {
- return nil, fmt.Errorf("Couldn't edit software (%s). Pre-install query YAML file %s doesn't have a query defined.", si.URL, queryFile)
- }
-
- qc = group.Queries[0].Query
- } else {
- // Gitops format
- var querySpecs []fleet.QuerySpec
- if err := yaml.Unmarshal(rawSpecExpanded, &querySpecs); err != nil {
- return nil, fmt.Errorf("Couldn't edit software (%s). Unable to parse pre-install query YAML file %s: %w", si.URL, queryFile, err)
- }
-
- if len(querySpecs) > 1 {
- return nil, fmt.Errorf("Couldn't edit software (%s). Pre-install query YAML file %s should have only one query.", si.URL, queryFile)
- }
-
- if len(querySpecs) == 0 {
- return nil, fmt.Errorf("Couldn't edit software (%s). Pre-install query YAML file %s doesn't have a query defined.", si.URL, queryFile)
- }
-
- qc = querySpecs[0].Query
- }
- }
-
- var ic []byte
- if si.InstallScript.Path != "" {
- installScriptFile := resolveApplyRelativePath(baseDir, si.InstallScript.Path)
- ic, err = os.ReadFile(installScriptFile)
- if err != nil {
- return nil, fmt.Errorf("Couldn't edit software (%s). Unable to read install script file %s: %w", si.URL, si.InstallScript.Path, err)
- }
- }
-
- var pc []byte
- if si.PostInstallScript.Path != "" {
- postInstallScriptFile := resolveApplyRelativePath(baseDir, si.PostInstallScript.Path)
- pc, err = os.ReadFile(postInstallScriptFile)
- if err != nil {
- return nil, fmt.Errorf("Couldn't edit software (%s). Unable to read post-install script file %s: %w", si.URL, si.PostInstallScript.Path, err)
- }
- }
-
- softwarePayloads[i] = fleet.SoftwareInstallerPayload{
- URL: si.URL,
- SelfService: si.SelfService,
- PreInstallQuery: qc,
- InstallScript: string(ic),
- PostInstallScript: string(pc),
- }
+ softwarePayloads, err := buildSoftwarePackagesPayload(baseDir, software)
+ if err != nil {
+ return nil, fmt.Errorf("applying software installers for team %q: %w", tmName, err)
}
-
tmSoftwarePackagesPayloads[tmName] = softwarePayloads
}
@@ -810,6 +731,95 @@ func (c *Client) ApplyGroup(
return teamIDsByName, nil
}
+func buildSoftwarePackagesPayload(baseDir string, specs []fleet.SoftwarePackageSpec) ([]fleet.SoftwareInstallerPayload, error) {
+ softwarePayloads := make([]fleet.SoftwareInstallerPayload, len(specs))
+ for i, si := range specs {
+ var qc string
+ var err error
+ if si.PreInstallQuery.Path != "" {
+ queryFile := resolveApplyRelativePath(baseDir, si.PreInstallQuery.Path)
+ rawSpec, err := os.ReadFile(queryFile)
+ if err != nil {
+ return nil, fmt.Errorf("reading pre-install query: %w", err)
+ }
+
+ rawSpecExpanded, err := spec.ExpandEnvBytes(rawSpec)
+ if err != nil {
+ return nil, fmt.Errorf("Couldn't exit software (%s). Unable to expand environment variable in YAML file %s: %w", si.URL, queryFile, err)
+ }
+
+ var top any
+
+ if err := yaml.Unmarshal(rawSpecExpanded, &top); err != nil {
+ return nil, fmt.Errorf("Couldn't exit software (%s). Unable to expand environment variable in YAML file %s: %w", si.URL, queryFile, err)
+ }
+
+ if _, ok := top.(map[any]any); ok {
+ // Old apply format
+ group, err := spec.GroupFromBytes(rawSpecExpanded)
+ if err != nil {
+ return nil, fmt.Errorf("Couldn't edit software (%s). Unable to parse pre-install apply format query YAML file %s: %w", si.URL, queryFile, err)
+ }
+
+ if len(group.Queries) > 1 {
+ return nil, fmt.Errorf("Couldn't edit software (%s). Pre-install query YAML file %s should have only one query.", si.URL, queryFile)
+ }
+
+ if len(group.Queries) == 0 {
+ return nil, fmt.Errorf("Couldn't edit software (%s). Pre-install query YAML file %s doesn't have a query defined.", si.URL, queryFile)
+ }
+
+ qc = group.Queries[0].Query
+ } else {
+ // Gitops format
+ var querySpecs []fleet.QuerySpec
+ if err := yaml.Unmarshal(rawSpecExpanded, &querySpecs); err != nil {
+ return nil, fmt.Errorf("Couldn't edit software (%s). Unable to parse pre-install query YAML file %s: %w", si.URL, queryFile, err)
+ }
+
+ if len(querySpecs) > 1 {
+ return nil, fmt.Errorf("Couldn't edit software (%s). Pre-install query YAML file %s should have only one query.", si.URL, queryFile)
+ }
+
+ if len(querySpecs) == 0 {
+ return nil, fmt.Errorf("Couldn't edit software (%s). Pre-install query YAML file %s doesn't have a query defined.", si.URL, queryFile)
+ }
+
+ qc = querySpecs[0].Query
+ }
+ }
+
+ var ic []byte
+ if si.InstallScript.Path != "" {
+ installScriptFile := resolveApplyRelativePath(baseDir, si.InstallScript.Path)
+ ic, err = os.ReadFile(installScriptFile)
+ if err != nil {
+ return nil, fmt.Errorf("Couldn't edit software (%s). Unable to read install script file %s: %w", si.URL, si.InstallScript.Path, err)
+ }
+ }
+
+ var pc []byte
+ if si.PostInstallScript.Path != "" {
+ postInstallScriptFile := resolveApplyRelativePath(baseDir, si.PostInstallScript.Path)
+ pc, err = os.ReadFile(postInstallScriptFile)
+ if err != nil {
+ return nil, fmt.Errorf("Couldn't edit software (%s). Unable to read post-install script file %s: %w", si.URL, si.PostInstallScript.Path, err)
+ }
+ }
+
+ softwarePayloads[i] = fleet.SoftwareInstallerPayload{
+ URL: si.URL,
+ SelfService: si.SelfService,
+ PreInstallQuery: qc,
+ InstallScript: string(ic),
+ PostInstallScript: string(pc),
+ }
+
+ }
+
+ return softwarePayloads, nil
+}
+
func extractAppCfgMacOSSetup(appCfg any) *fleet.MacOSSetup {
asMap, ok := appCfg.(map[string]interface{})
if !ok {
@@ -1045,8 +1055,8 @@ func extractTmSpecsMDMCustomSettings(tmSpecs []json.RawMessage) map[string]profi
return m
}
-func extractTmSpecsSoftwarePackages(tmSpecs []json.RawMessage) map[string][]fleet.TeamSpecSoftwarePackage {
- var m map[string][]fleet.TeamSpecSoftwarePackage
+func extractTmSpecsSoftwarePackages(tmSpecs []json.RawMessage) map[string][]fleet.SoftwarePackageSpec {
+ var m map[string][]fleet.SoftwarePackageSpec
for _, tm := range tmSpecs {
var spec struct {
Name string `json:"name"`
@@ -1059,10 +1069,10 @@ func extractTmSpecsSoftwarePackages(tmSpecs []json.RawMessage) map[string][]flee
spec.Name = norm.NFC.String(spec.Name)
if spec.Name != "" && len(spec.Software) > 0 {
if m == nil {
- m = make(map[string][]fleet.TeamSpecSoftwarePackage)
+ m = make(map[string][]fleet.SoftwarePackageSpec)
}
- var software fleet.TeamSpecSoftware
- var packages []fleet.TeamSpecSoftwarePackage
+ var software fleet.SoftwareSpec
+ var packages []fleet.SoftwarePackageSpec
if err := json.Unmarshal(spec.Software, &software); err != nil {
// ignore, will fail in apply team specs call
continue
@@ -1070,7 +1080,7 @@ func extractTmSpecsSoftwarePackages(tmSpecs []json.RawMessage) map[string][]flee
if !software.Packages.Valid {
// to be consistent with the AppConfig custom settings, set it to an
// empty slice if the provided custom settings are present but empty.
- packages = []fleet.TeamSpecSoftwarePackage{}
+ packages = []fleet.SoftwarePackageSpec{}
} else {
packages = software.Packages.Value
}
@@ -1096,7 +1106,7 @@ func extractTmSpecsSoftwareApps(tmSpecs []json.RawMessage) map[string][]fleet.Te
if m == nil {
m = make(map[string][]fleet.TeamSpecAppStoreApp)
}
- var software fleet.TeamSpecSoftware
+ var software fleet.SoftwareSpec
var apps []fleet.TeamSpecAppStoreApp
if err := json.Unmarshal(spec.Software, &software); err != nil {
// ignore, will fail in apply team specs call
@@ -1255,6 +1265,8 @@ func (c *Client) DoGitOps(
}
}
group.AppConfig.(map[string]interface{})["scripts"] = scripts
+
+ group.Software = config.Software.Packages
} else {
team = make(map[string]interface{})
team["name"] = *config.TeamName
@@ -1413,7 +1425,7 @@ func (c *Client) DoGitOps(
}
// Apply org settings, scripts, enroll secrets, and controls
- teamIDsByName, err := c.ApplyGroup(ctx, &group, baseDir, logf, fleet.ApplyClientSpecOptions{
+ teamIDsByName, err := c.ApplyGroup(ctx, &group, baseDir, logf, appConfig, fleet.ApplyClientSpecOptions{
ApplySpecOptions: fleet.ApplySpecOptions{
DryRun: dryRun,
},
@@ -1449,9 +1461,39 @@ func (c *Client) DoGitOps(
return nil, err
}
+ err = c.doGitOpsNoTeamSoftware(group, baseDir, appConfig, logFn, dryRun)
+ if err != nil {
+ return nil, err
+ }
+
return teamAssumptions, nil
}
+func (c *Client) doGitOpsNoTeamSoftware(specs spec.Group, baseDir string, appconfig *fleet.EnrichedAppConfig, logFn func(format string, args ...interface{}), dryRun bool) error {
+ if len(specs.Teams) == 0 && appconfig != nil && appconfig.License.IsPremium() {
+ packages := make([]fleet.SoftwarePackageSpec, 0, len(specs.Software))
+ for _, software := range specs.Software {
+ if software != nil {
+ packages = append(packages, *software)
+ }
+ }
+ payload, err := buildSoftwarePackagesPayload(baseDir, packages)
+ if err != nil {
+ return fmt.Errorf("applying software installers: %w", err)
+ }
+ if err := c.ApplyNoTeamSoftwareInstallers(payload, fleet.ApplySpecOptions{DryRun: dryRun}); err != nil {
+ return fmt.Errorf("applying software installers: %w", err)
+ }
+
+ if dryRun {
+ logFn("[+] would've applied 'No Team' software installers\n")
+ } else {
+ logFn("[+] applied 'No Team' software installers\n")
+ }
+ }
+ return nil
+}
+
func (c *Client) doGitOpsPolicies(config *spec.GitOps, logFn func(format string, args ...interface{}), dryRun bool) error {
// Get the ids and names of current policies to figure out which ones to delete
policies, err := c.GetPolicies(config.TeamID)
diff --git a/server/service/client_software.go b/server/service/client_software.go
index 22c602e96c..d08faee404 100644
--- a/server/service/client_software.go
+++ b/server/service/client_software.go
@@ -1,6 +1,8 @@
package service
import (
+ "net/url"
+
"github.com/fleetdm/fleet/v4/server/fleet"
)
@@ -25,3 +27,12 @@ func (c *Client) ListSoftwareTitles(query string) ([]fleet.SoftwareTitleListResu
}
return responseBody.SoftwareTitles, nil
}
+
+func (c *Client) ApplyNoTeamSoftwareInstallers(softwareInstallers []fleet.SoftwareInstallerPayload, opts fleet.ApplySpecOptions) error {
+ verb, path := "POST", "/api/latest/fleet/software/batch"
+ query, err := url.ParseQuery(opts.RawQuery())
+ if err != nil {
+ return err
+ }
+ return c.authenticatedRequestWithQuery(map[string]interface{}{"software": softwareInstallers}, verb, path, nil, query.Encode())
+}
diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go
index 438138dc67..79e61b96d1 100644
--- a/server/service/integration_enterprise_test.go
+++ b/server/service/integration_enterprise_test.go
@@ -10161,7 +10161,7 @@ func (s *integrationEnterpriseTestSuite) TestApplyTeamsSoftwareConfig() {
}
s.Do("POST", "/api/latest/fleet/spec/teams", teamSpecs, http.StatusOK)
- wantSoftwarePackages := []fleet.TeamSpecSoftwarePackage{
+ wantSoftwarePackages := []fleet.SoftwarePackageSpec{
{
URL: "http://foo.com",
SelfService: true,
@@ -10320,9 +10320,6 @@ func (s *integrationEnterpriseTestSuite) TestApplyTeamsSoftwareConfig() {
func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallers() {
t := s.T()
- // a team name is required (we don't allow installers for "no team")
- s.Do("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{}, http.StatusBadRequest)
-
// non-existent team
s.Do("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{}, http.StatusNotFound, "team_name", "foo")
@@ -10397,6 +10394,42 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallers() {
s.DoJSON("GET", "/api/v1/fleet/software/titles", nil, http.StatusOK, &titlesResp, "available_for_install", "true", "team_id", strconv.Itoa(int(tm.ID)))
require.Equal(t, 0, titlesResp.Count)
require.Len(t, titlesResp.SoftwareTitles, 0)
+
+ //////////////////////////
+ // Do a request with a valid URL with no team
+ //////////////////////////
+ softwareToInstall = []fleet.SoftwareInstallerPayload{
+ {URL: srv.URL},
+ }
+ s.Do("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusNoContent)
+
+ // check the application status on team 0
+ titlesResp = listSoftwareTitlesResponse{}
+ s.DoJSON("GET", "/api/v1/fleet/software/titles", nil, http.StatusOK, &titlesResp, "available_for_install", "true", "team_id", strconv.Itoa(int(0)))
+ require.Equal(t, 1, titlesResp.Count)
+ require.Len(t, titlesResp.SoftwareTitles, 1)
+
+ // same payload doesn't modify anything
+ s.Do("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusNoContent)
+ newTitlesResp = listSoftwareTitlesResponse{}
+ s.DoJSON("GET", "/api/v1/fleet/software/titles", nil, http.StatusOK, &newTitlesResp, "available_for_install", "true", "team_id", strconv.Itoa(int(0)))
+ require.Equal(t, titlesResp, newTitlesResp)
+
+ // setting self-service to true updates the software title metadata
+ softwareToInstall[0].SelfService = true
+ s.Do("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusNoContent)
+ newTitlesResp = listSoftwareTitlesResponse{}
+ s.DoJSON("GET", "/api/v1/fleet/software/titles", nil, http.StatusOK, &newTitlesResp, "available_for_install", "true", "team_id", strconv.Itoa(int(0)))
+ titlesResp.SoftwareTitles[0].SoftwarePackage.SelfService = ptr.Bool(true)
+ require.Equal(t, titlesResp, newTitlesResp)
+
+ // empty payload cleans the software items
+ softwareToInstall = []fleet.SoftwareInstallerPayload{}
+ s.Do("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusNoContent)
+ titlesResp = listSoftwareTitlesResponse{}
+ s.DoJSON("GET", "/api/v1/fleet/software/titles", nil, http.StatusOK, &titlesResp, "available_for_install", "true", "team_id", strconv.Itoa(int(0)))
+ require.Equal(t, 0, titlesResp.Count)
+ require.Len(t, titlesResp.SoftwareTitles, 0)
}
func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerNewInstallRequestPlatformValidation() {
diff --git a/server/service/software_installers.go b/server/service/software_installers.go
index 5de426c2d6..0cdff35c2a 100644
--- a/server/service/software_installers.go
+++ b/server/service/software_installers.go
@@ -318,7 +318,7 @@ func (svc *Service) GetSoftwareInstallResults(ctx context.Context, resultUUID st
////////////////////////////////////////////////////////////////////////////////
type batchSetSoftwareInstallersRequest struct {
- TeamName string `json:"-" query:"team_name"`
+ TeamName string `json:"-" query:"team_name,optional"`
DryRun bool `json:"-" query:"dry_run,optional"` // if true, apply validation but do not save changes
Software []fleet.SoftwareInstallerPayload `json:"software"`
}
From 5730c1c3c855ff8837b7475cd5b5c02bd8b122a8 Mon Sep 17 00:00:00 2001
From: jacobshandling <61553566+jacobshandling@users.noreply.github.com>
Date: Mon, 5 Aug 2024 12:57:53 -0700
Subject: [PATCH 041/612] =?UTF-8?q?UI=20=E2=80=93=20Render=20"vulns=20not?=
=?UTF-8?q?=20supported"=20empty=20state=20for=20iphone/ipad=20host=20soft?=
=?UTF-8?q?ware=20filtered=20by=20vuln=20(#21029)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## #21027
- [x] Manual QA for all new/changed functionality
Co-authored-by: Jacob Shandling
---
.../HostDetailsPage/HostDetailsPage.tsx | 3 ++
.../details/cards/Software/HostSoftware.tsx | 31 ++++++++++++++++---
.../HostSoftwareTable/HostSoftwareTable.tsx | 24 ++++++++++++--
3 files changed, 51 insertions(+), 7 deletions(-)
diff --git a/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx b/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx
index 5f4a57a356..cf63e55962 100644
--- a/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx
+++ b/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx
@@ -53,6 +53,8 @@ import {
HOST_OSQUERY_DATA,
} from "utilities/constants";
+import { Platform } from "interfaces/platform";
+
import Spinner from "components/Spinner";
import TabsWrapper from "components/TabsWrapper";
import MainContent from "components/MainContent";
@@ -921,6 +923,7 @@ const HostDetailsPage = ({
{
interface IHostSoftwareProps {
/** This is the host id or the device token */
id: number | string;
+ /** The host's platform. Only used for the host details page, so can be omited on the Device User Page. */
+ platform?: Platform;
softwareUpdatedAt?: string;
hostCanInstallSoftware: boolean;
router: InjectedRouter;
@@ -82,6 +85,7 @@ export const parseHostSoftwareQueryParams = (queryParams: {
const HostSoftware = ({
id,
+ platform,
softwareUpdatedAt,
hostCanInstallSoftware,
router,
@@ -93,6 +97,8 @@ const HostSoftware = ({
isMyDevicePage = false,
}: IHostSoftwareProps) => {
const { renderFlash } = useContext(NotificationContext);
+ const vulnFilterAndNotSupported =
+ ["ios", "ipados"].includes(platform ?? "") && queryParams.vulnerable;
const {
isGlobalAdmin,
isGlobalMaintainer,
@@ -129,7 +135,8 @@ const HostSoftware = ({
},
{
...DEFAULT_USE_QUERY_OPTIONS,
- enabled: isSoftwareEnabled && !isMyDevicePage, // if disabled, we'll always show a generic "No software detected" message
+ enabled:
+ isSoftwareEnabled && !isMyDevicePage && !vulnFilterAndNotSupported,
keepPreviousData: true,
staleTime: 7000,
}
@@ -158,7 +165,7 @@ const HostSoftware = ({
({ queryKey }) => deviceAPI.getDeviceSoftware(queryKey[0]),
{
...DEFAULT_USE_QUERY_OPTIONS,
- enabled: isSoftwareEnabled && isMyDevicePage, // if disabled, we'll always show a generic "No software detected" message
+ enabled: isSoftwareEnabled && isMyDevicePage, // if disabled, we'll always show a generic "No software detected" message. No DUP for iPad/iPhone
keepPreviousData: true,
staleTime: 7000,
}
@@ -251,7 +258,10 @@ const HostSoftware = ({
if (isLoading) {
return ;
}
-
+ // will never be the case - to handle `platform` typing discrepancy with DeviceUserPage
+ if (!platform) {
+ return null;
+ }
return (
<>
{isError && }
@@ -260,7 +270,20 @@ const HostSoftware = ({
isLoading={
isMyDevicePage ? deviceSoftwareFetching : hostSoftwareFetching
}
- data={data}
+ // this could be cleaner, however, we are going to revert this commit anyway once vulns are
+ // supported for iPad/iPhone, by the end of next sprint
+ data={
+ vulnFilterAndNotSupported
+ ? ({
+ count: 0,
+ meta: {
+ has_next_results: false,
+ has_previous_results: false,
+ },
+ } as IGetHostSoftwareResponse)
+ : data
+ } // eshould be mpty for iPad/iPhone since API call is disabled, but to be sure to trigger empty state
+ platform={platform}
router={router}
tableConfig={tableConfig}
sortHeader={queryParams.order_key}
diff --git a/frontend/pages/hosts/details/cards/Software/HostSoftwareTable/HostSoftwareTable.tsx b/frontend/pages/hosts/details/cards/Software/HostSoftwareTable/HostSoftwareTable.tsx
index 6ac5f7a4a9..cf37cd0065 100644
--- a/frontend/pages/hosts/details/cards/Software/HostSoftwareTable/HostSoftwareTable.tsx
+++ b/frontend/pages/hosts/details/cards/Software/HostSoftwareTable/HostSoftwareTable.tsx
@@ -8,6 +8,12 @@ import { QueryParams } from "utilities/url";
import { ISoftwareDropdownFilterVal } from "pages/SoftwarePage/SoftwareTitles/SoftwareTable/helpers";
+import {
+ ApplePlatform,
+ APPLE_PLATFORM_DISPLAY_NAMES,
+ Platform,
+} from "interfaces/platform";
+
import TableContainer from "components/TableContainer";
import { ITableQueryData } from "components/TableContainer/TableContainer";
// @ts-ignore
@@ -15,6 +21,7 @@ import Dropdown from "components/forms/fields/Dropdown";
import EmptySoftwareTable from "pages/SoftwarePage/components/EmptySoftwareTable";
import TableCount from "components/TableContainer/TableCount";
+import { VulnsNotSupported } from "pages/SoftwarePage/components/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable";
const DEFAULT_PAGE_SIZE = 20;
@@ -45,6 +52,7 @@ export const DROPDOWN_OPTIONS = [
interface IHostSoftwareTableProps {
tableConfig: any; // TODO: type
data?: IGetHostSoftwareResponse | IGetDeviceSoftwareResponse;
+ platform: Platform;
isLoading: boolean;
router: InjectedRouter;
sortHeader: string;
@@ -60,6 +68,7 @@ interface IHostSoftwareTableProps {
const HostSoftwareTable = ({
tableConfig,
data,
+ platform,
isLoading,
router,
sortHeader,
@@ -167,7 +176,7 @@ const HostSoftwareTable = ({
[determineQueryParamChange, pagePath, generateNewQueryParams, router]
);
- const count = data?.count || data?.software.length || 0;
+ const count = data?.count || data?.software?.length || 0;
const isSoftwareNotDetected = count === 0 && searchQuery === "";
const memoizedSoftwareCount = useCallback(() => {
@@ -179,8 +188,17 @@ const HostSoftwareTable = ({
}, [count, isSoftwareNotDetected]);
const memoizedEmptyComponent = useCallback(() => {
- return ;
- }, [searchQuery]);
+ const vulnFilterAndNotSupported =
+ ["ios", "ipados"].includes(platform) &&
+ hostSoftwareFilter === "vulnerableSoftware";
+ return vulnFilterAndNotSupported ? (
+
+ ) : (
+
+ );
+ }, [hostSoftwareFilter, platform, searchQuery]);
return (
From afc292666c1703f4e0c6a5c4be7a44b98aeb17d1 Mon Sep 17 00:00:00 2001
From: RachelElysia <71795832+RachelElysia@users.noreply.github.com>
Date: Mon, 5 Aug 2024 16:54:16 -0400
Subject: [PATCH 042/612] Fleet UI: Update host details > software > status
"Installed" tooltip (#21052)
---
frontend/pages/SoftwarePage/SoftwarePage.tsx | 2 +-
.../cards/Software/InstallStatusCell/InstallStatusCell.tsx | 5 +++--
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/frontend/pages/SoftwarePage/SoftwarePage.tsx b/frontend/pages/SoftwarePage/SoftwarePage.tsx
index 1cf063cc2d..34aeb03cc5 100644
--- a/frontend/pages/SoftwarePage/SoftwarePage.tsx
+++ b/frontend/pages/SoftwarePage/SoftwarePage.tsx
@@ -348,7 +348,7 @@ const SoftwarePage = ({ children, router, location }: ISoftwarePageProps) => {
const renderHeaderDescription = () => {
return (
- Manage software and search for installed software, OS and
+ Manage software and search for installed software, OS, and
vulnerabilities {isAllTeamsSelected ? "for all hosts" : "on this team"}.
);
diff --git a/frontend/pages/hosts/details/cards/Software/InstallStatusCell/InstallStatusCell.tsx b/frontend/pages/hosts/details/cards/Software/InstallStatusCell/InstallStatusCell.tsx
index 38a3a42df3..7e8ea90bc1 100644
--- a/frontend/pages/hosts/details/cards/Software/InstallStatusCell/InstallStatusCell.tsx
+++ b/frontend/pages/hosts/details/cards/Software/InstallStatusCell/InstallStatusCell.tsx
@@ -38,8 +38,9 @@ export const INSTALL_STATUS_DISPLAY_OPTIONS: Record<
displayText: "Installed",
tooltip: ({ lastInstalledAt: lastInstall }) => (
<>
- Fleet installed software on these hosts. (
- {dateAgo(lastInstall as string)})
+ Fleet installed software on this host
+ {dateAgo(lastInstall as string)}). Currently, if the software is
+ deleted, the “Installed” status won’t be updated.
>
),
},
From 379aa9e6f742daca8d58c3bc1661cdfd170fe2fb Mon Sep 17 00:00:00 2001
From: Roberto Dip
Date: Mon, 5 Aug 2024 18:14:12 -0300
Subject: [PATCH 043/612] add Escrow Buddy to TUF.md after the push to `stable`
(#21066)
---
orbit/TUF.md | 2 ++
tools/tuf/status/tuf-status.go | 7 +++++--
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/orbit/TUF.md b/orbit/TUF.md
index 53be1e46e8..634ee62b5d 100644
--- a/orbit/TUF.md
+++ b/orbit/TUF.md
@@ -12,6 +12,7 @@ Following are the currently deployed versions of fleetd components on the `stabl
| osqueryd | 5.12.1 | 5.12.1 | 5.12.1 | 5.12.1 |
| nudge | 1.1.10.81462 | - | - | - |
| swiftDialog | 2.1.0 | - | - | - |
+| escrowBuddy | 1.0.0 | - | - | - |
## `edge`
@@ -22,3 +23,4 @@ Following are the currently deployed versions of fleetd components on the `stabl
| osqueryd | 5.13.0 | 5.13.0 | 5.13.0 | 5.13.0 |
| nudge | - | - | - | - |
| swiftDialog | - | - | - | - |
+| escrowBuddy | - | - | - | - |
diff --git a/tools/tuf/status/tuf-status.go b/tools/tuf/status/tuf-status.go
index 6f8aa6fd36..e62dc14f12 100644
--- a/tools/tuf/status/tuf-status.go
+++ b/tools/tuf/status/tuf-status.go
@@ -186,6 +186,9 @@ func channelVersionCommand() *cli.Command {
"swiftDialog": {
"macos": "swiftDialog.app.tar.gz",
},
+ "escrowBuddy": {
+ "macos": "escrowBuddy.pkg",
+ },
}
var (
channel string
@@ -208,7 +211,7 @@ func channelVersionCommand() *cli.Command {
&cli.StringSliceFlag{
Name: "components",
EnvVars: []string{"TUF_STATUS_COMPONENTS"},
- Value: cli.NewStringSlice("orbit", "desktop", "osqueryd", "nudge", "swiftDialog"),
+ Value: cli.NewStringSlice("orbit", "desktop", "osqueryd", "nudge", "swiftDialog", "escrowBuddy"),
Destination: &components,
Usage: "List of components",
},
@@ -324,7 +327,7 @@ func channelVersionCommand() *cli.Command {
Right: true,
})
var rows [][]string
- componentsInOrder := []string{"orbit", "desktop", "osqueryd", "nudge", "swiftDialog"}
+ componentsInOrder := []string{"orbit", "desktop", "osqueryd", "nudge", "swiftDialog", "escrowBuddy"}
setIfEmpty := func(m map[string]string, k string) string {
v := m[k]
if v == "" {
From b49841acc13de31b16ff47929c4997f2b6c6eb38 Mon Sep 17 00:00:00 2001
From: Eric
Date: Mon, 5 Aug 2024 16:23:08 -0500
Subject: [PATCH 044/612] Website: Add unsubscribe link to marketing emails
(#21055)
Closes: https://github.com/fleetdm/confidential/issues/7528
Changes:
- Added a new action: unsubscribe-from-marketing-emails, which updates a
user record that uses a specified email address to exclude it from the
deliver-nurture-emails script.
- Updated the email template to include a link that users can click to
unsubscribe.
- Added a modal to the homepage that is shown to users who unsubscribe
from marketing emails
- Updated the email template data in deliver-nurture-emails and
view-email-template-preview
---
.../admin/view-email-template-preview.js | 9 ++-
.../unsubscribe-from-marketing-emails.js | 79 +++++++++++++++++++
website/api/models/User.js | 7 +-
website/assets/js/pages/homepage.page.js | 4 +
website/config/routes.js | 3 +-
website/scripts/deliver-nurture-emails.js | 9 ++-
.../views/layouts/layout-nurture-email.ejs | 1 +
website/views/pages/homepage.ejs | 3 +
8 files changed, 104 insertions(+), 11 deletions(-)
create mode 100644 website/api/controllers/unsubscribe-from-marketing-emails.js
diff --git a/website/api/controllers/admin/view-email-template-preview.js b/website/api/controllers/admin/view-email-template-preview.js
index 5692353463..6898cb3370 100644
--- a/website/api/controllers/admin/view-email-template-preview.js
+++ b/website/api/controllers/admin/view-email-template-preview.js
@@ -117,19 +117,22 @@ module.exports = {
case 'email-nurture-stage-three':
layout = 'layout-nurture-email';
fakeData = {
- firstName: 'Sage'
+ firstName: 'Sage',
+ emailAddress: 'sage@example.com',
};
break;
case 'email-nurture-stage-four':
layout = 'layout-nurture-email';
fakeData = {
- firstName: 'Sage'
+ firstName: 'Sage',
+ emailAddress: 'sage@example.com',
};
break;
case 'email-nurture-stage-five':
layout = 'layout-nurture-email';
fakeData = {
- firstName: 'Sage'
+ firstName: 'Sage',
+ emailAddress: 'sage@example.com',
};
break;
case 'email-deal-registration':
diff --git a/website/api/controllers/unsubscribe-from-marketing-emails.js b/website/api/controllers/unsubscribe-from-marketing-emails.js
new file mode 100644
index 0000000000..26a9b53ee8
--- /dev/null
+++ b/website/api/controllers/unsubscribe-from-marketing-emails.js
@@ -0,0 +1,79 @@
+module.exports = {
+
+
+ friendlyName: 'Unsubscribe from marketing emails',
+
+
+ description: 'Unsubscribes a specified email address from the nurture email automation.',
+
+
+ inputs: {
+ emailAddress: {
+ type: 'string',
+ description: 'The email address of the user who wants to unsubscribe from marketing emails.',
+ required: true,
+ }
+ },
+
+
+ exits: {
+ userNotFound: {
+ description: 'The provided email address could not be matched to a Fleet user account',
+ responseType: 'badRequest',
+ },
+ success: {
+ description: 'The user has opted out of markering emails',
+ }
+ },
+
+
+ fn: async function ({emailAddress}) {
+
+ let userRecord = await User.findOne({emailAddress: emailAddress});
+
+ if(!userRecord){
+ throw 'userNotFound';
+ }
+ // Update the user record for this email address to set their nurture email timestamps to 1
+ // so they are excluded them from future runs of the deliver-nurture-emails script.
+ // FUTURE: update the user model to have a subscribedToNurtureEmails attribute.
+ await User.updateOne({emailAddress: emailAddress}).set({
+ stageThreeNurtureEmailSentAt: 1,
+ stageFourNurtureEmailSentAt: 1,
+ stageFiveNurtureEmailSentAt: 1,
+ });
+
+ // Update the contact record in salesforce for this email address to indicate that they have opted out of marketing emails.
+ if(sails.config.environment === 'production'){
+ require('assert')(sails.config.custom.salesforceIntegrationUsername);
+ require('assert')(sails.config.custom.salesforceIntegrationPasskey);
+
+ // Log in to Salesforce.
+ let jsforce = require('jsforce');
+ let salesforceConnection = new jsforce.Connection({
+ loginUrl : 'https://fleetdm.my.salesforce.com'
+ });
+ await salesforceConnection.login(sails.config.custom.salesforceIntegrationUsername, sails.config.custom.salesforceIntegrationPasskey);
+
+ let existingContactRecord = await salesforceConnection.sobject('Contact')
+ .findOne({
+ Email: emailAddress,
+ });
+
+ if(existingContactRecord) {
+ //If we found an existing contact record in salesforce, update its status to be "Do not contact"
+ let salesforceContactId = existingContactRecord.Id;
+ await salesforceConnection.sobject('Contact')
+ .update({
+ Id: salesforceContactId,
+ Unsubscribed_from_email_contact__c: true,// eslint-disable-line camelcase
+ });
+ }
+ }
+ // Redirect the user to the homepage with a #unsubscribe hash link.
+ return this.res.redirect('/#unsubscribed');
+
+ }
+
+
+};
diff --git a/website/api/models/User.js b/website/api/models/User.js
index 72b603338d..53be23f9c6 100644
--- a/website/api/models/User.js
+++ b/website/api/models/User.js
@@ -248,20 +248,19 @@ without necessarily having a billing card.`
stageThreeNurtureEmailSentAt: {
type: 'number',
- description: 'A JS timestamp of when the stage 3 nurture email was sent to the user.'
+ description: 'A JS timestamp of when the stage 3 nurture email was sent to the user, or 1 if the user is unsubscribed from automated emails.',
},
stageFourNurtureEmailSentAt: {
type: 'number',
- description: 'A JS timestamp of when the stage 4 nurture email was sent to the user.'
+ description: 'A JS timestamp of when the stage 4 nurture email was sent to the user, or 1 if the user is unsubscribed from automated emails.',
},
stageFiveNurtureEmailSentAt: {
type: 'number',
- description: 'A JS timestamp of when the stage 5 nurture email was sent to the user.'
+ description: 'A JS timestamp of when the stage 5 nurture email was sent to the user, or 1 if the user is unsubscribed from automated emails.',
},
-
// ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗
// ║╣ ║║║╠╩╗║╣ ║║╚═╗
// ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝
diff --git a/website/assets/js/pages/homepage.page.js b/website/assets/js/pages/homepage.page.js
index 5a5100cebc..b9bfc560b4 100644
--- a/website/assets/js/pages/homepage.page.js
+++ b/website/assets/js/pages/homepage.page.js
@@ -12,6 +12,10 @@ parasails.registerPage('homepage', {
// ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
beforeMount: function() {
//…
+ if(window.location.hash === '#unsubscribed'){
+ this.modal = 'unsubscribed';
+ window.location.hash = '';
+ }
},
mounted: async function() {
//…
diff --git a/website/config/routes.js b/website/config/routes.js
index 59b068e650..1a87ea2567 100644
--- a/website/config/routes.js
+++ b/website/config/routes.js
@@ -480,7 +480,7 @@ module.exports.routes = {
'GET /get-started': '/try-fleet',
'GET /g': (req,res)=> { let originalQueryStringWithAmp = req.url.match(/\?(.+)$/) ? '&'+req.url.match(/\?(.+)$/)[1] : ''; return res.redirect(301, sails.config.custom.baseUrl+'/?meet-fleet'+originalQueryStringWithAmp); },
'GET /test-fleet-sandbox': '/register',
- 'GET /unsubscribe': (req,res)=> { let originalQueryString = req.url.match(/\?(.+)$/) ? req.url.match(/\?(.+)$/)[1] : ''; return res.redirect(301, sails.config.custom.baseUrl+'/api/v1/unsubscribe-from-all-newsletters?'+originalQueryString);},
+ 'GET /unsubscribe': (req,res)=> { let originalQueryString = req.url.match(/\?(.+)$/) ? req.url.match(/\?(.+)$/)[1] : ''; return res.redirect(301, sails.config.custom.baseUrl+'/api/v1/unsubscribe-from-marketing-emails?'+originalQueryString);},
'GET /tables': '/tables/account_policy_data',
'GET /imagine/launch-party': 'https://www.eventbrite.com/e/601763519887',
'GET /blackhat2023': 'https://github.com/fleetdm/fleet/tree/main/tools/blackhat-mdm', // Assets from @marcosd4h & @zwass Black Hat 2023 talk
@@ -605,4 +605,5 @@ module.exports.routes = {
'POST /api/v1/save-questionnaire-progress': { action: 'save-questionnaire-progress' },
'POST /api/v1/account/update-start-cta-visibility': { action: 'account/update-start-cta-visibility' },
'POST /api/v1/deliver-deal-registration-submission': { action: 'deliver-deal-registration-submission' },
+ '/api/v1/unsubscribe-from-marketing-emails': { action: 'unsubscribe-from-marketing-emails' },
};
diff --git a/website/scripts/deliver-nurture-emails.js b/website/scripts/deliver-nurture-emails.js
index 552c75b0a5..e3f90e254f 100644
--- a/website/scripts/deliver-nurture-emails.js
+++ b/website/scripts/deliver-nurture-emails.js
@@ -52,7 +52,8 @@ module.exports = {
template: 'email-nurture-stage-three',
layout: 'layout-nurture-email',
templateData: {
- firstName: user.firstName
+ firstName: user.firstName,
+ emailAddress: user.emailAddress
},
to: user.emailAddress,
toName: `${user.firstName} ${user.lastName}`,
@@ -80,7 +81,8 @@ module.exports = {
template: 'email-nurture-stage-four',
layout: 'layout-nurture-email',
templateData: {
- firstName: user.firstName
+ firstName: user.firstName,
+ emailAddress: user.emailAddress
},
to: user.emailAddress,
toName: `${user.firstName} ${user.lastName}`,
@@ -109,7 +111,8 @@ module.exports = {
template: 'email-nurture-stage-five',
layout: 'layout-nurture-email',
templateData: {
- firstName: user.firstName
+ firstName: user.firstName,
+ emailAddress: user.emailAddress
},
to: user.emailAddress,
toName: `${user.firstName} ${user.lastName}`,
diff --git a/website/views/layouts/layout-nurture-email.ejs b/website/views/layouts/layout-nurture-email.ejs
index 007880839b..5c4df42a0e 100644
--- a/website/views/layouts/layout-nurture-email.ejs
+++ b/website/views/layouts/layout-nurture-email.ejs
@@ -18,5 +18,6 @@
© <%= (new Date()).getFullYear() %> Fleet Inc.
All trademarks are the property of their respective owners.
+ Unsubscribe