From 5beeb248f7b2eb2c7cca600a3fc9a6691014b998 Mon Sep 17 00:00:00 2001 From: Ian Littman Date: Thu, 2 Jan 2025 10:41:08 -0600 Subject: [PATCH 001/208] Handle long interned strings in MSI parsing (#25079) For #24720. Used https://github.com/ChaelChu/msi-props-reader/blob/master/src/msiPropsReader.ts as inspiration. Not sure why the shift is 17 bits rather than 16 here but confirmed that 17 works and 16 doesn't. Tested against both existing GDrive MSIs for regression testing, plus the one mentioned in the ticket. # 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/24720-msi-large-interned-strings | 1 + pkg/file/msi.go | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 changes/24720-msi-large-interned-strings diff --git a/changes/24720-msi-large-interned-strings b/changes/24720-msi-large-interned-strings new file mode 100644 index 000000000000..c5e559360270 --- /dev/null +++ b/changes/24720-msi-large-interned-strings @@ -0,0 +1 @@ +* Fixed MSI parsing for packages including long interned strings (e.g. licenses for the OpenVPN Connect installer) diff --git a/pkg/file/msi.go b/pkg/file/msi.go index 29538ae5d7e6..db549e9b4ea1 100644 --- a/pkg/file/msi.go +++ b/pkg/file/msi.go @@ -242,9 +242,25 @@ func decodeStrings(dataReader, poolReader io.Reader) ([]string, error) { } return nil, fmt.Errorf("failed to read pool entry: %w", err) } + stringEntrySize := int(stringEntry.Size) + + // For string pool entries too long for the size to fit in a single uint16, entry size is 8 bytes instead of 4, + // with the first two bytes as zeroes, the next two are the two most-significant bytes of the size, shifted + // 17 (?!?) bits to the left, the following two are the less-significant bits of the size, and the last two are + // the reference count. Verified with the OpenVPN Connect v3 installer, which has a large string blob for + // licenses where a 17-bit shift captures the length properly. + if stringEntry.Size == 0 && stringEntry.RefCount != 0 { + stringEntrySize = int(stringEntry.RefCount) << 17 + err := binary.Read(poolReader, binary.LittleEndian, &stringEntry) + if err != nil { + return nil, fmt.Errorf("failed to read large string pool entry: %w", err) + } + stringEntrySize += int(stringEntry.Size) + } + buf.Reset() - buf.Grow(int(stringEntry.Size)) - _, err = io.CopyN(&buf, dataReader, int64(stringEntry.Size)) + buf.Grow(stringEntrySize) + _, err = io.CopyN(&buf, dataReader, int64(stringEntrySize)) if err != nil { return nil, fmt.Errorf("failed to read string data: %w", err) } From 425182c8967c775c373653e6bbc7191276610d8c Mon Sep 17 00:00:00 2001 From: Ian Littman Date: Thu, 2 Jan 2025 10:46:07 -0600 Subject: [PATCH 002/208] Cherry-Pick: Handle long interned strings in MSI parsing (#25096) For #24720, merged into `main` via #25079. From bbc35cb76b0ff972cb32fab45d80bcbea61756c0 Mon Sep 17 00:00:00 2001 From: Ian Littman Date: Thu, 2 Jan 2025 11:38:15 -0600 Subject: [PATCH 003/208] Include pre-releases when building osquery version list constant (#25089) Also updates said constant via this script to include 5.15.0. Idea for this is that including pre-releases as they're published ensures that by the time the corresponding Fleet release ships we have a current list, without having to cherry-pick these updates. # 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 --- .github/scripts/update_osquery_versions.py | 3 +-- changes/osquery-constant-prerelease | 1 + frontend/utilities/constants.tsx | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 changes/osquery-constant-prerelease diff --git a/.github/scripts/update_osquery_versions.py b/.github/scripts/update_osquery_versions.py index c65deceb2ee4..28a932e6acb1 100755 --- a/.github/scripts/update_osquery_versions.py +++ b/.github/scripts/update_osquery_versions.py @@ -14,9 +14,8 @@ def fetch_osquery_versions(): resp = conn.getresponse() content = resp.read() conn.close() - releases = json.loads(content.decode('utf-8')) - return [release['tag_name'] for release in releases if not release['prerelease']] + return [release['tag_name'] for release in json.loads(content.decode('utf-8'))] def update_min_osquery_version_options(new_versions): with open(FILE_PATH, 'r') as file: diff --git a/changes/osquery-constant-prerelease b/changes/osquery-constant-prerelease new file mode 100644 index 000000000000..eda6bad0aa8e --- /dev/null +++ b/changes/osquery-constant-prerelease @@ -0,0 +1 @@ +* Included osquery pre-releases in daily UI constant update GitHub Actions job diff --git a/frontend/utilities/constants.tsx b/frontend/utilities/constants.tsx index 50feb4774f16..5f091b0bfec2 100644 --- a/frontend/utilities/constants.tsx +++ b/frontend/utilities/constants.tsx @@ -84,6 +84,7 @@ export const MAX_OSQUERY_SCHEDULED_QUERY_INTERVAL = 604800; export const MIN_OSQUERY_VERSION_OPTIONS = [ { label: "All", value: "" }, + { label: "5.15.0 +", value: "5.15.0" }, { label: "5.14.1 +", value: "5.14.1" }, { label: "5.13.1 +", value: "5.13.1" }, { label: "5.12.2 +", value: "5.12.2" }, From f2a15bcec864921ae2fbb71cef1ea28eae682646 Mon Sep 17 00:00:00 2001 From: Gabriel Hernandez Date: Thu, 2 Jan 2025 18:06:13 +0000 Subject: [PATCH 004/208] UI fixes to scoped software labels (#25094) relates to #25062, 25063 quick fixes for UI issues with scoped software via labels feature. - [x] Manual QA for all new/changed functionality --- frontend/components/PlatformSelector/PlatformSelector.tsx | 5 +++-- .../components/SoftwareDetailsModal/_styles.scss | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/components/PlatformSelector/PlatformSelector.tsx b/frontend/components/PlatformSelector/PlatformSelector.tsx index 3eb907f9b2ea..592193a6d0ab 100644 --- a/frontend/components/PlatformSelector/PlatformSelector.tsx +++ b/frontend/components/PlatformSelector/PlatformSelector.tsx @@ -71,8 +71,9 @@ export const PlatformSelector = ({
- To apply the profile to new hosts, you'll have to delete it and - upload a new profile. + Your policy will only run on the selected platform(s). Additionally, if + install software automation is enabled, it will run only on hosts + defined in the software scope.
); diff --git a/frontend/pages/DashboardPage/cards/ActivityFeed/components/SoftwareDetailsModal/_styles.scss b/frontend/pages/DashboardPage/cards/ActivityFeed/components/SoftwareDetailsModal/_styles.scss index 023d021a9869..6c4db7f6d6e3 100644 --- a/frontend/pages/DashboardPage/cards/ActivityFeed/components/SoftwareDetailsModal/_styles.scss +++ b/frontend/pages/DashboardPage/cards/ActivityFeed/components/SoftwareDetailsModal/_styles.scss @@ -1,7 +1,9 @@ .software-details-modal { &__modal-content { display: flex; - gap: $pad-xxlarge; + column-gap: $pad-xxlarge; + row-gap: $pad-xlarge; + flex-wrap: wrap; } .react-tooltip { From 631af6b8bfee3b5fe7becccc2fc53ceb1fddccbd Mon Sep 17 00:00:00 2001 From: Gabriel Hernandez Date: Thu, 2 Jan 2025 18:06:23 +0000 Subject: [PATCH 005/208] Update secret error messages across the UI (#25085) relates to #24550 more updates to the various secret error messages after some API changes - [x] Manual QA for all new/changed functionality --- .../components/ProfileUploader/helpers.tsx | 3 +- .../components/ScriptUploader/helpers.ts | 3 +- .../SoftwareCustomPackage/helpers.tsx | 4 +- .../FleetMaintainedAppDetailsPage.tsx | 4 +- .../FleetMaintainedAppDetailsPage/helpers.tsx | 14 +++++++ .../EditSoftwareModal/helpers.tsx | 5 +-- frontend/pages/SoftwarePage/helpers.ts | 40 +++++++++++++++++++ 7 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 frontend/pages/SoftwarePage/helpers.ts diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/helpers.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/helpers.tsx index f33d5fc08f10..4a3defbf1cf7 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/helpers.tsx +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/helpers.tsx @@ -1,6 +1,7 @@ import React from "react"; import { AxiosResponse } from "axios"; import { IApiError } from "interfaces/errors"; +import { generateSecretErrMsg } from "pages/SoftwarePage/helpers"; export const parseFile = async (file: File): Promise<[string, string]> => { // get the file name and extension @@ -60,7 +61,7 @@ export const getErrorMessage = (err: AxiosResponse) => { } if (apiReason.includes("Secret variable")) { - return apiReason.replace("missing from database", "doesn't exist"); + return generateSecretErrMsg(err); } return apiReason || DEFAULT_ERROR_MESSAGE; diff --git a/frontend/pages/ManageControlsPage/Scripts/components/ScriptUploader/helpers.ts b/frontend/pages/ManageControlsPage/Scripts/components/ScriptUploader/helpers.ts index f341ec38b953..016efb8b6e08 100644 --- a/frontend/pages/ManageControlsPage/Scripts/components/ScriptUploader/helpers.ts +++ b/frontend/pages/ManageControlsPage/Scripts/components/ScriptUploader/helpers.ts @@ -1,4 +1,5 @@ import { getErrorReason } from "interfaces/errors"; +import { generateSecretErrMsg } from "pages/SoftwarePage/helpers"; const DEFAULT_ERROR_MESSAGE = "Couldn't upload. Please try again."; @@ -13,7 +14,7 @@ export const getErrorMessage = (err: unknown) => { ) { return "Couldn't upload. The file should be .sh or .ps1 file."; } else if (apiErrMessage.includes("Secret variable")) { - return apiErrMessage.replace("missing from database", "doesn't exist"); + return generateSecretErrMsg(err); } return apiErrMessage || DEFAULT_ERROR_MESSAGE; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/helpers.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/helpers.tsx index 99a09e229bcc..06e3c2e23fdc 100644 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/helpers.tsx +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/helpers.tsx @@ -6,6 +6,8 @@ import { LEARN_MORE_ABOUT_BASE_LINK } from "utilities/constants"; import CustomLink from "components/CustomLink"; +import { generateSecretErrMsg } from "pages/SoftwarePage/helpers"; + const DEFAULT_ERROR_MESSAGE = "Couldn't add. Please try again."; // eslint-disable-next-line import/prefer-default-export @@ -30,7 +32,7 @@ export const getErrorMessage = (err: unknown) => { ); } else if (reason.includes("Secret variable")) { - return reason.replace("missing from database", "doesn't exist"); + return generateSecretErrMsg(err); } else if (reason.includes("Unable to extract necessary metadata")) { return ( <> diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetMaintainedAppDetailsPage.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetMaintainedAppDetailsPage.tsx index d47ccd9ef045..8516ff248378 100644 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetMaintainedAppDetailsPage.tsx +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetMaintainedAppDetailsPage.tsx @@ -12,7 +12,6 @@ import labelsAPI, { getCustomLabels } from "services/entities/labels"; import { QueryContext } from "context/query"; import { AppContext } from "context/app"; import { NotificationContext } from "context/notification"; -import { getErrorReason } from "interfaces/errors"; import { Platform, PLATFORM_DISPLAY_NAMES } from "interfaces/platform"; import { ILabelSummary } from "interfaces/label"; import useToggleSidePanel from "hooks/useToggleSidePanel"; @@ -33,6 +32,7 @@ import { IFleetMaintainedAppFormData } from "./FleetAppDetailsForm/FleetAppDetai import AddFleetAppSoftwareModal from "./AddFleetAppSoftwareModal"; import { + getErrorMessage, getFleetAppPolicyDescription, getFleetAppPolicyName, getFleetAppPolicyQuery, @@ -192,7 +192,7 @@ const FleetMaintainedAppDetailsPage = ({ } catch (error) { // quick exit if there was an error adding the software. Skip the policy // creation. - renderFlash("error", getErrorReason(error)); + renderFlash("error", getErrorMessage(error)); setShowAddFleetAppSoftwareModal(false); return; } diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/helpers.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/helpers.tsx index 3ccdf8df4c3c..3a1f93411e6d 100644 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/helpers.tsx +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/helpers.tsx @@ -1,3 +1,7 @@ +import { getErrorReason } from "interfaces/errors"; + +import { generateSecretErrMsg } from "pages/SoftwarePage/helpers"; + import fleetAppData from "../../../../../../server/mdm/maintainedapps/apps.json"; const NameToIdentifierMap: Record = { @@ -40,3 +44,13 @@ export const getFleetAppPolicyDescription = (appName: string) => { export const getFleetAppPolicyQuery = (name: string) => { return getFleetAppData(name)?.automatic_policy_query; }; + +export const getErrorMessage = (err: unknown) => { + const reason = getErrorReason(err); + + if (reason.includes("Secret variable")) { + return generateSecretErrMsg(err); + } + + return reason; +}; diff --git a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditSoftwareModal/helpers.tsx b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditSoftwareModal/helpers.tsx index 9b4151003cfe..df399a8c5c34 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditSoftwareModal/helpers.tsx +++ b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditSoftwareModal/helpers.tsx @@ -6,6 +6,7 @@ import { ISoftwarePackage } from "interfaces/software"; import CustomLink from "components/CustomLink"; import { LEARN_MORE_ABOUT_BASE_LINK } from "utilities/constants"; +import { generateSecretErrMsg } from "pages/SoftwarePage/helpers"; const DEFAULT_ERROR_MESSAGE = "Couldn't edit software. Please try again."; @@ -36,9 +37,7 @@ export const getErrorMessage = (err: unknown, software: ISoftwarePackage) => { ); } else if (reason.includes("Secret variable")) { - return reason - .replace("missing from database", "doesn't exist") - .replace("Couldn't add", "Couldn't edit"); + return generateSecretErrMsg(err).replace("Couldn't add", "Couldn't edit"); } return reason || DEFAULT_ERROR_MESSAGE; diff --git a/frontend/pages/SoftwarePage/helpers.ts b/frontend/pages/SoftwarePage/helpers.ts new file mode 100644 index 000000000000..48901f0efc52 --- /dev/null +++ b/frontend/pages/SoftwarePage/helpers.ts @@ -0,0 +1,40 @@ +import { getErrorReason } from "interfaces/errors"; + +/** + * helper function to generate error message for secret variables based + * on the error reason. + */ +// eslint-disable-next-line import/prefer-default-export +export const generateSecretErrMsg = (err: unknown) => { + const reason = getErrorReason(err); + + let errorType = ""; + if (getErrorReason(err, { nameEquals: "install script" })) { + errorType = "install script"; + } else if (getErrorReason(err, { nameEquals: "post-install script" })) { + errorType = "post-install script"; + } else if (getErrorReason(err, { nameEquals: "uninstall script" })) { + errorType = "uninstall script"; + } else if (getErrorReason(err, { nameEquals: "profile" })) { + errorType = "profile"; + } + + if (errorType === "profile") { + return reason + .split(":")[1] + .replace(/Secret variables?/i, "Variable") + .replace("missing from database", "doesn't exist."); + } + + // all other specific error types + if (errorType) { + return reason + .replace(/Secret variables?/i, `Variable used in ${errorType} `) + .replace("missing from database", "doesn't exist."); + } + + // no spcial error type. return generic secret error message + return reason + .replace(/Secret variables?/i, "Variable") + .replace("missing from database", "doesn't exist."); +}; From 120f01ad1e90607711617f1ed73bbb5a9355e661 Mon Sep 17 00:00:00 2001 From: jacobshandling <61553566+jacobshandling@users.noreply.github.com> Date: Thu, 2 Jan 2025 10:24:25 -0800 Subject: [PATCH 006/208] Fix verify fleetd-base files > verify-fleetd-base-msi powershell script (#25064) See failed workflow run [here](https://github.com/fleetdm/fleet/actions/runs/12555703803) - Fix the powershell script that was broken by `.yml` auto-format - Exclude github workflow `.yml` files from prettier autoformating, since they often contain non-yaml code as part of job definitions - [ ] Manual QA for all new/changed functionality --------- Co-authored-by: Jacob Shandling --- .github/workflows/verify-fleetd-base.yml | 4 +--- .prettierignore | 3 +++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/verify-fleetd-base.yml b/.github/workflows/verify-fleetd-base.yml index ef84d679dbc9..bd3f1b522950 100644 --- a/.github/workflows/verify-fleetd-base.yml +++ b/.github/workflows/verify-fleetd-base.yml @@ -114,9 +114,7 @@ jobs: Start-Sleep -Seconds 5 cd "C:\Windows\System32\config\systemprofile\AppData\Local\FleetDM\Orbit\Logs" Get-ChildItem - if (!(Test-Path - "C:\Windows\System32\config\systemprofile\AppData\Local\FleetDM\Orbit\Logs\orbit-osquery.log" - -PathType Leaf)) { exit 1 } + if (!(Test-Path "C:\Windows\System32\config\systemprofile\AppData\Local\FleetDM\Orbit\Logs\orbit-osquery.log" -PathType Leaf)) { exit 1 } - name: Slack Notification if: failure() uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 diff --git a/.prettierignore b/.prettierignore index 9bc2a357719b..8eb4d80d724e 100644 --- a/.prettierignore +++ b/.prettierignore @@ -34,3 +34,6 @@ website/ # certain frontend files that are not meant to be formatted frontend/components/FleetAce/mode.ts frontend/components/FleetAce/theme.ts + +# github workflow yaml, which may contain shell scripts that shouldn't be formatted +.github/workflows/* \ No newline at end of file From 495fddc4e6d382637ef372266aca8f8abfa8b3c4 Mon Sep 17 00:00:00 2001 From: jacobshandling <61553566+jacobshandling@users.noreply.github.com> Date: Thu, 2 Jan 2025 10:30:41 -0800 Subject: [PATCH 007/208] UI - Improve validation of SMTP settings form (#25051) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## #25009 - Update validation to match pattern defined in `frontend/docs/patterns.md` - Validate email even when not enabling the feature, since we allow setting it - Remove "CONFIGURED" and "NOT CONFIGURED" copy Screenshot 2024-12-30 at 11 27 08 AM Screenshot 2024-12-30 at 11 27 16 AM Screenshot 2024-12-30 at 11 27 24 AM Screenshot 2024-12-30 at 11 44 10 AM - [x] Changes file added for user-visible changes in `changes/` - [x] Manual QA for all new/changed functionality --------- Co-authored-by: Jacob Shandling --- changes/25009-smtp-page-validation | 1 + .../pages/admin/OrgSettingsPage/_styles.scss | 12 -- .../admin/OrgSettingsPage/cards/Smtp/Smtp.tsx | 146 ++++++++++-------- 3 files changed, 82 insertions(+), 77 deletions(-) create mode 100644 changes/25009-smtp-page-validation diff --git a/changes/25009-smtp-page-validation b/changes/25009-smtp-page-validation new file mode 100644 index 000000000000..4dee1c5ab9b3 --- /dev/null +++ b/changes/25009-smtp-page-validation @@ -0,0 +1 @@ +- Improve validation workflow on SMTP settings page diff --git a/frontend/pages/admin/OrgSettingsPage/_styles.scss b/frontend/pages/admin/OrgSettingsPage/_styles.scss index 9919e5865f96..10d7a76b4af3 100644 --- a/frontend/pages/admin/OrgSettingsPage/_styles.scss +++ b/frontend/pages/admin/OrgSettingsPage/_styles.scss @@ -58,18 +58,6 @@ em { font-style: normal; } - - &--configured { - em { - color: $ui-success; - } - } - - &--notconfigured { - em { - color: $ui-error; - } - } } } diff --git a/frontend/pages/admin/OrgSettingsPage/cards/Smtp/Smtp.tsx b/frontend/pages/admin/OrgSettingsPage/cards/Smtp/Smtp.tsx index d1f6160a5b1c..eaef7914de9d 100644 --- a/frontend/pages/admin/OrgSettingsPage/cards/Smtp/Smtp.tsx +++ b/frontend/pages/admin/OrgSettingsPage/cards/Smtp/Smtp.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useContext } from "react"; +import React, { useState, useContext } from "react"; import { AppContext } from "context/app"; @@ -43,6 +43,54 @@ interface ISmtpConfigFormErrors { password?: string | null; } +const validateFormData = (newData: ISmtpConfigFormData) => { + const errors: ISmtpConfigFormErrors = {}; + + const { + enableSMTP, + smtpSenderAddress, + smtpServer, + smtpPort, + smtpAuthenticationType, + smtpUsername, + smtpPassword, + } = newData; + + if (enableSMTP) { + if (!smtpSenderAddress) { + errors.sender_address = "SMTP sender address must be present"; + } else if (!validEmail(smtpSenderAddress)) { + errors.sender_address = `${smtpSenderAddress} is not a valid email`; + } + + if (!smtpServer) { + errors.server = "SMTP server must be present"; + } + if (!smtpPort) { + errors.server = "SMTP server port must be present"; + errors.server_port = "Port"; + } + if (!smtpServer && !smtpPort) { + errors.server = "SMTP server and server port must be present"; + errors.server_port = "Port"; + } + if (smtpAuthenticationType === "authtype_username_password") { + if (smtpUsername === "") { + errors.user_name = "SMTP username must be present"; + } + if (smtpPassword === "") { + errors.password = "SMTP password must be present"; + } + } + } else if (smtpSenderAddress && !validEmail(smtpSenderAddress)) { + // validations for valid submissions even when smtp not enabled, i.e., updating what will be + // used once it IS enabled + errors.sender_address = `${smtpSenderAddress} is not a valid email`; + } + + return errors; +}; + const baseClass = "app-config-form"; const Smtp = ({ @@ -82,50 +130,35 @@ const Smtp = ({ const sesConfigured = appConfig.email?.backend === "ses" || false; const onInputChange = ({ name, value }: IFormField) => { - setFormData({ ...formData, [name]: value }); - }; - - const validateForm = () => { - const errors: ISmtpConfigFormErrors = {}; - - if (enableSMTP) { - if (!smtpSenderAddress) { - errors.sender_address = "SMTP sender address must be present"; - } else if (!validEmail(smtpSenderAddress)) { - errors.sender_address = `${smtpSenderAddress} is not a valid email`; - } - - if (!smtpServer) { - errors.server = "SMTP server must be present"; - } - if (!smtpPort) { - errors.server = "SMTP server port must be present"; - errors.server_port = "Port"; + const newFormData = { ...formData, [name]: value }; + setFormData(newFormData); + const newErrs = validateFormData(newFormData); + // only set errors that are updates of existing errors + // new errors are only set onBlur or submit + const errsToSet: Record = {}; + Object.keys(formErrors).forEach((k) => { + // @ts-ignore + if (newErrs[k]) { + // @ts-ignore + errsToSet[k] = newErrs[k]; } - if (!smtpServer && !smtpPort) { - errors.server = "SMTP server and server port must be present"; - errors.server_port = "Port"; - } - if (smtpAuthenticationType === "authtype_username_password") { - if (smtpUsername === "") { - errors.user_name = "SMTP username must be present"; - } - if (smtpPassword === "") { - errors.password = "SMTP password must be present"; - } - } - } - - setFormErrors(errors); + }); + setFormErrors(errsToSet); }; - useEffect(() => { - validateForm(); - }, [smtpAuthenticationType]); + const onInputBlur = () => { + setFormErrors(validateFormData(formData)); + }; const onFormSubmit = (evt: React.MouseEvent) => { evt.preventDefault(); + const errs = validateFormData(formData); + if (Object.keys(errs).length > 0) { + setFormErrors(errs); + return; + } + // Formatting of API not UI const formDataToSubmit = { smtp_settings: { @@ -157,7 +190,7 @@ const Smtp = ({ name="smtpUsername" value={smtpUsername} parseTarget - onBlur={validateForm} + onBlur={onInputBlur} error={formErrors.user_name} blockAutoComplete /> @@ -168,7 +201,7 @@ const Smtp = ({ name="smtpPassword" value={smtpPassword} parseTarget - onBlur={validateForm} + onBlur={onInputBlur} error={formErrors.password} blockAutoComplete /> @@ -177,6 +210,7 @@ const Smtp = ({ options={authMethodOptions} placeholder="" onChange={onInputChange} + onBlur={onInputBlur} name="smtpAuthenticationMethod" value={smtpAuthenticationMethod} parseTarget @@ -205,6 +239,7 @@ const Smtp = ({
@@ -228,7 +263,7 @@ const Smtp = ({ name="smtpServer" value={smtpServer} parseTarget - onBlur={validateForm} + onBlur={onInputBlur} error={formErrors.server} tooltip="The hostname / private IP address and corresponding port of your organization's SMTP server." /> @@ -239,12 +274,13 @@ const Smtp = ({ name="smtpPort" value={smtpPort} parseTarget - onBlur={validateForm} + onBlur={onInputBlur} error={formErrors.server_port} />
- - - {appConfig.smtp_settings?.configured - ? "CONFIGURED" - : "NOT CONFIGURED"} - - - ) : ( - <> - ) - } - /> + {sesConfigured ? renderSesEnabled() : renderSmtpForm()}
From 9eb115cf7c4615dbbf82a51c0724b29611803334 Mon Sep 17 00:00:00 2001 From: Ian Littman Date: Thu, 2 Jan 2025 13:07:02 -0600 Subject: [PATCH 008/208] Ignore CVE-2024-10327 since it's iOS-only (#25083) For #25075 # Checklist for submitter - [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] Added/updated tests - [x] Manual QA for all new/changed functionality --- changes/25075-false-positive | 1 + server/vulnerabilities/nvd/cpe_matching_rules.go | 10 ++++++++-- server/vulnerabilities/nvd/cve_test.go | 4 ++++ 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 changes/25075-false-positive diff --git a/changes/25075-false-positive b/changes/25075-false-positive new file mode 100644 index 000000000000..9d60639930e9 --- /dev/null +++ b/changes/25075-false-positive @@ -0,0 +1 @@ +* Fixed CVE-2024-10327 false positive on Fleet-supported platforms (vuln is iOS-only and iOS vuln checking is not supported) diff --git a/server/vulnerabilities/nvd/cpe_matching_rules.go b/server/vulnerabilities/nvd/cpe_matching_rules.go index 88aae7f2dc96..b1eaa6881395 100644 --- a/server/vulnerabilities/nvd/cpe_matching_rules.go +++ b/server/vulnerabilities/nvd/cpe_matching_rules.go @@ -249,8 +249,14 @@ func GetKnownNVDBugRules() (CPEMatchingRules, error) { return cpeMeta.TargetSW != "windows" }, }, - // CVE-2024-10004 only targets iOS, and we don't yet support iOS vuln scanning (and can't tell iOS/Mac CPEs apart yet) - CPEMatchingRule{CVEs: map[string]struct{}{"CVE-2024-10004": {}}, IgnoreAll: true}, + // these CVEs only target iOS, and we don't yet support iOS vuln scanning (and can't tell iOS/Mac CPEs apart yet) + CPEMatchingRule{ + CVEs: map[string]struct{}{ + "CVE-2024-10004": {}, + "CVE-2024-10327": {}, // also missing a CPE as of 2025-01-01 + }, + IgnoreAll: true, + }, } for i, rule := range rules { diff --git a/server/vulnerabilities/nvd/cve_test.go b/server/vulnerabilities/nvd/cve_test.go index 4442f7342506..22057fa7d8ed 100644 --- a/server/vulnerabilities/nvd/cve_test.go +++ b/server/vulnerabilities/nvd/cve_test.go @@ -363,6 +363,10 @@ func TestTranslateCPEToCVE(t *testing.T) { excludedCVEs: []string{"CVE-2024-10004"}, continuesToUpdate: true, }, + "cpe:2.3:a:okta:verify:9.27.0:*:*:*:*:macos:*:*": { + excludedCVEs: []string{"CVE-2024-10327"}, + continuesToUpdate: true, + }, } cveOSTests := []struct { From d3e6311989a36d5274e51598898586722ae956a5 Mon Sep 17 00:00:00 2001 From: jacobshandling <61553566+jacobshandling@users.noreply.github.com> Date: Thu, 2 Jan 2025 12:10:58 -0800 Subject: [PATCH 009/208] =?UTF-8?q?UI=20=E2=80=93=20Fix=20team=20changing?= =?UTF-8?q?=20on=20queries=20table=20search=20query=20change=20(#25103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## #25057 ![ezgif-6-d7867ea6c2](https://github.com/user-attachments/assets/462df801-389e-489e-8339-3b252bf24745) - [x] Manual QA for all new/changed functionality --------- Co-authored-by: Jacob Shandling --- .../pages/queries/ManageQueriesPage/ManageQueriesPage.tsx | 1 - .../components/QueriesTable/QueriesTable.tsx | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/pages/queries/ManageQueriesPage/ManageQueriesPage.tsx b/frontend/pages/queries/ManageQueriesPage/ManageQueriesPage.tsx index 1f5e1b22744b..f23dd14cc1c1 100644 --- a/frontend/pages/queries/ManageQueriesPage/ManageQueriesPage.tsx +++ b/frontend/pages/queries/ManageQueriesPage/ManageQueriesPage.tsx @@ -30,7 +30,6 @@ import queriesAPI, { IQueriesResponse } from "services/entities/queries"; import PATHS from "router/paths"; import { DEFAULT_QUERY } from "utilities/constants"; import Button from "components/buttons/Button"; -import Spinner from "components/Spinner"; import TableDataError from "components/DataError"; import MainContent from "components/MainContent"; import TeamsDropdown from "components/TeamsDropdown"; diff --git a/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTable.tsx b/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTable.tsx index b263d5574ec7..5b330ef13e10 100644 --- a/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTable.tsx +++ b/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTable.tsx @@ -89,6 +89,7 @@ const QueriesTable = ({ const { currentUser } = useContext(AppContext); // Functions to avoid race conditions + // TODO - confirm these are still necessary const initialSearchQuery = (() => queryParams?.query ?? "")(); const initialSortHeader = (() => (queryParams?.order_key as "name" | "updated_at" | "author") ?? @@ -151,12 +152,12 @@ const QueriesTable = ({ router?.push(locationPath); }, [ - sortHeader, + curTargetedPlatformFilter, sortDirection, + sortHeader, searchQuery, - curTargetedPlatformFilter, + queryParams, router, - page, ] ); From 6014908ab660840126c800edd47ae871e387f81a Mon Sep 17 00:00:00 2001 From: Ian Littman Date: Thu, 2 Jan 2025 14:43:36 -0600 Subject: [PATCH 010/208] Correct fix for long MSI interned strings (#25104) h/t https://github.com/binref/refinery/issues/72, for #24720. No changes file as this is an unreleased bug. Also added output for version in the custom package parser tool. # 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 --- pkg/file/msi.go | 18 +++++++----------- tools/custom-package-parser/main.go | 4 ++-- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/pkg/file/msi.go b/pkg/file/msi.go index db549e9b4ea1..7271dd0d9117 100644 --- a/pkg/file/msi.go +++ b/pkg/file/msi.go @@ -242,24 +242,20 @@ func decodeStrings(dataReader, poolReader io.Reader) ([]string, error) { } return nil, fmt.Errorf("failed to read pool entry: %w", err) } - stringEntrySize := int(stringEntry.Size) + stringEntrySize := uint32(stringEntry.Size) - // For string pool entries too long for the size to fit in a single uint16, entry size is 8 bytes instead of 4, - // with the first two bytes as zeroes, the next two are the two most-significant bytes of the size, shifted - // 17 (?!?) bits to the left, the following two are the less-significant bits of the size, and the last two are - // the reference count. Verified with the OpenVPN Connect v3 installer, which has a large string blob for - // licenses where a 17-bit shift captures the length properly. + // For string pool entries too long for the size to fit in a single uint16, the format sets the size as zero, + // maintains the reference count location in the structure, then uses the following four bytes (little-endian) + // to store the string size. See https://github.com/binref/refinery/issues/72. if stringEntry.Size == 0 && stringEntry.RefCount != 0 { - stringEntrySize = int(stringEntry.RefCount) << 17 - err := binary.Read(poolReader, binary.LittleEndian, &stringEntry) + err := binary.Read(poolReader, binary.LittleEndian, &stringEntrySize) if err != nil { - return nil, fmt.Errorf("failed to read large string pool entry: %w", err) + return nil, fmt.Errorf("failed to read size of large string in string pool: %w", err) } - stringEntrySize += int(stringEntry.Size) } buf.Reset() - buf.Grow(stringEntrySize) + buf.Grow(int(stringEntrySize)) _, err = io.CopyN(&buf, dataReader, int64(stringEntrySize)) if err != nil { return nil, fmt.Errorf("failed to read string data: %w", err) diff --git a/tools/custom-package-parser/main.go b/tools/custom-package-parser/main.go index d64506625e2c..9820da7cb7d9 100644 --- a/tools/custom-package-parser/main.go +++ b/tools/custom-package-parser/main.go @@ -31,8 +31,8 @@ func main() { } fmt.Printf( - "- Name: '%s'\n- Bundle Identifier: '%s'\n- Package IDs: '%s'\n", - metadata.Name, metadata.BundleIdentifier, strings.Join(metadata.PackageIDs, ","), + "- Name: '%s'\n- Bundle Identifier: '%s'\n- Package IDs: '%s'\n- Version: %s\n\n", + metadata.Name, metadata.BundleIdentifier, strings.Join(metadata.PackageIDs, ","), metadata.Version, ) } From 0b9f36ad03868be939c1ab68ccbc5bb5827109d6 Mon Sep 17 00:00:00 2001 From: Victor Lyuboslavsky Date: Thu, 2 Jan 2025 15:36:10 -0600 Subject: [PATCH 011/208] Enable MySQL 9.1 Go tests (#25055) Enable MySQL 9 tests in the nightly Go test run. The tests passed in my run: https://github.com/fleetdm/fleet/actions/runs/12552738253/job/34999129651 --- .github/workflows/test-go.yaml | 8 +++++++- docs/Deploy/Reference-Architectures.md | 2 +- docs/Get started/FAQ.md | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-go.yaml b/.github/workflows/test-go.yaml index 1e42bd3fbe29..75c480092854 100644 --- a/.github/workflows/test-go.yaml +++ b/.github/workflows/test-go.yaml @@ -44,7 +44,13 @@ jobs: matrix: suite: ["integration", "core"] os: [ubuntu-latest] - mysql: ["mysql:8.0.36", "mysql:8.4.3"] # make sure to update supported versions docs when this changes + mysql: ["mysql:8.0.36", "mysql:8.4.3", "mysql:9.1.0"] # make sure to update supported versions docs when this changes + isCron: + - ${{ github.event_name == 'schedule' }} + # Only run MySQL 9 tests on cron schedule + exclude: + - isCron: false + mysql: "mysql:9.1.0" continue-on-error: ${{ matrix.suite == 'integration' }} # Since integration tests have a higher chance of failing, often for unrelated reasons, we don't want to fail the whole job if they fail runs-on: ${{ matrix.os }} diff --git a/docs/Deploy/Reference-Architectures.md b/docs/Deploy/Reference-Architectures.md index 6359abb6d830..c75e0a7ad3f1 100644 --- a/docs/Deploy/Reference-Architectures.md +++ b/docs/Deploy/Reference-Architectures.md @@ -33,7 +33,7 @@ Fleet currently has three infrastructure dependencies: MySQL, Redis, and a TLS c Fleet uses MySQL extensively as its main database. Many cloud providers (such as [AWS](https://aws.amazon.com/rds/mysql/) and [GCP](https://cloud.google.com/sql/)) host reliable MySQL services which you may consider for this purpose. A well-supported MySQL [Docker image](https://hub.docker.com/_/mysql/) also exists if you would rather run MySQL in a container. For more information on how to configure the `fleet` binary to use the correct MySQL instance, see the [MySQL configuration](https://fleetdm.com/docs/configuration/fleet-server-configuration#mysql) documentation. -Fleet requires at least MySQL version 8.0.36, and is tested using the InnoDB storage engine [with versions 8.0.36 and 8.4.2](https://github.com/fleetdm/fleet/blob/main/.github/workflows/test-go.yaml#L47). +Fleet requires at least MySQL version 8.0.36, and is tested using the InnoDB storage engine [with versions 8.0.36, 8.4.3, and 9.1.0](https://github.com/fleetdm/fleet/blob/main/.github/workflows/test-go.yaml#L47). There are many "drop-in replacements" for MySQL available. If you'd like to experiment with some bleeding-edge technology and use Fleet with one of these alternative database servers, we think that's awesome! Please be aware they are not officially supported and that it is very important to set up a dev environment to thoroughly test new releases. diff --git a/docs/Get started/FAQ.md b/docs/Get started/FAQ.md index 539a6185a0cf..cb63e6448381 100644 --- a/docs/Get started/FAQ.md +++ b/docs/Get started/FAQ.md @@ -659,7 +659,7 @@ Yes! Please sign up for the [Fleet Cloud Beta](https://kqphpqst851.typeform.com/ ### What MySQL versions are supported? -Fleet is tested with MySQL 8.0.36 and 8.4.2. Newer versions of MySQL 8 typically work well. AWS Aurora requires at least version 3.07.0. Please avoid using MariaDB or other MySQL variants that are not officially supported. Compatibility issues have been identified with MySQL variants, and these may not be addressed in future Fleet releases. +Fleet is tested with MySQL 8.0.36, 8.4.3, and 9.1.0. Newer versions of MySQL 8 typically work well. AWS Aurora requires at least version 3.07.0. Please avoid using MariaDB or other MySQL variants that are not officially supported. Compatibility issues have been identified with MySQL variants, and these may not be addressed in future Fleet releases. ### What are the MySQL user requirements? From 5892edf466006c30186370e041a9e1ae66ea5a46 Mon Sep 17 00:00:00 2001 From: Ian Littman Date: Thu, 2 Jan 2025 15:41:19 -0600 Subject: [PATCH 012/208] Add "should have a QA plan on the associated ticket" to PR checklist (#25053) --- .github/pull_request_template.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index cf6e9c46e2d8..d82e1696088c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -8,13 +8,14 @@ If some of the following don't apply, delete the relevant line. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files) for more information. - [ ] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [ ] Added support on fleet's osquery simulator `cmd/osquery-perf` for new osquery data ingestion features. -- [ ] Added/updated tests - [ ] If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes - [ ] If database migrations are included, checked table schema to confirm autoupdate - For database migrations: - [ ] Checked schema for all modified table for columns that will auto-update timestamps during migration. - [ ] Confirmed that updating the timestamps is acceptable, and will not cause unwanted side effects. - [ ] Ensured the correct collation is explicitly set for character columns (`COLLATE utf8mb4_unicode_ci`). +- [ ] Added/updated automated tests +- [ ] A detailed QA plan exists on the associated ticket (if it isn't there, work with the product group's QA engineer to add it) - [ ] 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`). From 40da87a7b56bb69cbb3c6cd277da73c2f3a9a594 Mon Sep 17 00:00:00 2001 From: Ian Littman Date: Thu, 2 Jan 2025 17:03:19 -0600 Subject: [PATCH 013/208] Mention configurable query result set cap in query docs (#25082) Noticed this hole in #25068. Fingers crossed the wording here matches what folks will search when they need to bump the cap. Also added query data discard config instructions for the UI, and moved how-to-disable instructions to the bottom of the "View a query report" section since users won't need those disclaimers until they have a few queries set up. Finally, dropped the mention of where an old UI was 25+ minor releases ago. --- articles/queries.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/articles/queries.md b/articles/queries.md index 6ce2f53d0ed7..680a8d7802be 100644 --- a/articles/queries.md +++ b/articles/queries.md @@ -35,12 +35,6 @@ How to create a query: ## View a query report -Fleet will store up to 1000 results for each scheduled query to give users a snapshot of query results. If the number of results for a scheduled query is below 1000, then the results will continuously get updated every time the hosts send results to Fleet. - -As you enable query reports, it is advisable to monitor your database to determine if it needs to be scaled up. As an alternative, you can disable query reports. - -> To disable query reports globally, modify `server_settings.query_reports_disabled` field in the global configuration. To disable reports for individual queries, use the `discard_data` field. - How to view a query report: 1. In the top navigation, select **Queries**. @@ -49,6 +43,16 @@ How to view a query report: 3. If you want to download the query report, select **Export results** to save it as a CSV. +Fleet will store up to 1000 results for each scheduled query to give users a snapshot of query results. If the number of results for a scheduled query is below 1000, then the results will continuously get updated every time the hosts send results to Fleet. + +> You can tell Fleet to store more than 1000 results in query reports by setting [`server_settings.query_report_cap`](https://fleetdm.com/docs/rest-api/rest-api#server-settings) via [the Modify configuration API endpoint](https://fleetdm.com/docs/rest-api/rest-api#modify-configuration). + +Persisting query reports within Fleet creates load on the database, so you'll want to monitor database load as you add queries. If needed, you can disable query reports either globally or per-query. + +* Globally via the UI: **Settings** > **Advanced options** > **Disable query reports** +* Globally via the API: set [`server_settings.query_reports_disabled`](https://fleetdm.com/docs/rest-api/rest-api#server-settings) via [the Modify configuration endpoint](https://fleetdm.com/docs/rest-api/rest-api#modify-configuration) +* Per-query via the UI: **Edit query** > **Show advanced options** > **Discard data** +* Per-query via the API: Set the `discard_data` field when [creating](https://fleetdm.com/docs/rest-api/rest-api#create-query) or [modifying](https://fleetdm.com/docs/rest-api/rest-api#modify-query) the query ## Run a query @@ -74,8 +78,6 @@ The query may take several seconds to complete because Fleet has to wait for the ## Schedule a query -*In Fleet 4.35.0, the "Schedule" page was removed, and query automations are now configured on the "Queries" page. Instructions for scheduling queries in earlier versions of Fleet can be found [here](https://github.com/fleetdm/fleet/blob/ac797c8f81ede770853c25fd04102da9f5e109bf/docs/Using-Fleet/Fleet-UI.md#schedule-a-query).* - Fleet allows you to schedule queries to run at a set frequency. By default, queries that run on a schedule will only target platforms compatible with that query. This behavior can be overridden by setting the platforms in **Advanced options** when saving a query. Scheduled queries will send data to Fleet and/or your [log destination](https://fleetdm.com/docs/using-fleet/log-destinations) automatically. Query automations can be turned off in **Advanced options** or using the bulk query automations UI. @@ -103,6 +105,6 @@ How to configure query automations in bulk: - + From 4c076aef5be1f02db50285de7cdd4a4fe147cd58 Mon Sep 17 00:00:00 2001 From: RachelElysia <71795832+RachelElysia@users.noreply.github.com> Date: Fri, 3 Jan 2025 09:28:43 -0500 Subject: [PATCH 014/208] Fleet UI: Fix whatsapp icon being off 1px (#25112) --- frontend/pages/SoftwarePage/components/icons/WhatsApp.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/pages/SoftwarePage/components/icons/WhatsApp.tsx b/frontend/pages/SoftwarePage/components/icons/WhatsApp.tsx index a839380f19f8..c6494695747f 100644 --- a/frontend/pages/SoftwarePage/components/icons/WhatsApp.tsx +++ b/frontend/pages/SoftwarePage/components/icons/WhatsApp.tsx @@ -4,13 +4,13 @@ import type { SVGProps } from "react"; const WhatsApp = (props: SVGProps) => ( - + From 486357326e41c34a3ab42be4c8a1cae1af91e828 Mon Sep 17 00:00:00 2001 From: RachelElysia <71795832+RachelElysia@users.noreply.github.com> Date: Fri, 3 Jan 2025 09:29:38 -0500 Subject: [PATCH 015/208] Fleet UI: Update bad links in setup experience (#25110) --- changes/24418-bad-links | 1 + .../components/AddInstallSoftware/AddInstallSoftware.tsx | 8 +++++++- .../cards/SetupExperienceScript/SetupExperienceScript.tsx | 7 +++++-- website/config/routes.js | 2 +- 4 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 changes/24418-bad-links diff --git a/changes/24418-bad-links b/changes/24418-bad-links new file mode 100644 index 000000000000..2db4eeb7ed36 --- /dev/null +++ b/changes/24418-bad-links @@ -0,0 +1 @@ +- Fleet UI: Fixed two broken links in Setup experience diff --git a/frontend/pages/ManageControlsPage/SetupExperience/cards/InstallSoftware/components/AddInstallSoftware/AddInstallSoftware.tsx b/frontend/pages/ManageControlsPage/SetupExperience/cards/InstallSoftware/components/AddInstallSoftware/AddInstallSoftware.tsx index 1ca1ac38edee..d1e9ce12d044 100644 --- a/frontend/pages/ManageControlsPage/SetupExperience/cards/InstallSoftware/components/AddInstallSoftware/AddInstallSoftware.tsx +++ b/frontend/pages/ManageControlsPage/SetupExperience/cards/InstallSoftware/components/AddInstallSoftware/AddInstallSoftware.tsx @@ -2,6 +2,8 @@ import React from "react"; import PATHS from "router/paths"; +import { LEARN_MORE_ABOUT_BASE_LINK } from "utilities/constants"; + import Button from "components/buttons/Button"; import CustomLink from "components/CustomLink"; import { ISoftwareTitle } from "interfaces/software"; @@ -84,7 +86,11 @@ const AddInstallSoftware = ({

Install software on hosts that automatically enroll to Fleet.

- + {addedText}
diff --git a/frontend/pages/ManageControlsPage/SetupExperience/cards/SetupExperienceScript/SetupExperienceScript.tsx b/frontend/pages/ManageControlsPage/SetupExperience/cards/SetupExperienceScript/SetupExperienceScript.tsx index 46dbc4696ce8..10a8b91dea79 100644 --- a/frontend/pages/ManageControlsPage/SetupExperience/cards/SetupExperienceScript/SetupExperienceScript.tsx +++ b/frontend/pages/ManageControlsPage/SetupExperience/cards/SetupExperienceScript/SetupExperienceScript.tsx @@ -2,7 +2,10 @@ import React, { useState } from "react"; import { useQuery } from "react-query"; import { AxiosError } from "axios"; -import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants"; +import { + DEFAULT_USE_QUERY_OPTIONS, + LEARN_MORE_ABOUT_BASE_LINK, +} from "utilities/constants"; import mdmAPI, { IGetSetupExperienceScriptResponse, } from "services/entities/mdm"; @@ -72,7 +75,7 @@ const SetupExperienceScript = ({ {!scriptUploaded || !script ? ( diff --git a/website/config/routes.js b/website/config/routes.js index 4005ddc36a7a..503f9f5c7f29 100644 --- a/website/config/routes.js +++ b/website/config/routes.js @@ -582,7 +582,7 @@ module.exports.routes = { 'GET /learn-more-about/setup-ndes': '/guides/ndes-scep-proxy', 'GET /learn-more-about/idp-email': 'https://fleetdm.com/docs/rest-api/rest-api#get-human-device-mapping', 'GET /learn-more-about/enrolling-hosts': '/docs/using-fleet/adding-hosts', - 'GET /learn-more-about/setup-assistant': '/docs/using-fleet/mdm-macos-setup-experience#macos-setup-assistant', + 'GET /learn-more-about/setup-assistant': '/guides/macos-setup-experience#macos-setup-assistant', 'GET /learn-more-about/policy-automations': '/docs/using-fleet/automations', 'GET /install-wine': 'https://github.com/fleetdm/fleet/blob/main/scripts/macos-install-wine.sh', 'GET /learn-more-about/creating-service-accounts': 'https://console.cloud.google.com/projectselector2/iam-admin/serviceaccounts/create?walkthrough_id=iam--create-service-account&pli=1#step_index=1', From 97fc14da72262d8865fb5f8cbcfbb00c93758e7b Mon Sep 17 00:00:00 2001 From: RachelElysia <71795832+RachelElysia@users.noreply.github.com> Date: Fri, 3 Jan 2025 09:29:56 -0500 Subject: [PATCH 016/208] Fleet UI: Fix radio help text font size (#25105) --- frontend/components/forms/fields/Radio/_styles.scss | 1 + .../FleetAppDetailsForm/FleetAppDetailsForm.tsx | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/components/forms/fields/Radio/_styles.scss b/frontend/components/forms/fields/Radio/_styles.scss index 57c116ab2568..d40961c93aa9 100644 --- a/frontend/components/forms/fields/Radio/_styles.scss +++ b/frontend/components/forms/fields/Radio/_styles.scss @@ -60,6 +60,7 @@ &__help-text { color: $ui-fleet-black-75; + font-size: $xx-small; margin-top: $pad-xxsmall; margin-left: calc(20px + #{$pad-small}); } diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetAppDetailsForm/FleetAppDetailsForm.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetAppDetailsForm/FleetAppDetailsForm.tsx index d70e229e3646..20aaa4c4c95e 100644 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetAppDetailsForm/FleetAppDetailsForm.tsx +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetAppDetailsForm/FleetAppDetailsForm.tsx @@ -89,7 +89,11 @@ export const InstallTypeSection = ({ name="install-type" label="Manual" onChange={onChangeInstallType} - helpText="Manually install on Host details page for each host." + helpText={ + <> + Manually install on Host details page for each host. + + } /> Date: Fri, 3 Jan 2025 09:30:59 -0500 Subject: [PATCH 017/208] Fleet UI: Fix app id link not row id (#25113) --- changes/23302-fma-click-bug | 1 + .../FleetMaintainedAppsTable.tsx | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 changes/23302-fma-click-bug diff --git a/changes/23302-fma-click-bug b/changes/23302-fma-click-bug new file mode 100644 index 000000000000..fdd4cd4d2fdb --- /dev/null +++ b/changes/23302-fma-click-bug @@ -0,0 +1 @@ +- Fleet UI: Fixed redirect when clicking on any column in the Fleet Maintained Apps table diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/FleetMaintainedAppsTable.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/FleetMaintainedAppsTable.tsx index f993128a9055..bb0087a33a27 100644 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/FleetMaintainedAppsTable.tsx +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/FleetMaintainedAppsTable.tsx @@ -47,6 +47,10 @@ interface IFleetMaintainedAppsTableProps { data?: ISoftwareFleetMaintainedAppsResponse; } +interface IRowProps { + original: IFleetMaintainedApp; +} + const FleetMaintainedAppsTable = ({ teamId, isLoading, @@ -118,9 +122,9 @@ const FleetMaintainedAppsTable = ({ [determineQueryParamChange, generateNewQueryParams, router] ); - const handleRowClick = (row: IFleetMaintainedApp) => { + const handleRowClick = (row: IRowProps) => { const path = `${PATHS.SOFTWARE_FLEET_MAINTAINED_DETAILS( - row.id + row.original.id )}?${buildQueryStringFromParams({ team_id: teamId, })}`; @@ -156,7 +160,7 @@ const FleetMaintainedAppsTable = ({ }; return ( - + className={baseClass} columnConfigs={tableHeadersConfig} data={data?.fleet_maintained_apps ?? []} From 645d4d8c256d2974da8d5c55b14868d3f3e8f38c Mon Sep 17 00:00:00 2001 From: RachelElysia <71795832+RachelElysia@users.noreply.github.com> Date: Fri, 3 Jan 2025 09:31:25 -0500 Subject: [PATCH 018/208] Fleet UI: Clarify VPP app teams (#25111) --- changes/21827-edit-vpp-teams | 1 + .../components/EditTeamsVppModal/EditTeamsVppModal.tsx | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 changes/21827-edit-vpp-teams diff --git a/changes/21827-edit-vpp-teams b/changes/21827-edit-vpp-teams new file mode 100644 index 000000000000..aca883e02fe8 --- /dev/null +++ b/changes/21827-edit-vpp-teams @@ -0,0 +1 @@ +- Fleet UI: Clarify editing VPP teams will remove App Store apps available to team, not uninstalling apps from hosts. diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/components/EditTeamsVppModal/EditTeamsVppModal.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/components/EditTeamsVppModal/EditTeamsVppModal.tsx index 029380e60e17..27e2d1a4252e 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/components/EditTeamsVppModal/EditTeamsVppModal.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/components/EditTeamsVppModal/EditTeamsVppModal.tsx @@ -202,6 +202,10 @@ const EditTeamsVppModal = ({

Edit teams for {currentToken.org_name}.

+

+ If you remove a team, the App Store apps will be removed from that + team. They won't be uninstalled from hosts. +

Date: Fri, 3 Jan 2025 09:32:31 -0500 Subject: [PATCH 019/208] Fleet UI: Fix software actions dropdown styling bug (#25102) --- changes/24335-dropdown-styling-bug | 1 + .../components/ActionsDropdown/ActionsDropdown.tsx | 5 ++--- .../SoftwarePackageCard/SoftwarePackageCard.tsx | 7 +++---- .../SoftwarePackageCard/_styles.scss | 14 -------------- 4 files changed, 6 insertions(+), 21 deletions(-) create mode 100644 changes/24335-dropdown-styling-bug diff --git a/changes/24335-dropdown-styling-bug b/changes/24335-dropdown-styling-bug new file mode 100644 index 000000000000..e56f27071a31 --- /dev/null +++ b/changes/24335-dropdown-styling-bug @@ -0,0 +1 @@ +- Fleet UI: Fix software actions dropdown styling bug diff --git a/frontend/components/ActionsDropdown/ActionsDropdown.tsx b/frontend/components/ActionsDropdown/ActionsDropdown.tsx index 0ac7c2d61c8d..a54b9df219b6 100644 --- a/frontend/components/ActionsDropdown/ActionsDropdown.tsx +++ b/frontend/components/ActionsDropdown/ActionsDropdown.tsx @@ -29,9 +29,7 @@ interface IActionsDropdownProps { } const getOptionBackgroundColor = (state: any) => { - return state.isSelected || state.isFocused - ? COLORS["ui-vibrant-blue-10"] - : "transparent"; + return state.isFocused ? COLORS["ui-vibrant-blue-10"] : "transparent"; }; const getLeftMenuAlign = (menuAlign: "right" | "left" | "default") => { @@ -238,6 +236,7 @@ const ActionsDropdown = ({ }} controlShouldRenderValue={false} // Doesn't change placeholder text to selected text isOptionSelected={() => false} // Hides any styling on selected option + value={null} // Prevent an option from being selected className={dropdownClassnames} classNamePrefix={`${baseClass}-select`} isOptionDisabled={(option) => !!option.disabled} diff --git a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwarePackageCard/SoftwarePackageCard.tsx b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwarePackageCard/SoftwarePackageCard.tsx index a23eaed60a2a..74b683476258 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwarePackageCard/SoftwarePackageCard.tsx +++ b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwarePackageCard/SoftwarePackageCard.tsx @@ -189,8 +189,8 @@ const SoftwareActionsDropdown = ({ onDeleteClick, onEditSoftwareClick, }: IActionsDropdownProps) => { - const onSelect = (value: string) => { - switch (value) { + const onSelect = (action: string) => { + switch (action) { case "download": onDownloadClick(); break; @@ -208,10 +208,9 @@ const SoftwareActionsDropdown = ({ return (
.Select-menu-outer { - left: -120px; - } - .Select-placeholder { - color: $core-fleet-black; - } - } - &__download-icon { display: flex; justify-content: center; From c237857f2653d608de4ab097c62ebbf1175f83fb Mon Sep 17 00:00:00 2001 From: Gabriel Hernandez Date: Fri, 3 Jan 2025 14:33:05 +0000 Subject: [PATCH 020/208] guide for automatical install for fleet maintinaed apps (#25042) relates to #24681 Docs for the automatical install for fleet maintained apps. --- articles/automatic-install-fleet-software | 35 +++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 articles/automatic-install-fleet-software diff --git a/articles/automatic-install-fleet-software b/articles/automatic-install-fleet-software new file mode 100644 index 000000000000..3d18e8eb9eee --- /dev/null +++ b/articles/automatic-install-fleet-software @@ -0,0 +1,35 @@ +# Automatic Installation for Fleet-Maintained Apps + +Fleet can now help simplify software deployment for IT admins with automatic installation of +Fleet-maintained apps. This update eliminates the need for manual installation on individual hosts, +saving time and effort when deploying new software across your organization. Here's how it works: + +1. **Select the App:** Navigate to the **Software** page and click the "Add software". Then choose + the **Fleet-maintained** section and select the desired Fleet-maintained app (e.g., Notion). +2. **Choose Installation Method:** You'll now see a new option: an **Install** section with "Manual" and "Automatic" options. Selecting "Automatic" will create a policy to automatically install the app on hosts that don't currently have it. +3. **Add Software:** Click "Add Software" to initiate the automatic installation process. + +## Key Benefits + +* **Simplified Deployment:** Deploy apps to multiple hosts with a single click, eliminating manual installation on each machine. +* **Automated Policy Creation:** The system automatically creates the necessary policy to manage the installations, removing the need for manual policy creation. +* **Automatic Reinstallation:** If a user uninstalls the app, the policy will automatically reinstall it on the next policy run, ensuring consistent deployment. +* **Clear Visibility:** A badge on the app listing indicates which apps are configured for automatic installation. The app's details page also clearly states its automatic installation status and provides a link to the associated policy. + +## How to Manage Automatic Installations + +* **View Policies:** Clicking on the automatic installation indicator on the app details page will + take you to the relevant policy's details view. You can also find the policy listed on the **Policies** page under the relevant team. +* **Monitor Installation Status:** Fleet provides real-time feedback on the installation process. + You can monitor the installation status on the **Host Details** page under the "Activity" section, which shows queued installations and + any potential failures. +* **Prevent Accidental Deletion:** If you attempt to delete an app that has an automatic + installation policy associated with it, the system will prevent the deletion until the policy is + removed. This prevents accidental disruption of app deployments. + +## Important Considerations + +**GitOps Compatibility:** Currently, Fleet-maintained apps are not fully compatible with GitOps. This means these automatically generated policies will be affected by GitOps runs. We are working on improving GitOps support for Fleet-maintained apps in future releases. For now, using uploaded packages and VPP is the recommended approach for GitOps workflows. + +This new feature simplifies app deployment, saving you time and ensuring consistent application +availability across your organization. We encourage you to try it out and reach out to us with any feedback. From 58afb100af35e57c91e9235e83cea5b9ad69caca Mon Sep 17 00:00:00 2001 From: RachelElysia <71795832+RachelElysia@users.noreply.github.com> Date: Fri, 3 Jan 2025 10:24:51 -0500 Subject: [PATCH 021/208] Unreleased bug fix: Hide install options from editing software modal (#25122) --- .../components/PackageForm/PackageForm.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/frontend/pages/SoftwarePage/components/PackageForm/PackageForm.tsx b/frontend/pages/SoftwarePage/components/PackageForm/PackageForm.tsx index 8da22a54071f..a623cc7884c6 100644 --- a/frontend/pages/SoftwarePage/components/PackageForm/PackageForm.tsx +++ b/frontend/pages/SoftwarePage/components/PackageForm/PackageForm.tsx @@ -241,12 +241,14 @@ const PackageForm = ({ formData.software ? getFileDetails(formData.software) : undefined } /> - + {!isEditingSoftware && ( + + )} Date: Fri, 3 Jan 2025 16:03:13 +0000 Subject: [PATCH 022/208] add software_title_id to added_app_store_app activity (#25119) relates to #24120 adds the `software_title_id` to the `added_app_store_activity` - [x] Added/updated automated tests - [x] Manual QA for all new/changed functionality --- docs/Contributing/Audit-logs.md | 2 ++ ee/server/service/vpp.go | 17 ++++++++------- server/fleet/activities.go | 15 +++++++------ server/service/integration_mdm_test.go | 29 +++++++++++++++++--------- 4 files changed, 40 insertions(+), 23 deletions(-) diff --git a/docs/Contributing/Audit-logs.md b/docs/Contributing/Audit-logs.md index feadc6e0cda6..19dea07e2f81 100644 --- a/docs/Contributing/Audit-logs.md +++ b/docs/Contributing/Audit-logs.md @@ -1376,6 +1376,7 @@ Generated when an App Store app is added to Fleet. This activity contains the following fields: - "software_title": Name of the App Store app. +- "software_title_id": ID of the added software title. - "app_store_id": ID of the app on the Apple App Store. - "platform": Platform of the app (`darwin`, `ios`, or `ipados`). - "self_service": App installation can be initiated by device owner. @@ -1387,6 +1388,7 @@ This activity contains the following fields: ```json { "software_title": "Logic Pro", + "software_title_id": 123, "app_store_id": "1234567", "platform": "darwin", "self_service": false, diff --git a/ee/server/service/vpp.go b/ee/server/service/vpp.go index d8f453a950e3..04eaef1ea9fa 100644 --- a/ee/server/service/vpp.go +++ b/ee/server/service/vpp.go @@ -363,17 +363,20 @@ func (svc *Service) AddAppStoreApp(ctx context.Context, teamID *uint, appID flee Name: assetMD.TrackName, LatestVersion: assetMD.Version, } - if _, err := svc.ds.InsertVPPAppWithTeam(ctx, app, teamID); err != nil { + + addedApp, err := svc.ds.InsertVPPAppWithTeam(ctx, app, teamID) + if err != nil { return ctxerr.Wrap(ctx, err, "writing VPP app to db") } act := fleet.ActivityAddedAppStoreApp{ - AppStoreID: app.AdamID, - Platform: app.Platform, - TeamName: &teamName, - SoftwareTitle: app.Name, - TeamID: teamID, - SelfService: app.SelfService, + AppStoreID: app.AdamID, + Platform: app.Platform, + TeamName: &teamName, + SoftwareTitle: app.Name, + SoftwareTitleId: addedApp.TitleID, + TeamID: teamID, + SelfService: app.SelfService, } if err := svc.NewActivity(ctx, authz.UserFromContext(ctx), act); err != nil { return ctxerr.Wrap(ctx, err, "create activity for add app store app") diff --git a/server/fleet/activities.go b/server/fleet/activities.go index fcc70fe610e8..5cb0d4fbb73a 100644 --- a/server/fleet/activities.go +++ b/server/fleet/activities.go @@ -1909,12 +1909,13 @@ func (a ActivityDisabledVPP) Documentation() (activity string, details string, d } type ActivityAddedAppStoreApp struct { - SoftwareTitle string `json:"software_title"` - AppStoreID string `json:"app_store_id"` - TeamName *string `json:"team_name"` - TeamID *uint `json:"team_id"` - Platform AppleDevicePlatform `json:"platform"` - SelfService bool `json:"self_service"` + SoftwareTitle string `json:"software_title"` + SoftwareTitleId uint `json:"software_title_id"` + AppStoreID string `json:"app_store_id"` + TeamName *string `json:"team_name"` + TeamID *uint `json:"team_id"` + Platform AppleDevicePlatform `json:"platform"` + SelfService bool `json:"self_service"` } func (a ActivityAddedAppStoreApp) ActivityName() string { @@ -1924,12 +1925,14 @@ func (a ActivityAddedAppStoreApp) ActivityName() string { func (a ActivityAddedAppStoreApp) Documentation() (activity string, details string, detailsExample string) { return "Generated when an App Store app is added to Fleet.", `This activity contains the following fields: - "software_title": Name of the App Store app. +- "software_title_id": ID of the added software title. - "app_store_id": ID of the app on the Apple App Store. - "platform": Platform of the app (` + "`darwin`, `ios`, or `ipados`" + `). - "self_service": App installation can be initiated by device owner. - "team_name": Name of the team to which this App Store app was added, or ` + "`null`" + ` if it was added to no team. - "team_id": ID of the team to which this App Store app was added, or ` + "`null`" + `if it was added to no team.`, `{ "software_title": "Logic Pro", + "software_title_id": 123, "app_store_id": "1234567", "platform": "darwin", "self_service": false, diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go index 71d2acde9b1b..fc321093df8e 100644 --- a/server/service/integration_mdm_test.go +++ b/server/service/integration_mdm_test.go @@ -3028,7 +3028,6 @@ func (s *integrationMDMTestSuite) TestEnqueueMDMCommandWithSecret() { require.NoError(t, err) assert.Contains(t, string(cmd.Raw), secretValue) assert.NotContains(t, string(cmd.Raw), "FLEET_SECRET_VALUE") - } func (s *integrationMDMTestSuite) TestMDMWindowsCommandResults() { @@ -7194,7 +7193,6 @@ func (s *integrationMDMTestSuite) TestWindowsMDMCommandWithSecret() { // The secret value should not be exposed via the regular API. assert.NotContains(t, string(getMDMCmdResp.Results[0].Payload), secretValue) assert.Contains(t, string(getMDMCmdResp.Results[0].Payload), "$FLEET_SECRET_DATA") - } func (s *integrationMDMTestSuite) TestWindowsAutomaticEnrollmentCommands() { @@ -10997,6 +10995,7 @@ func (s *integrationMDMTestSuite) TestVPPApps() { s.DoJSON("GET", "/api/latest/fleet/software/app_store_apps", &getAppStoreAppsRequest{}, http.StatusOK, &appResp, "team_id", fmt.Sprint(team.ID)) require.NoError(t, appResp.Err) + macOSApp := fleet.VPPApp{ VPPAppTeam: fleet.VPPAppTeam{ VPPAppID: fleet.VPPAppID{ @@ -11064,6 +11063,16 @@ func (s *integrationMDMTestSuite) TestVPPApps() { } assert.ElementsMatch(t, expectedApps, appResp.AppStoreApps) + getSoftwareTitleIDFromApp := func(app *fleet.VPPApp) uint { + var titleID uint + mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error { + ctx := context.Background() + return sqlx.GetContext(ctx, q, &titleID, `SELECT title_id FROM vpp_apps WHERE adam_id = ? AND platform = ?;`, app.AdamID, app.Platform) + }) + + return titleID + } + // Insert/deletion flow for macOS app // Add an app store app to team 1 addedApp := expectedApps[0] @@ -11073,8 +11082,8 @@ func (s *integrationMDMTestSuite) TestVPPApps() { s.DoJSON("POST", "/api/latest/fleet/software/app_store_apps", &addAppStoreAppRequest{TeamID: &team.ID, AppStoreID: addedApp.AdamID, SelfService: true}, http.StatusOK, &addAppResp) s.lastActivityMatches(fleet.ActivityAddedAppStoreApp{}.ActivityName(), - fmt.Sprintf(`{"team_name": "%s", "software_title": "%s", "app_store_id": "%s", "team_id": %d, "platform": "%s", "self_service": true}`, team.Name, - addedApp.Name, addedApp.AdamID, team.ID, addedApp.Platform), 0) + fmt.Sprintf(`{"team_name": "%s", "software_title": "%s", "software_title_id": %d, "app_store_id": "%s", "team_id": %d, "platform": "%s", "self_service": true}`, team.Name, + addedApp.Name, getSoftwareTitleIDFromApp(addedApp), addedApp.AdamID, team.ID, addedApp.Platform), 0) // Now we should be filtering out the app we added to team 1 appResp = getAppStoreAppsResponse{} @@ -11124,8 +11133,8 @@ func (s *integrationMDMTestSuite) TestVPPApps() { &addAppStoreAppRequest{TeamID: &team.ID, AppStoreID: addedApp.AdamID, Platform: addedApp.Platform}, http.StatusOK, &addAppResp) s.lastActivityMatches(fleet.ActivityAddedAppStoreApp{}.ActivityName(), - fmt.Sprintf(`{"team_name": "%s", "software_title": "%s", "app_store_id": "%s", "team_id": %d, "platform": "%s", "self_service": false}`, team.Name, - addedApp.Name, addedApp.AdamID, team.ID, addedApp.Platform), 0) + fmt.Sprintf(`{"team_name": "%s", "software_title": "%s", "software_title_id": %d, "app_store_id": "%s", "team_id": %d, "platform": "%s", "self_service": false}`, team.Name, + addedApp.Name, getSoftwareTitleIDFromApp(addedApp), addedApp.AdamID, team.ID, addedApp.Platform), 0) // Now we should be filtering out the app we added to team 1 appResp = getAppStoreAppsResponse{} @@ -11198,8 +11207,8 @@ func (s *integrationMDMTestSuite) TestVPPApps() { http.StatusOK, &addAppResp) s.lastActivityMatches( fleet.ActivityAddedAppStoreApp{}.ActivityName(), - fmt.Sprintf(`{"team_name": "%s", "software_title": "%s", "app_store_id": "%s", "team_id": %d, "platform": "%s", "self_service": true}`, team.Name, - appSelfService.Name, appSelfService.AdamID, team.ID, appSelfService.Platform), + fmt.Sprintf(`{"team_name": "%s", "software_title": "%s", "software_title_id": %d, "app_store_id": "%s", "team_id": %d, "platform": "%s", "self_service": true}`, team.Name, + appSelfService.Name, getSoftwareTitleIDFromApp(appSelfService), appSelfService.AdamID, team.ID, appSelfService.Platform), 0, ) listSw = listSoftwareTitlesResponse{} @@ -11213,8 +11222,8 @@ func (s *integrationMDMTestSuite) TestVPPApps() { http.StatusOK, &addAppResp) s.lastActivityMatches( fleet.ActivityAddedAppStoreApp{}.ActivityName(), - fmt.Sprintf(`{"team_name": "%s", "software_title": "%s", "app_store_id": "%s", "team_id": %d, "platform": "%s", "self_service": false}`, team.Name, - app.Name, app.AdamID, team.ID, app.Platform), + fmt.Sprintf(`{"team_name": "%s", "software_title": "%s", "software_title_id": %d, "app_store_id": "%s", "team_id": %d, "platform": "%s", "self_service": false}`, team.Name, + app.Name, getSoftwareTitleIDFromApp(app), app.AdamID, team.ID, app.Platform), 0, ) listSw = listSoftwareTitlesResponse{} From ecab28b000a21d9af0ed38aefa16241dbcf93d01 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 3 Jan 2025 10:14:13 -0600 Subject: [PATCH 023/208] MSP Dashboard: Add Entra SSO Hook (#24740) Related to: #24688 Changes: - Added two new dependencies: `jsonwebtoken` and `@azure/msal-node` - Added a new hook: `entra-sso`. A hook that replaces the default authentication mechanism with Microsoft Entra SSO. - Added a new action: signup-sso-user-or-redirect. This action finds or creates user records for authenticated SSO users and attaches the user record to the user's session. - updated the is-logged-in policy to check if an SSO user's token is still valid. - Added a link to the account page to the app's header navigation. --- .../account/view-account-overview.js | 1 + .../controllers/account/view-edit-profile.js | 4 +- .../entrance/signup-sso-user-or-redirect.js | 48 +++++++ .../api/hooks/entra-sso/index.js | 127 ++++++++++++++++++ .../api/policies/is-logged-in.js | 8 ++ ee/bulk-operations-dashboard/config/custom.js | 15 +++ ee/bulk-operations-dashboard/config/routes.js | 1 + ee/bulk-operations-dashboard/package.json | 2 + .../views/layouts/layout.ejs | 3 + .../views/pages/account/account-overview.ejs | 9 +- .../views/pages/account/edit-profile.ejs | 6 +- 11 files changed, 215 insertions(+), 9 deletions(-) create mode 100644 ee/bulk-operations-dashboard/api/controllers/entrance/signup-sso-user-or-redirect.js create mode 100644 ee/bulk-operations-dashboard/api/hooks/entra-sso/index.js diff --git a/ee/bulk-operations-dashboard/api/controllers/account/view-account-overview.js b/ee/bulk-operations-dashboard/api/controllers/account/view-account-overview.js index f5841f99813d..06fceb94c971 100644 --- a/ee/bulk-operations-dashboard/api/controllers/account/view-account-overview.js +++ b/ee/bulk-operations-dashboard/api/controllers/account/view-account-overview.js @@ -21,6 +21,7 @@ module.exports = { // If billing features are enabled, include our configured Stripe.js // public key in the view locals. Otherwise, leave it as undefined. return { + replaceBuiltInAuthWithEntra: (sails.config.custom.entraClientSecret !== undefined), stripePublishableKey: sails.config.custom.enableBillingFeatures? sails.config.custom.stripePublishableKey : undefined, }; diff --git a/ee/bulk-operations-dashboard/api/controllers/account/view-edit-profile.js b/ee/bulk-operations-dashboard/api/controllers/account/view-edit-profile.js index baea0f7c206d..017ce5dba572 100644 --- a/ee/bulk-operations-dashboard/api/controllers/account/view-edit-profile.js +++ b/ee/bulk-operations-dashboard/api/controllers/account/view-edit-profile.js @@ -18,7 +18,9 @@ module.exports = { fn: async function () { - return {}; + return { + replaceBuiltInAuthWithEntra: (sails.config.custom.entraClientSecret !== undefined), + }; } diff --git a/ee/bulk-operations-dashboard/api/controllers/entrance/signup-sso-user-or-redirect.js b/ee/bulk-operations-dashboard/api/controllers/entrance/signup-sso-user-or-redirect.js new file mode 100644 index 000000000000..8c3f090b9c20 --- /dev/null +++ b/ee/bulk-operations-dashboard/api/controllers/entrance/signup-sso-user-or-redirect.js @@ -0,0 +1,48 @@ +module.exports = { + + + friendlyName: 'Signup SSO user or redirect', + + + description: 'Looks up or creates user records for Entra SSO users, and attaches the database id to the requesting user\'s session.', + + + exits: { + redirect: { + responseType: 'redirect', + }, + }, + + + fn: async function () { + if(!this.req.session) {// If the requesting user does not have a session, redirect them to the login page. + throw {redirect: '/login'}; + } + // If the requesting user has a session, but it does not contain a ssoUserInformation object, we'll redirect them to the login page. + if (!this.req.session.ssoUserInformation) { + throw {redirect: '/login'}; + } + let ssoUserInfo = this.req.session.ssoUserInformation; + // Look for a user record with this sso user's email address. + let possibleUserRecordForThisEntraUser = await User.findOne({emailAddress: ssoUserInfo.unique_name}); + + if(possibleUserRecordForThisEntraUser) { + // If we found an existing user record that uses this Entra user's email address, we'll set the requesting session.userId to be the id of the database record. + this.req.session.userId = possibleUserRecordForThisEntraUser.id; + } else { + // If we did not find a user in the database for this Entra user, we'll create a new one. + let newUserRecord = await User.create({ + fullName: ssoUserInfo.given_name +' '+ssoUserInfo.family_name, + emailAddress: ssoUserInfo.unique_name, + password: await sails.helpers.passwords.hashPassword(ssoUserInfo.sub),// Note: this password cannot be changed. + apiToken: await sails.helpers.strings.uuid(), + }).fetch(); + this.req.session.userId = newUserRecord.id; + } + // Redirect the logged-in user to the homepage. + return this.res.redirect('/'); + + } + + +}; diff --git a/ee/bulk-operations-dashboard/api/hooks/entra-sso/index.js b/ee/bulk-operations-dashboard/api/hooks/entra-sso/index.js new file mode 100644 index 000000000000..677d582ebee4 --- /dev/null +++ b/ee/bulk-operations-dashboard/api/hooks/entra-sso/index.js @@ -0,0 +1,127 @@ +/** + * Module dependencies + */ +let { ConfidentialClientApplication } = require('@azure/msal-node'); +let jwt = require('jsonwebtoken'); + +/** + * Entra SSO Hook + */ +module.exports = function (sails) { + + let entraSSOClient; + return { + defaults: { + entraSSO: { + userModelIdentity: 'user', + }, + }, + + initialize: function (cb) { + if (!sails.config.custom.entraClientId) { + return cb(); + } + + sails.log('Entra SSO enabled. The built-in authorization mechanism will be disabled.'); + + // Throw errors if required config variables are missing. + if(!sails.config.custom.entraTenantId){ + throw new Error(`Missing config! No sails.config.custom.entraTenantId was configured. To replace this app's built-in authorization mechanism with Entra SSO, an entraTenantId value is required.`); + } + if(!sails.config.custom.entraClientSecret){ + throw new Error(`Missing config! No sails.config.custom.entraClientSecret was configured. To replace this app's built-in authorization mechanism with Entra SSO, an entraClientSecret value is required.`); + } + + // [?]: https://learn.microsoft.com/en-us/entra/external-id/customers/tutorial-web-app-node-sign-in-sign-out#create-msal-configuration-object + // Configure the SSO client application. + entraSSOClient = new ConfidentialClientApplication({ + auth: { + clientId: sails.config.custom.entraClientId, + authority: `https://login.microsoftonline.com/${sails.config.custom.entraTenantId}`, + clientSecret: sails.config.custom.entraClientSecret, + }, + }); + + var err; + // Validate `userModelIdentity` config + if (typeof sails.config.entraSSO.userModelIdentity !== 'string') { + sails.config.entraSSO.userModelIdentity = 'user'; + } + sails.config.entraSSO.userModelIdentity = sails.config.entraSSO.userModelIdentity.toLowerCase(); + // We must wait for the `orm` hook before acquiring our user model from `sails.models` + // because it might not be ready yet. + if (!sails.hooks.orm) { + err = new Error(); + err.code = 'E_HOOK_INITIALIZE'; + err.name = 'Entra SSO Hook Error'; + err.message = 'The "Entra SSO" hook depends on the "orm" hook- cannot load the "Entra SSO" hook without it!'; + return cb(err); + } + sails.after('hook:orm:loaded', ()=>{ + + // Look up configured user model + var UserModel = sails.models[sails.config.entraSSO.userModelIdentity]; + + if (!UserModel) { + err = new Error(); + err.code = 'E_HOOK_INITIALIZE'; + err.name = 'Entra SSO Hook Error'; + err.message = 'Could not load the Entra SSO hook because `sails.config.passport.userModelIdentity` refers to an unknown model: "'+sails.config.entraSSO.userModelIdentity+'".'; + if (sails.config.entraSSO.userModelIdentity === 'user') { + err.message += '\nThis option defaults to `user` if unspecified or invalid- maybe you need to set or correct it?'; + } + return cb(err); + } + cb(); + }); + }, + + routes: { + before: { + '/login': async (req, res, next) => { + if (!sails.config.custom.entraClientId) { + return next(); + } + // Get the sso login url and redirect the user + // [?]: https://learn.microsoft.com/en-us/javascript/api/%40azure/msal-node/confidentialclientapplication?view=msal-js-latest#@azure-msal-node-confidentialclientapplication-getauthcodeurl + let entraAuthorizationUrl = await entraSSOClient.getAuthCodeUrl({ + redirectUri: `${sails.config.custom.baseUrl}/authorization-code/callback`, + scopes: ['openid', 'profile', 'email', 'User.Read'], + }); + // Redirect the user to the SSO login url. + res.redirect(entraAuthorizationUrl); + }, + '/authorization-code/callback': async (req, res, next) => { + if (!sails.config.custom.entraClientId) { + return next(); + } + // Make sure there is a code query string. + let codeToGetToken = req.query.code; + if(!codeToGetToken){ + res.unauthorized(); + } + // [?]: https://learn.microsoft.com/en-us/javascript/api/%40azure/msal-node/confidentialclientapplication?view=msal-js-latest#@azure-msal-node-confidentialclientapplication-acquiretokenbycode + let responseFromEntra = await entraSSOClient.acquireTokenByCode({ + code: codeToGetToken, + redirectUri: `${sails.config.custom.baseUrl}/authorization-code/callback`, + scopes: ['openid', 'profile', 'email', 'User.Read'], + }); + // Decode the accessToken in the response from Entra. + let decodedToken = jwt.decode(responseFromEntra.accessToken); + // Set the decoded token as the user's ssoUserInformation in their session. + req.session.ssoUserInformation = decodedToken; + // Redirect the user to the signup-sso-user-or-redirect endpoint. + res.redirect('/entrance/signup-sso-user-or-redirect'); // Note: This action handles signing in/up users who authenticate through Microsoft Entra. + }, + '/logout': async(req, res, next)=>{ + if (!sails.config.custom.entraClientId) { + return next(); + } + let logoutUri = `https://login.microsoftonline.com/${sails.config.custom.entraTenantId}/oauth2/v2.0/logout?post_logout_redirect_uri=${sails.config.custom.baseUrl}/`; + delete req.session.userId; + res.redirect(logoutUri); + }, + }, + }, + }; +}; diff --git a/ee/bulk-operations-dashboard/api/policies/is-logged-in.js b/ee/bulk-operations-dashboard/api/policies/is-logged-in.js index 0c03b1a689c1..7c38e54a4a53 100644 --- a/ee/bulk-operations-dashboard/api/policies/is-logged-in.js +++ b/ee/bulk-operations-dashboard/api/policies/is-logged-in.js @@ -16,6 +16,14 @@ module.exports = async function (req, res, proceed) { // > For more about where `req.me` comes from, check out this app's // > custom hook (`api/hooks/custom/index.js`). if (req.me) { + if(sails.config.custom.msalClientSecret){ + if (req.session.ssoUserInformation) { + let tokenExpiresAt = req.session.ssoUserInformation.exp * 1000; + if(tokenExpiresAt < Date.now() || req.session.ssoUserInformation.tid !== sails.config.custom.entraTenantId) { + return res.unauthorized(); + } + } + } return proceed(); } diff --git a/ee/bulk-operations-dashboard/config/custom.js b/ee/bulk-operations-dashboard/config/custom.js index a33b5d98cc84..e9b1e0967b23 100644 --- a/ee/bulk-operations-dashboard/config/custom.js +++ b/ee/bulk-operations-dashboard/config/custom.js @@ -107,4 +107,19 @@ module.exports.custom = { // [?] Here's how you get one: https://fleetdm.com/docs/using-fleet/fleetctl-cli#get-the-api-token-of-an-api-only-user // fleetApiToken: 'asdfasdfasdfasdf', + + /************************************************************************** + * * + * Entra SSO configuration * + * * + **************************************************************************/ + // entraTenantId: '...', // « The tenant ID of this application in the Microsoft Entra dashboard. + // entraClientId: '...', // « The Application (client) ID of this application in the Microsoft Entra dashboard. + // entraClientSecret: '...', //« The client secret for the application that has been created for this dashboard. + //-------------------------------------------------------------------------- + // /\ Configure these to replace the built-in authentication with Microsoft Entra SSO. + // || + //-------------------------------------------------------------------------- + + }; diff --git a/ee/bulk-operations-dashboard/config/routes.js b/ee/bulk-operations-dashboard/config/routes.js index 74c428effc8a..4f0b4b52f452 100644 --- a/ee/bulk-operations-dashboard/config/routes.js +++ b/ee/bulk-operations-dashboard/config/routes.js @@ -68,4 +68,5 @@ module.exports.routes = { 'POST /api/v1/software/edit-software': { action: 'software/edit-software' }, 'POST /api/v1/software/upload-software': { action: 'software/upload-software' }, 'GET /api/v1/get-labels': { action: 'get-labels' }, + 'GET /entrance/signup-sso-user-or-redirect': { action: 'entrance/signup-sso-user-or-redirect' }, }; diff --git a/ee/bulk-operations-dashboard/package.json b/ee/bulk-operations-dashboard/package.json index ca805a15af45..8c417fb867da 100644 --- a/ee/bulk-operations-dashboard/package.json +++ b/ee/bulk-operations-dashboard/package.json @@ -5,11 +5,13 @@ "description": "a Sails application", "keywords": [], "dependencies": { + "@azure/msal-node": "2.16.2", "@sailshq/connect-redis": "^6.1.3", "@sailshq/lodash": "^3.10.6", "@sailshq/socket.io-redis": "^6.1.2", "axios": "1.7.7", "form-data": "4.0.1", + "jsonwebtoken": "9.0.2", "sails": "^1.5.11", "sails-hook-apianalytics": "^2.0.6", "sails-hook-organics": "^2.2.2", diff --git a/ee/bulk-operations-dashboard/views/layouts/layout.ejs b/ee/bulk-operations-dashboard/views/layouts/layout.ejs index 6a8dba9199f7..e3ff9c8a89e4 100644 --- a/ee/bulk-operations-dashboard/views/layouts/layout.ejs +++ b/ee/bulk-operations-dashboard/views/layouts/layout.ejs @@ -57,6 +57,8 @@ Scripts Software <% if(me) { %> + + Account Sign out <% } else { %> Log in @@ -68,6 +70,7 @@ Configuration profiles Scripts <% if(me) { %> + Account Sign out <% } else { %> Log in diff --git a/ee/bulk-operations-dashboard/views/pages/account/account-overview.ejs b/ee/bulk-operations-dashboard/views/pages/account/account-overview.ejs index 2f5f3dc3d180..6035bbfd5300 100644 --- a/ee/bulk-operations-dashboard/views/pages/account/account-overview.ejs +++ b/ee/bulk-operations-dashboard/views/pages/account/account-overview.ejs @@ -1,5 +1,4 @@
-

My account


@@ -24,18 +23,18 @@ Unverified
-
-
+
+ -
+
Password:
••••••••••
diff --git a/ee/bulk-operations-dashboard/views/pages/account/edit-profile.ejs b/ee/bulk-operations-dashboard/views/pages/account/edit-profile.ejs index 75589f176da6..0d5ca7a6fc96 100644 --- a/ee/bulk-operations-dashboard/views/pages/account/edit-profile.ejs +++ b/ee/bulk-operations-dashboard/views/pages/account/edit-profile.ejs @@ -1,5 +1,4 @@
-

Update personal info


@@ -14,9 +13,10 @@
- - + +
Please enter a valid email address.
+ Changing email addresses is currently not supported when using SSO.
From 69cef7c20b1097143d5f19a700ec42fdb87b16fa Mon Sep 17 00:00:00 2001 From: Gabriel Hernandez Date: Fri, 3 Jan 2025 17:36:42 +0000 Subject: [PATCH 024/208] Chore UI more fixes scoped software label (#25118) relates to #25067, #25061 includes two fixes for the scoped software via labels feature: 1. update the delete label modal copy 2. don't show the confirm changes modal if only the self service value has changed. - [x] Manual QA for all new/changed functionality --- frontend/components/PlatformSelector/PlatformSelector.tsx | 4 ++-- frontend/interfaces/software.ts | 2 +- .../ConfirmSaveChangesModal/ConfirmSaveChangesModal.tsx | 4 +--- .../EditSoftwareModal/EditSoftwareModal.tsx | 8 +++----- .../pages/SoftwarePage/components/PackageForm/helpers.tsx | 4 ++++ .../components/DeleteLabelModal/DeleteLabelModal.tsx | 8 +++++--- frontend/pages/hosts/ManageHostsPage/helpers.ts | 2 +- 7 files changed, 17 insertions(+), 15 deletions(-) diff --git a/frontend/components/PlatformSelector/PlatformSelector.tsx b/frontend/components/PlatformSelector/PlatformSelector.tsx index 592193a6d0ab..6f87dd236841 100644 --- a/frontend/components/PlatformSelector/PlatformSelector.tsx +++ b/frontend/components/PlatformSelector/PlatformSelector.tsx @@ -72,8 +72,8 @@ export const PlatformSelector = ({
Your policy will only run on the selected platform(s). Additionally, if - install software automation is enabled, it will run only on hosts - defined in the software scope. + install software automation is enabled, it will only be installed on + hosts defined in the software scope.
); diff --git a/frontend/interfaces/software.ts b/frontend/interfaces/software.ts index 5f8797f27fb3..99470e4585df 100644 --- a/frontend/interfaces/software.ts +++ b/frontend/interfaces/software.ts @@ -82,7 +82,7 @@ export interface ISoftwarePackage { pending_uninstall: number; failed_uninstall: number; }; - automatic_install_policies?: ISoftwarePackagePolicy[]; + automatic_install_policies?: ISoftwarePackagePolicy[] | null; install_during_setup?: boolean; labels_include_any: ILabelSoftwareTitle[] | null; labels_exclude_any: ILabelSoftwareTitle[] | null; diff --git a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/ConfirmSaveChangesModal/ConfirmSaveChangesModal.tsx b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/ConfirmSaveChangesModal/ConfirmSaveChangesModal.tsx index d164f8dd8b89..ecc4bc8f8832 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/ConfirmSaveChangesModal/ConfirmSaveChangesModal.tsx +++ b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/ConfirmSaveChangesModal/ConfirmSaveChangesModal.tsx @@ -3,8 +3,6 @@ import React from "react"; import Button from "components/buttons/Button"; import Modal from "components/Modal"; -import { IPackageFormData } from "pages/SoftwarePage/components/PackageForm/PackageForm"; - const baseClass = "save-changes-modal"; export interface IConfirmSaveChangesModalProps { @@ -38,7 +36,7 @@ const ConfirmSaveChangesModal = ({

{warningText}

Installs or uninstalls currently running on a host will still - complete, but results won’t appear in Fleet. + complete, but results won't appear in Fleet.

You cannot undo this action.

diff --git a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditSoftwareModal/EditSoftwareModal.tsx b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditSoftwareModal/EditSoftwareModal.tsx index 98f2ee9bc69e..a7c69196906d 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditSoftwareModal/EditSoftwareModal.tsx +++ b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditSoftwareModal/EditSoftwareModal.tsx @@ -24,6 +24,7 @@ import { IPackageFormData } from "pages/SoftwarePage/components/PackageForm/Pack import { generateSelectedLabels, getCustomTarget, + getInstallType, getTargetType, } from "pages/SoftwarePage/components/PackageForm/helpers"; @@ -70,11 +71,7 @@ const EditSoftwareModal = ({ }); const [uploadProgress, setUploadProgress] = useState(0); - const { - data: labels, - isLoading: isLoadingLabels, - isError: isErrorLabels, - } = useQuery( + const { data: labels } = useQuery( ["custom_labels"], () => labelsAPI.summary().then((res) => getCustomLabels(res.labels)), { @@ -178,6 +175,7 @@ const EditSoftwareModal = ({ postInstallScript: software.post_install_script || "", uninstallScript: software.uninstall_script || "", selfService: software.self_service || false, + installType: getInstallType(software), targetType: getTargetType(software), customTarget: getCustomTarget(software), labelTargets: generateSelectedLabels(software), diff --git a/frontend/pages/SoftwarePage/components/PackageForm/helpers.tsx b/frontend/pages/SoftwarePage/components/PackageForm/helpers.tsx index b1b87262f698..8b18db4308eb 100644 --- a/frontend/pages/SoftwarePage/components/PackageForm/helpers.tsx +++ b/frontend/pages/SoftwarePage/components/PackageForm/helpers.tsx @@ -120,6 +120,10 @@ export const CUSTOM_TARGET_OPTIONS: IDropdownOption[] = [ }, ]; +export const getInstallType = (softwarePackage: ISoftwarePackage) => { + return softwarePackage.automatic_install_policies ? "automatic" : "manual"; +}; + export const getTargetType = (softwarePackage: ISoftwarePackage) => { if (!softwarePackage) return "All hosts"; diff --git a/frontend/pages/hosts/ManageHostsPage/components/DeleteLabelModal/DeleteLabelModal.tsx b/frontend/pages/hosts/ManageHostsPage/components/DeleteLabelModal/DeleteLabelModal.tsx index 8780026f1d1d..7bd4f7753186 100644 --- a/frontend/pages/hosts/ManageHostsPage/components/DeleteLabelModal/DeleteLabelModal.tsx +++ b/frontend/pages/hosts/ManageHostsPage/components/DeleteLabelModal/DeleteLabelModal.tsx @@ -26,11 +26,13 @@ const DeleteLabelModal = ({ <>

If a configuration profile uses this label as a custom target, the - profile will break: it won't be applied to new hosts. + profile will break. After deleting the label, remove broken profiles + and upload new profiles in their place.

- To apply the profile to new hosts, you'll have to delete it and - upload a new profile. + If software uses this label as a custom target, the label will not be + able to be deleted. Please remove the label from the software target + first before deleting.

+ An error occurred while generating your queries. Please reload this page and try again. + Generate queries + + +
+

Generated Query:

+
{{queryResult}}
+
Generate another
+
+ +
+
+
+<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %> From aac0ee734eef0bc81c2f2e16e9ee464d5cae8eb1 Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Wed, 8 Jan 2025 15:11:01 -0500 Subject: [PATCH 068/208] Roadmap preview, January 2025 (#25249) Quarterly roadmap blogpost ritual for Q1: https://github.com/fleetdm/fleet/issues/23523 --------- Co-authored-by: Drew Baker <89049099+Drew-P-drawers@users.noreply.github.com> --- articles/roadmap-preview-january-2025.md | 32 ++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 articles/roadmap-preview-january-2025.md diff --git a/articles/roadmap-preview-january-2025.md b/articles/roadmap-preview-january-2025.md new file mode 100644 index 000000000000..11a0f3075008 --- /dev/null +++ b/articles/roadmap-preview-january-2025.md @@ -0,0 +1,32 @@ +# Roadmap preview, January 2025 + +
+ +
+ +The Fleet roadmap is set for the first three months of 2025. Watch the video above for a walkthrough, or continue reading for the highlights. + +In the next 3 months, Fleet will ship... +- 🛡️ Integration with Microsoft compliance tools +- 🦾 Read-only GitOps mode +- 💝 Fleet-maintained apps for Windows +- ☑️ Integration with DigiCert +- 🎡 One queue for MDM commands, software, and scripts +- 📱 Android MDM for personal devices (BYOD) +- 🔍 More data for detection & response and threat hunting +- 🔄 Auto-patch software without writing custom policies +- 🧑‍💼 Identify provider host vitals + +Big opportunities that Fleet is building towards in the near future (next 180 days): +- 🍏 Account-based user enrollment for personal devices (BYOD) +- 🗓️ Native patching for apps and OS during maintenance windows +- 🤖 AI-generated osquery queries + +Any feedback or a questions? You can find us where we hang out in the [osquery // #fleet Slack channel](https://chat.osquery.io/c/fleet). + + + + + + + From 4479484333a05918836f642dfc154e00128b7a31 Mon Sep 17 00:00:00 2001 From: Sam Pfluger <108141731+Sampfluger88@users.noreply.github.com> Date: Wed, 8 Jan 2025 14:14:46 -0600 Subject: [PATCH 069/208] Add ADR position to website (#25259) --- handbook/company/open-positions.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/handbook/company/open-positions.yml b/handbook/company/open-positions.yml index e36e4740ac3c..90ccd33d8d5c 100644 --- a/handbook/company/open-positions.yml +++ b/handbook/company/open-positions.yml @@ -59,3 +59,29 @@ - 💖 An excellent understanding of macOS, Windows, Linux and core services like Autopilot, ABM/ASM, MDM, ADE, APNs, syslog, etc. - ✍️ Familiarity with SQLite, shell scripting, Python, Powershell, and using the terminal to execute commands or run scripts is a bonus. - 🧑‍🔬 Experience working with enterprise customers to help resolve complex technical issues. + +- jobTitle: 🫧 Account Development Representative + department: Demand + hiringManagerName: Drew Baker + hiringManagerGithubUsername: drew-p-drawers + hiringManagerLinkedInUrl: https://www.linkedin.com/in/andrew-baker-51547179/ + responsibilities: | + - 🔧 Manually research, collect, and organize CRM data. + - 📈 Track and analyze sales performance using CRM software and other tools + - 🤝 Work with internal teams to develop and maintain external relationships. + - 🧑‍💼 Represent Fleet and interact with the community at events and meetups as well as using multiple social media platforms. + - 🗣️ Identify and pursue new business opportunities through research and networking. + - 🦺 Help manage the flow of "planned" and "unplanned" work using multiple tools and ticketing systems. + - 📣 Collaborate with internal teams (sales, marketing, product) to ensure alignment and effective communication. + - 🧑‍💻 Work remotely, both within a team and individually, to help develop, document, and perform relevant responsibilities outlined at https://fleetdm.com/handbook/demand#responsibilities. + experience: | + - 🏃‍♂️ Strong desire to build a technical and operational-based skill set. + - 🚀 Detail-oriented, highly organized, and able to move quickly to solve complex problems using boring solutions. + - 🦉 Good understanding of Google Suite (Gmail, Google Calendar, Google Sheets, Google Docs, etc.) + - 🫀 Experience dealing with sensitive personal information of team members and customers. + - ✍️ Strong writing and oral communication for general and technical topics. + - 💭 Capable of understanding and translating technical concepts and personas. + - 🛠️ Ability to work in a process-driven team-based environment. + - 🟣 Openness: You are flexible and open to new ideas and ways of working. + - ➕ Bonus: Customer service\support background. + From 800aa7ecbddec3ea4e095e83c979d9080cf1183a Mon Sep 17 00:00:00 2001 From: RachelElysia <71795832+RachelElysia@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:35:48 -0500 Subject: [PATCH 070/208] Fleet UI: Fix software name from overflowing (#25262) --- changes/25072-software-name-overflow | 1 + frontend/components/FileDetails/_styles.scss | 1 + .../SoftwareTitleDetailsPage/DeleteSoftwareModal/_styles.scss | 3 +++ 3 files changed, 5 insertions(+) create mode 100644 changes/25072-software-name-overflow create mode 100644 frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/DeleteSoftwareModal/_styles.scss diff --git a/changes/25072-software-name-overflow b/changes/25072-software-name-overflow new file mode 100644 index 000000000000..f8a893f0ff98 --- /dev/null +++ b/changes/25072-software-name-overflow @@ -0,0 +1 @@ +* Fleet UI: Fixed software name overflow \ No newline at end of file diff --git a/frontend/components/FileDetails/_styles.scss b/frontend/components/FileDetails/_styles.scss index 718574db0681..265df6191a79 100644 --- a/frontend/components/FileDetails/_styles.scss +++ b/frontend/components/FileDetails/_styles.scss @@ -15,6 +15,7 @@ &__name { font-size: $x-small; font-weight: $bold; + overflow-wrap: anywhere; // Prevent long file name overflow } &__platform { diff --git a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/DeleteSoftwareModal/_styles.scss b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/DeleteSoftwareModal/_styles.scss new file mode 100644 index 000000000000..50a2b15fea24 --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/DeleteSoftwareModal/_styles.scss @@ -0,0 +1,3 @@ +.delete-software-modal { + overflow-wrap: anywhere; // Prevent long software name overflow +} From a3b52d6c7958d1a3073963bf68311a25b90acbf9 Mon Sep 17 00:00:00 2001 From: Josh Brower Date: Wed, 8 Jan 2025 17:06:43 -0500 Subject: [PATCH 071/208] Add missing CIS checks (#24787) PR for https://github.com/fleetdm/fleet/issues/24647 Adds: - 2.6.3.3: Ensure Improve Assistive Voice Features Is Disabled - 5.11: Ensure Logging Is Enabled for Sudo (Automated) 2.6.3.1, 2.6.3.2, 2.6.3.4 were previously added. 2.7.2. is a `Manual` check, which is not supported here. --------- Co-authored-by: Sharon Katz <121527325+sharon-fdm@users.noreply.github.com> --- ee/cis/macos-15/cis-policy-queries.yml | 45 ++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/ee/cis/macos-15/cis-policy-queries.yml b/ee/cis/macos-15/cis-policy-queries.yml index 6fd04bf6d953..5a04c084f8cb 100644 --- a/ee/cis/macos-15/cis-policy-queries.yml +++ b/ee/cis/macos-15/cis-policy-queries.yml @@ -1890,6 +1890,7 @@ spec: 4. Verify that Share Mac Analytics is not enabled 5. Verify that Share with App Developers is not enabled 6. Verify that Improve Siri & Dictation is not enabled + 7. Verify that Improve Assistive Voice Features is not enabled query: | SELECT 1 WHERE EXISTS ( @@ -1906,6 +1907,13 @@ spec: (value = 0 OR value = 'false') AND username = '' ) + AND EXISTS ( + SELECT 1 FROM managed_policies WHERE + domain='com.apple.Accessibility' AND + name='AXSAudioDonationSiriImprovementEnabled' AND + (value = 0 OR value = 'false') AND + username = '' + ) AND EXISTS ( SELECT 1 FROM managed_policies WHERE domain='com.apple.applicationaccess' AND @@ -3464,3 +3472,40 @@ spec: purpose: Informational tags: compliance, CIS, CIS_Level1 contributors: sharon-fdm +--- +apiVersion: v1 +kind: policy +spec: + name: CIS - Ensure Logging Is Enabled for Sudo (MDM Required) + platforms: macOS + platform: darwin + description: | + In order to properly monitor the use of the sudo command, logs events for any use of sudo should be captured in the unified log. + resolution: | + Ask your system administrator to deploy a script that will configure: + /usr/bin/sudo /usr/sbin/visudo -f /etc/sudoers + Remove the line, or comment out with # before the line, 'Defaults !log_allowed' + query: | + SELECT 1 + FROM file_lines + WHERE path = '/etc/sudoers' + AND ( + -- No matching line + NOT EXISTS ( + SELECT 1 + FROM file_lines + WHERE path = '/etc/sudoers' + AND line LIKE '%!log_allowed%' + ) + -- OR line exists but is commented + AND NOT EXISTS ( + SELECT 1 + FROM file_lines + WHERE path = '/etc/sudoers' + AND line LIKE '%#%Defaults%!log_allowed%' + ) + ) + LIMIT 1; + purpose: Informational + tags: compliance, CIS, CIS_Level1 + contributors: defensivedepth \ No newline at end of file From ba69f764813e586ee38587b3e94abe0a36c6915d Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Wed, 8 Jan 2025 16:10:17 -0600 Subject: [PATCH 072/208] Add cron failure monitoring SNS topic to IAM policy for Lambda (#25268) for #25267 This was missed in the TF config for the cron-monitoring Lambda updates, leading to a failure when trying to publish to the `#help-p2` topic. --- terraform/addons/monitoring/main.tf | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/terraform/addons/monitoring/main.tf b/terraform/addons/monitoring/main.tf index 57a3cb5ae3bd..7bd60c21af5e 100644 --- a/terraform/addons/monitoring/main.tf +++ b/terraform/addons/monitoring/main.tf @@ -448,7 +448,10 @@ data "aws_iam_policy_document" "cron_monitoring_lambda" { "sns:Publish" ] - resources = lookup(var.sns_topic_arns_map, "cron_monitoring", var.default_sns_topic_arns) + resources = distinct(concat( + lookup(var.sns_topic_arns_map, "cron_monitoring", var.default_sns_topic_arns), + lookup(var.sns_topic_arns_map, "cron_job_failure_monitoring", var.default_sns_topic_arns) + )) effect = "Allow" } From ec2a8660297d5fc6dfedcee38b4f95d5daab638b Mon Sep 17 00:00:00 2001 From: Harrison Ravazzolo <38767391+harrisonravazzolo@users.noreply.github.com> Date: Wed, 8 Jan 2025 14:13:46 -0800 Subject: [PATCH 073/208] Update proxy doc in reference architecture (#25230) Small change from a slack convo - https://fleetdm.slack.com/archives/C019WG4GH0A/p1736282697358929 Co-authored-by: Harrison John --- 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 70dc4303ede6..20e1b7702a5e 100644 --- a/docs/Deploy/Reference-Architectures.md +++ b/docs/Deploy/Reference-Architectures.md @@ -111,7 +111,7 @@ In order for osqueryd clients to connect, the connection to Fleet must use TLS. ## Using a proxy -If you are in an enterprise environment where Fleet is behind a proxy and you would like to be able to retrieve vulnerability data for [vulnerability processing](https://fleetdm.com/docs/using-fleet/vulnerability-processing#vulnerability-processing), it may be necessary to configure the proxy settings. Fleet automatically uses the `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` environment variables. +In enterprise environments where Fleet operates behind a proxy, you may need to configure proxy settings to enable services requiring outbound traffic, such as [vulnerability processing](https://fleetdm.com/docs/using-fleet/vulnerability-processing#vulnerability-processing) or [device management](https://fleetdm.com/device-management). Fleet automatically uses the `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` environment variables. For example, to configure the proxy in a systemd service file: From d9d96e28226e9d75e95f50aa3fa217a7d7f4f349 Mon Sep 17 00:00:00 2001 From: Allen Houchins <32207388+allenhouchins@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:17:36 -0600 Subject: [PATCH 074/208] Update teams.md (#24957) Minor changes to support fleetdm/confidential#9168 and https://github.com/fleetdm/fleet/pull/24952 --- articles/teams.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/articles/teams.md b/articles/teams.md index ea688bc08b0c..934be78511e7 100644 --- a/articles/teams.md +++ b/articles/teams.md @@ -20,7 +20,7 @@ Fleet's best practice teams: - `Compliance exclusions`: All contributors' test work computers or virtual machines (VMs). Used for validating workflows for Fleet customers or reproducing bugs in the Fleet product. - `📱🏢 Company-owned iPhones`: iPhones purchased by the organization that enroll to Fleet automatically via Apple Business Manager. For example, iPhones used by iOS Engineers. - `🔳🏢 Company-owned iPads`: iPads purchased by the organization that enroll to Fleet automatically via Apple Business Manager. For example, conference-room iPads. - +- `📱🔐 Personally-owned iPhones`: End users' personal iPhones, like those enrolled through a BYOD program, that have access to company resources. If some of your hosts don't fall under the above teams, what are these hosts for? The answer determines the the hosts' risk/compliance needs, and thus their security basline, and thus their "team" in Fleet. If the hosts' have a different compliance needs, and thus different security baseline, then it's time to create a new team in Fleet. From 679019e8c876a60879583eebcf23b9217dde9c78 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 8 Jan 2025 16:48:11 -0600 Subject: [PATCH 075/208] Website: add view changelog button to fleetsm.com/releases (#25269) Closes: https://github.com/fleetdm/confidential/issues/8824 Changes: - Added a "view changelog" button to fleetmd.com/releases that goes to https://github.com/fleetdm/fleet/releases --- .../styles/pages/articles/articles.less | 48 +++++++++++++++++++ website/views/pages/articles/articles.ejs | 3 ++ 2 files changed, 51 insertions(+) diff --git a/website/assets/styles/pages/articles/articles.less b/website/assets/styles/pages/articles/articles.less index 7d5fd9f09742..7971d4acc16a 100644 --- a/website/assets/styles/pages/articles/articles.less +++ b/website/assets/styles/pages/articles/articles.less @@ -32,6 +32,10 @@ } [purpose='category-title'] { padding-bottom: 40px; + margin-right: 25px; + p { + margin-bottom: 0px; + } } [purpose='guides-category-page'] { @@ -40,6 +44,36 @@ margin-right: 6px; } } + [purpose='changelog-button'] { + display: flex; + padding: var(--spacing-spacing-xxs, 4px) var(--spacing-spacing-sm, 16px); + justify-content: center; + align-items: center; + gap: var(--spacing-spacing-xxs, 4px); + border-radius: var(--spacing-spacing-xs, 8px); + border: 1px solid var(--color-grey-200, #E2E4EA); + background: var(--color-grey-50, #F9FAFC); + color: #515774; + text-align: center; + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 21px; + width: 168px; + img { + width: 16px; + height: 16px; + margin-right: 8px; + } + &:hover { + color: #515774; + text-decoration: none; + border-radius: var(--spacing-spacing-xs, 8px); + border: 1px solid var(--color-grey-400, #C5C7D1); + background: var(--color-grey-100, #F2F2F5); + } + } [purpose='rss-button'] { padding: 4px 8px; cursor: pointer; @@ -290,6 +324,10 @@ margin-left: 10px; margin-right: 10px; } + [purpose='category-title'] { + padding-bottom: 40px; + margin-right: 35px; + } [purpose='articles'] { [purpose='article-card'] { flex: 1 1 350px; @@ -304,6 +342,7 @@ } @media (min-width: 991px) { + [purpose='articles'] { [purpose='article-card'] { flex: 1 1 290px; @@ -316,6 +355,9 @@ [purpose='page-container'] { padding: 64px 32px; } + [purpose='category-title'] { + margin-right: 20px; + } [purpose='guide-card'] { max-width: unset; } @@ -358,6 +400,11 @@ width: 100%; } } + [purpose='category-title'] { + p { + margin-bottom: 16px; + } + } [purpose='guides'] { column-count: 2; } @@ -392,6 +439,7 @@ max-width: 100%; } } + [purpose='guide-card'] { width: 100%; } diff --git a/website/views/pages/articles/articles.ejs b/website/views/pages/articles/articles.ejs index f3cf3e267715..2c77fb33c120 100644 --- a/website/views/pages/articles/articles.ejs +++ b/website/views/pages/articles/articles.ejs @@ -22,6 +22,9 @@
+
From 992144bd5928b7244d956f8412f9dcb4a330ef81 Mon Sep 17 00:00:00 2001 From: Victor Lyuboslavsky Date: Wed, 8 Jan 2025 17:14:10 -0600 Subject: [PATCH 076/208] Downgraded expected/common "BootstrapPackage not found" server error to a debug message. (#25266) For #25265 Downgraded expected/common "BootstrapPackage not found" server error to a debug message. Occurs when UI/API checks if bootstrap package exists. # Checklist for submitter - [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/25265-boostrap-package-not-found | 1 + ee/server/service/software_installers.go | 3 ++- server/contexts/logging/logging.go | 19 ++++++++++++++++++- server/service/apple_mdm.go | 8 +++++++- server/service/integration_logger_test.go | 2 +- 5 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 changes/25265-boostrap-package-not-found diff --git a/changes/25265-boostrap-package-not-found b/changes/25265-boostrap-package-not-found new file mode 100644 index 000000000000..5089e6c29c0e --- /dev/null +++ b/changes/25265-boostrap-package-not-found @@ -0,0 +1 @@ +Downgraded expected/common "BootstrapPackage not found" server error to a debug message. Occurs when UI/API checks if bootstrap package exists. diff --git a/ee/server/service/software_installers.go b/ee/server/service/software_installers.go index 4bdba884a577..10d9c93edaa4 100644 --- a/ee/server/service/software_installers.go +++ b/ee/server/service/software_installers.go @@ -836,7 +836,8 @@ func (svc *Service) getSoftwareInstallerBinary(ctx context.Context, storageID st return nil, ctxerr.Wrap(ctx, err, "checking if installer exists") } if !exists { - return nil, ctxerr.Wrap(ctx, notFoundError{}, "does not exist in software installer store") + return nil, ctxerr.Wrapf(ctx, notFoundError{}, "%s with filename %s does not exist in software installer store", storageID, + filename) } // get the installer from the store diff --git a/server/contexts/logging/logging.go b/server/contexts/logging/logging.go index 8ef8236a272e..056f6a81ad87 100644 --- a/server/contexts/logging/logging.go +++ b/server/contexts/logging/logging.go @@ -53,6 +53,14 @@ func WithNoUser(ctx context.Context) context.Context { return ctx } +// WithNoError returns a context with logging.SkipError set to true so error won't be logged at level=error +func WithNoError(ctx context.Context) context.Context { + if logCtx, ok := FromContext(ctx); ok { + logCtx.SetSkipError() + } + return ctx +} + // WithExtras returns a context with logging.Extras set as the values provided func WithExtras(ctx context.Context, extras ...interface{}) context.Context { if logCtx, ok := FromContext(ctx); ok { @@ -61,6 +69,8 @@ func WithExtras(ctx context.Context, extras ...interface{}) context.Context { return ctx } +// WithLevel forces a log level for the current request/context. +// Level may still be upgraded to Error if an error is present. func WithLevel(ctx context.Context, level func(kitlog.Logger) kitlog.Logger) context.Context { if logCtx, ok := FromContext(ctx); ok { logCtx.SetForceLevel(level) @@ -77,6 +87,7 @@ type LoggingContext struct { Extras []interface{} SkipUser bool ForceLevel func(kitlog.Logger) kitlog.Logger + SkipError bool } func (l *LoggingContext) SetForceLevel(level func(kitlog.Logger) kitlog.Logger) { @@ -97,6 +108,12 @@ func (l *LoggingContext) SetSkipUser() { l.SkipUser = true } +func (l *LoggingContext) SetSkipError() { + l.l.Lock() + defer l.l.Unlock() + l.SkipError = true +} + func (l *LoggingContext) SetStartTime() { l.l.Lock() defer l.l.Unlock() @@ -115,7 +132,7 @@ func (l *LoggingContext) Log(ctx context.Context, logger kitlog.Logger) { defer l.l.Unlock() switch { - case len(l.Errs) > 0: + case len(l.Errs) > 0 && !l.SkipError: logger = level.Error(logger) case l.ForceLevel != nil: logger = l.ForceLevel(logger) diff --git a/server/service/apple_mdm.go b/server/service/apple_mdm.go index 50758be0cd87..0d2965c99713 100644 --- a/server/service/apple_mdm.go +++ b/server/service/apple_mdm.go @@ -2376,7 +2376,13 @@ func (r bootstrapPackageMetadataResponse) error() error { return r.Err } func bootstrapPackageMetadataEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) { req := request.(*bootstrapPackageMetadataRequest) meta, err := svc.GetMDMAppleBootstrapPackageMetadata(ctx, req.TeamID, req.ForUpdate) - if err != nil { + switch { + case fleet.IsNotFound(err): + // Don't log this response as error -- it's expected to happen when the bootstrap package is missing, which is a common case. + logging.WithNoError(ctx) + return bootstrapPackageMetadataResponse{Err: fleet.NewInvalidArgumentError("team_id", + "bootstrap package for this team does not exist").WithStatus(http.StatusNotFound)}, nil + case err != nil: return bootstrapPackageMetadataResponse{Err: err}, nil } return bootstrapPackageMetadataResponse{MDMAppleBootstrapPackage: meta}, nil diff --git a/server/service/integration_logger_test.go b/server/service/integration_logger_test.go index 6a6f7be93147..307801c09edb 100644 --- a/server/service/integration_logger_test.go +++ b/server/service/integration_logger_test.go @@ -148,7 +148,7 @@ func (s *integrationLoggerTestSuite) TestLoggerLogin() { require.NotContains(t, logData, "user") // logger context is set to skip user for _, e := range tt.expectedLogs { - assert.Equal(t, logData[e.key], e.val) + assert.Equal(t, e.val, logData[e.key], fmt.Sprintf("%+v", tt.expectedLogs)) } s.buf.Reset() } From 649f7b3052e7f6c0ec953efff0b7b91842a6ce32 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 8 Jan 2025 17:35:41 -0600 Subject: [PATCH 077/208] Website: Move `yaml` depenency (#25277) Changes: - Moved `yaml` from devDepenencies to dependencies in the website's package.json --- website/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/package.json b/website/package.json index d6029c1187a5..24fcd9199334 100644 --- a/website/package.json +++ b/website/package.json @@ -18,7 +18,8 @@ "sails-hook-orm": "^4.0.3", "sails-hook-sockets": "^3.0.0", "sails-postgresql": "^5.0.1", - "stripe": "17.3.1" + "stripe": "17.3.1", + "yaml": "1.10.2" }, "devDependencies": { "eslint": "5.16.0", @@ -26,8 +27,7 @@ "htmlhint": "0.11.0", "lesshint": "6.3.6", "marked": "4.0.10", - "sails-hook-grunt": "^5.0.0", - "yaml": "1.10.2" + "sails-hook-grunt": "^5.0.0" }, "scripts": { "custom-tests": "echo \"(No other custom tests yet.)\" && echo", From 69459efd1da8d247c7718ef7eff63385f4cf3d96 Mon Sep 17 00:00:00 2001 From: Sarah Gillespie <73313222+gillespi314@users.noreply.github.com> Date: Wed, 8 Jan 2025 17:41:26 -0600 Subject: [PATCH 078/208] Remove arrow icon from MDM solution table (#25211) --- changes/24962-ui-dashboard-mdm-solutions-table | 1 + .../DataTable/InternalLinkCell/InternalLinkCell.tsx | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 changes/24962-ui-dashboard-mdm-solutions-table diff --git a/changes/24962-ui-dashboard-mdm-solutions-table b/changes/24962-ui-dashboard-mdm-solutions-table new file mode 100644 index 000000000000..972a18bf8087 --- /dev/null +++ b/changes/24962-ui-dashboard-mdm-solutions-table @@ -0,0 +1 @@ +- Removed arrow icon from MDM solution table on dashboard page. diff --git a/frontend/components/TableContainer/DataTable/InternalLinkCell/InternalLinkCell.tsx b/frontend/components/TableContainer/DataTable/InternalLinkCell/InternalLinkCell.tsx index f4c360174233..7596767f113f 100644 --- a/frontend/components/TableContainer/DataTable/InternalLinkCell/InternalLinkCell.tsx +++ b/frontend/components/TableContainer/DataTable/InternalLinkCell/InternalLinkCell.tsx @@ -3,6 +3,7 @@ import classnames from "classnames"; import { noop } from "lodash"; import Icon from "components/Icon"; +import { IconNames } from "components/icons"; const baseClass = "internal-link-cell"; @@ -10,6 +11,13 @@ interface IInternalLinkCellProps { value: string; onClick?: () => void; className?: string; + /** iconName is the name of the icon that will be dislayed to the right + * of the text. + * + * NOTE: This component has not been tested with all icons. Callers should + * ensure that the specified icon displays properly. Some issue were observed + * with icon clipping, sizing etc. */ + iconName?: IconNames; } /** This cell is used when you want a clickable cell value that does not link @@ -23,6 +31,7 @@ const InternalLinkCell = ({ value, onClick = noop, className, + iconName, }: IInternalLinkCellProps) => { const classNames = classnames(baseClass, className); @@ -34,7 +43,7 @@ const InternalLinkCell = ({ */}
{value} - + {iconName && }
); From 20694617cff7b9bd66ee04f2b59a1a65362013fa Mon Sep 17 00:00:00 2001 From: Mike Thomas <78363703+mike-j-thomas@users.noreply.github.com> Date: Thu, 9 Jan 2025 08:55:47 +0900 Subject: [PATCH 079/208] Remove security marketing fluff from (#25279) I updated the orchestration page with @allenhouchins to remove marketing fluff for security users. Closes https://github.com/fleetdm/fleet/issues/24640 --- website/views/pages/observability.ejs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/views/pages/observability.ejs b/website/views/pages/observability.ejs index e5318882e123..4d867d4df041 100644 --- a/website/views/pages/observability.ejs +++ b/website/views/pages/observability.ejs @@ -234,14 +234,14 @@
Attack surface management -
Attack surface management
-

Discover security misconfigurations and vulnerabilities and prioritize risks that matter to your organization.

+
Security posture
+

Identify security misconfigurations and vulnerabilities and prioritize risks that matter to your organization.

Malware detection -
Malware detection
-

Continuously scan host filesystems for indicators of compromise (IOC). Import malware signatures from threat intelligence sources.

+
Threat management
+

Use attack signatures from threat intelligence sources to scan and resolve indicators of compromise (IOC).

From 60c4227b539a9c6618448fac0f289a55a532d3ec Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 8 Jan 2025 18:17:34 -0600 Subject: [PATCH 080/208] Website: add note about other agents to /better page (#25281) Closes: #24641 Changes: - Added a note about cybersecurity tools that might be installed on users devices to the /better page --- website/assets/styles/pages/transparency.less | 10 ++++++++-- website/views/pages/transparency.ejs | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/website/assets/styles/pages/transparency.less b/website/assets/styles/pages/transparency.less index 68d561ac880f..70daec8c8ef5 100644 --- a/website/assets/styles/pages/transparency.less +++ b/website/assets/styles/pages/transparency.less @@ -212,6 +212,13 @@ } + [purpose='agents-note'] { + color: #515774; + font-size: 16px; + line-height: 24px; + padding-top: 24px; + margin-bottom: 40px; + } [purpose='accordion'] { @@ -221,7 +228,6 @@ [purpose='accordion-item'] { border-bottom: 1px solid #E2E4EA; - padding-bottom: ; margin-top: 16px; margin-bottom: 16px; } @@ -295,6 +301,7 @@ text-align: center; margin-bottom: 0px; } + } @media (max-width: 767px) { @@ -303,7 +310,6 @@ padding-top: 40px; padding-bottom: 40px; } - [purpose='feature-row'] { flex-direction: row; align-items: start; diff --git a/website/views/pages/transparency.ejs b/website/views/pages/transparency.ejs index 2b1f5c3f1de8..7c769042ef7a 100644 --- a/website/views/pages/transparency.ejs +++ b/website/views/pages/transparency.ejs @@ -152,6 +152,7 @@

+

Fleet can't guarantee that your personal data isn't being tracked by other common cybersecurity tools like Veriato, CrowdStrike, SentinelOne, and Sophos.

From ed5698691862e8253af5961c0341a0b63db20d69 Mon Sep 17 00:00:00 2001 From: Janis Watts <184028114+jmwatts@users.noreply.github.com> Date: Wed, 8 Jan 2025 20:00:58 -0600 Subject: [PATCH 081/208] Update release-qa.md (#25273) On issue template, split tests into product groups, update dead link --- .github/ISSUE_TEMPLATE/release-qa.md | 130 +++++++++++++++------------ 1 file changed, 74 insertions(+), 56 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/release-qa.md b/.github/ISSUE_TEMPLATE/release-qa.md index ce35f1290dac..b29885ce1fe8 100644 --- a/.github/ISSUE_TEMPLATE/release-qa.md +++ b/.github/ISSUE_TEMPLATE/release-qa.md @@ -27,11 +27,12 @@ Smoke tests are limited to core functionality and serve as a pre-release final r ### Prerequisites -1. `fleetctl preview` is set up and running the desired test version using [`--tag` parameters.](https://github.com/fleetdm/fleet/blob/main/handbook/product.md#manual-qa ) +1. `fleetctl preview` is set up and running the desired test version using [`--tag` parameters.](https://fleetdm.com/handbook/engineering#run-fleet-locally-for-qa-purposes) 2. Unless you are explicitly testing older browser versions, browser is up to date. 3. Certificate & flagfile are in place to create new host. 4. In your browser, clear local storage using devtools. +### Orchestration @@ -53,20 +54,6 @@ Smoke tests are limited to core functionality and serve as a pre-release final r 3. forget password link prompts for email 4. valid credentials result in a successful login. 5. valid sso credentials result in a successful login - - - + +
Test nameStep instructionsExpected resultpass/fail
$Name{what a tester should do}{what a tester should see when they do that}pass/fail
pass/fail
Query flowCreate, edit, run, and delete queries. - -1. permissions regarding creating/editing/deleting queries are up to date with documentation -2. syntax errors result in error messaging -3. queries can be run manually -pass/fail
Host FlowVerify a new host can be added and removed following modal instructions using your own device. - -1. Host is added via command line -2. Host serial number and date added are accurate -3. Host is not visible after it is deleted -4. Warning and informational modals show when expected and make sense -pass/fail
Packs flowVerify management, operation, and logging of ["2017 packs"](https://fleetdm.com/handbook/company/why-this-way#why-does-fleet-support-query-packs). 1. Packs successfully run on host machines after migrations @@ -82,16 +69,17 @@ Smoke tests are limited to core functionality and serve as a pre-release final r 2. Software, query, policy, and packs logs are successfully sent to Filesystem log destinations pass/fail
OS settingsVerify OS settings functionality - -
My device pageVerify the end user's my device page loads successfully. - -1. Clicking the Fleet desktop item, then "My device" successfully loads the my device page. -2. The "My device" page is populated correctly and as expected. -3. Styling and padding appears correct. - +1. Verify able to configure Disk encryption (macOS, Windows, & Linux). +2. Verify host enrolled with Disk encryption enforced successfully encrypts. pass/fail
+### MDM + + + - - - - - +
Test nameStep instructionsExpected resultpass/fail
$Name{what a tester should do}{what a tester should see when they do that}pass/fail
MDM enrollment flowVerify MDM enrollments, run MDM commands 1. Erase an ADE-eligible macOS host and verify able to complete automated enrollment flow. @@ -106,45 +94,22 @@ Smoke tests are limited to core functionality and serve as a pre-release final r 3. Turn off MDM on a non ADE-eligible macOS host. 4. On the My device page, follow the "Turn on MDM" instructions and verify that MDM is turned on. pass/fail
ScriptsVerify script library and execution - -1. Verify able to run a script on all host types from CLI. -2. Verify scripts library upload/download/delete. -3. From Host details (macOS, Windows, & Linux) run a script that should PASS, verify. -4. From Host details (macOS, Windows, & Linux) run a script that should FAIL, verify. -5. Verify UI loading state and statuses for scripts. -6. Disable scripts globally and verify unable to run. -7. Verify scripts display correctly in Activity feed. -pass/fail
SoftwareVerify software library and install / download - -1. Verify software library upload/download/delete. -2. From Host details (macOS, Windows, & Linux) run an install that should PASS, verify. -3. From My Device (macOS, Windows, & Linux) software tab should have self-service items available, verify. -4. Verify UI loading state and statuses for installing software. -6. Verify software installs display correctly in Activity feed. -pass/fail
OS settingsVerify OS settings functionality -1. Verify able to configure Disk encryption (macOS, Windows, & Linux). -2. Verify host enrolled with Disk encryption enforced successfully encrypts. -3. Verify Profiles upload/download/delete (macOS & Windows). -4. Verify Profiles are delivered to host and applied. +1. Verify Profiles upload/download/delete (macOS & Windows). +2. Verify Profiles are delivered to host and applied. pass/fail
Setup experienceVerify macOS Setup experience 1. Configure End user authentication. -2. Upload a Bootstrap package. -3. Add software (FMA, VPP, & Custom pkg) -4. Add a script -5. Enroll an ADE-eligible macOS host and verify successful authentication. -6. Verify Bootstrap package is delivered. -7. Verify SwiftDialogue window displays. -8. Verify software installs and script runs. +3. Upload a Bootstrap package. +4. Add software (FMA, VPP, & Custom pkg) +5. Add a script +6. Enroll an ADE-eligible macOS host and verify successful authentication. +7. Verify Bootstrap package is delivered. +8. Verify SwiftDialogue window displays. +9. Verify software installs and script runs. pass/fail
OS updatesVerify OS updates flow @@ -161,6 +126,7 @@ Smoke tests are limited to core functionality and serve as a pre-release final r 3. Verify Profiles are delivered to host and applied. 4. Verify VPP apps install & display correctly in Activity feed. +pass/fail
Certificates UploadAPNs cert and ABM token renewal workflow 1. Renew APNs Certificate. @@ -168,18 +134,70 @@ Smoke tests are limited to core functionality and serve as a pre-release final r 3. Ensure ADE hosts can enroll. pass/fail
+ +### Software + + + + + + + + + + + - +
Test nameStep instructionsExpected resultpass/fail
$Name{what a tester should do}{what a tester should see when they do that}pass/fail
Query flowCreate, edit, run, and delete queries. + +1. permissions regarding creating/editing/deleting queries are up to date with documentation +2. syntax errors result in error messaging +3. queries can be run manually +pass/fail
Host FlowVerify a new host can be added and removed following modal instructions using your own device. + +1. Host is added via command line +2. Host serial number and date added are accurate +3. Host is not visible after it is deleted +4. Warning and informational modals show when expected and make sense +pass/fail
My device pageVerify the end user's my device page loads successfully. + +1. Clicking the Fleet desktop item, then "My device" successfully loads the my device page. +2. The "My device" page is populated correctly and as expected. +3. Styling and padding appears correct. + +pass/fail
ScriptsVerify script library and execution + +1. Verify able to run a script on all host types from CLI. +2. Verify scripts library upload/download/delete. +3. From Host details (macOS, Windows, & Linux) run a script that should PASS, verify. +4. From Host details (macOS, Windows, & Linux) run a script that should FAIL, verify. +5. Verify UI loading state and statuses for scripts. +8. Disable scripts globally and verify unable to run. +9. Verify scripts display correctly in Activity feed. +pass/fail
SoftwareVerify software library and install / download + +1. Verify software library upload/download/delete. +2. From Host details (macOS, Windows, & Linux) run an install that should PASS, verify. +3. From My Device (macOS, Windows, & Linux) software tab should have self-service items available, verify. +4. Verify UI loading state and statuses for installing software. +7. Verify software installs display correctly in Activity feed. +pass/fail
Migration TestVerify Fleet can migrate to the next version with no issues. Using the migration scripts located in fleet/test/upgrade/ 1. Run the upgrade_test.go script using the most recent stable version of Fleet and `main`. 2. Upgrade test returns an 'OK' response. pass/fail
+ +### All Product Groups + + + +
Test nameStep instructionsExpected resultpass/fail
$Name{what a tester should do}{what a tester should see when they do that}pass/fail
Release blockersVerify there are no outstanding release blocking tickets. 1. Check [this](https://github.com/fleetdm/fleet/labels/~release%20blocker) filter to view all open `~release blocker` tickets. 2. If any are found raise an alarm in the `#help-engineering` and `#g-mdm` (or `#g-endpoint-ops`) channels. -pass/fail
pass/fail
### Notes From 9ad246876cce451fb1422d8e3656abdb87f9ae37 Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Thu, 9 Jan 2025 10:00:22 -0600 Subject: [PATCH 082/208] Add YARA queries to osquery-perf (#25272) # Overview This PR adds support for remote YARA queries to osquery-perf, so that remote YARA queries can be load-tested. # Details The existing `runLiveQuery()` is updated to branch off into different query running functions based on the content of the query. If the query contains `from yara` and `sigurl`, then the new `runLiveYaraQuery()` function is run which makes a request to the Fleet "get yara rules" API before returning an appropriate response. Otherwise, the new `RunLiveMockQuery()` function is run which includes the previous logic for sending a mock response. # Testing I don't see any automated testing for osquery-perf, but I manually tested in the following way: 1. Started osquery-perf with `go run agent.go` 2. Ran a live query on the new host using ``` SELECT * FROM yara where sigurl="https://localhost:8080/api/osquery/yara/rule1.yar" ``` and verified that the result was as-expected: image I also used a log in Fleet to verify that the "get yara rules" API was really being called. 3. Ran another live query on the host using: ``` SELECT * FROM system_info" ``` and verified that the result was as expected: image I also tested that sending a `sigurl` with the wrong host returns a `live yara query failed because sigurl host did not match server address` error # Checklist for submitter - [X] Added support on fleet's osquery simulator `cmd/osquery-perf` for new osquery data ingestion features. --- cmd/osquery-perf/agent.go | 82 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/cmd/osquery-perf/agent.go b/cmd/osquery-perf/agent.go index 0671766fc2ed..388f08537a83 100644 --- a/cmd/osquery-perf/agent.go +++ b/cmd/osquery-perf/agent.go @@ -18,6 +18,7 @@ import ( "net/http" _ "net/http/pprof" "os" + "regexp" "sort" "strconv" "strings" @@ -2090,10 +2091,89 @@ func (a *agent) runLiveQuery(query string) (results []map[string]string, status ss := fleet.OsqueryStatus(1) return []map[string]string{}, &ss, ptr.String("live query failed with error foobar"), nil } - ss := fleet.OsqueryStatus(0) if a.liveQueryNoResultsProb > 0.0 && rand.Float64() <= a.liveQueryNoResultsProb { + ss := fleet.OsqueryStatus(0) return []map[string]string{}, &ss, nil, nil } + + // Switch based on contents of the query. + lcQuery := strings.ToLower(query) + switch { + case strings.Contains(lcQuery, "from yara") && strings.Contains(lcQuery, "sigurl"): + return a.runLiveYaraQuery(query) + default: + return a.runLiveMockQuery(query) + } +} + +func (a *agent) runLiveYaraQuery(query string) (results []map[string]string, status *fleet.OsqueryStatus, message *string, stats *fleet.Stats) { + // Get the URL of the YARA rule to request (i.e. the sigurl). + urlRegex := regexp.MustCompile(`sigurl=(["'])([^"']*)["']`) + matches := urlRegex.FindStringSubmatch(query) + var url string + if len(matches) > 2 { + url = matches[2] + } else { + ss := fleet.OsqueryStatus(1) + return []map[string]string{}, &ss, ptr.String("live yara query failed because a valid sigurl could not be found"), nil + } + + // Osquery validates that the sigurl is one of a configured set, so that it's not + // sending requests to just anywhere. We'll check that it's at least the same host + // as the Fleet server. + if !strings.HasPrefix(url, a.serverAddress) { + ss := fleet.OsqueryStatus(1) + return []map[string]string{}, &ss, ptr.String("live yara query failed because sigurl host did not match server address"), nil + } + + // Make the request. + body := []byte(`{"node_key": "` + a.nodeKey + `"}`) + request, err := http.NewRequest("POST", url, bytes.NewReader(body)) + if err != nil { + ss := fleet.OsqueryStatus(1) + return []map[string]string{}, &ss, ptr.String("live yara query failed due to error creating request"), nil + } + request.Header.Add("Content-type", "application/json") + + // Make the request. + response, err := http.DefaultClient.Do(request) + if err != nil { + ss := fleet.OsqueryStatus(1) + return []map[string]string{}, &ss, ptr.String(fmt.Sprintf("yara request failed to run: %v", err)), nil + } + defer response.Body.Close() + + // For load testing purposes we don't actually care about the response, but check that we at least got one. + if _, err := io.Copy(io.Discard, response.Body); err != nil { + ss := fleet.OsqueryStatus(1) + return []map[string]string{}, &ss, ptr.String(fmt.Sprintf("error reading response from yara API: %v", err)), nil + } + + // Return a response indicating that the file is clean. + ss := fleet.OsqueryStatus(0) + return []map[string]string{ + { + "count": "0", + "matches": "", + "strings": "", + "tags": "", + "sig_group": "", + "sigfile": "", + "sigrule": "", + "sigurl": url, + // Could pull this from the query, but not necessary for load testing. + "path": "/some/path", + }, + }, &ss, nil, &fleet.Stats{ + WallTimeMs: uint64(rand.Intn(1000) * 1000), + UserTime: uint64(rand.Intn(1000)), + SystemTime: uint64(rand.Intn(1000)), + Memory: uint64(rand.Intn(1000)), + } +} + +func (a *agent) runLiveMockQuery(query string) (results []map[string]string, status *fleet.OsqueryStatus, message *string, stats *fleet.Stats) { + ss := fleet.OsqueryStatus(0) return []map[string]string{ { "admindir": "/var/lib/dpkg", From 7ceb5bcac4d13b96df6da95bf92fd7977087164e Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 9 Jan 2025 10:25:46 -0600 Subject: [PATCH 083/208] Website: add contributor information to policy details page. (#25275) Closes: #24454 Changes: - Updated the policy details page to include the policy's contributor --- website/assets/styles/pages/query-detail.less | 27 +++++++++++++++++++ website/views/pages/query-detail.ejs | 5 ++++ 2 files changed, 32 insertions(+) diff --git a/website/assets/styles/pages/query-detail.less b/website/assets/styles/pages/query-detail.less index 8026d382f41b..3e56e7d5add3 100644 --- a/website/assets/styles/pages/query-detail.less +++ b/website/assets/styles/pages/query-detail.less @@ -155,6 +155,33 @@ line-height: 150%; margin-bottom: 0px; padding: 16px 0px 32px 0px; + } + [purpose='policy-attribution'] { + display: flex; + flex-direction: row; + align-items: center; + padding-top: 8px; + padding-bottom: 16px; + [purpose='contributor-profile-picture'] { + width: 28.8px; + height: 28.8px; + margin-right: 8px; + border-radius: 50%; + } + [purpose='contributor-profile-name'] { + font-size: 14px; + line-height: 150%; + } + [purpose='policy-link'] { + color: unset; + text-decoration: none; + + &:hover { + text-decoration: none; + } + } + + } [purpose='policy-details'] { padding-right: 64px; diff --git a/website/views/pages/query-detail.ejs b/website/views/pages/query-detail.ejs index d4cccec5bb19..86d3d053d8e0 100644 --- a/website/views/pages/query-detail.ejs +++ b/website/views/pages/query-detail.ejs @@ -30,6 +30,11 @@

<%- query.name %>

+

<%- query.description %>

- - -Fleet allows you to update the settings of the agent installed on all your hosts at once. In Fleet, these settings are called "agent options." - -The default agent options are good to start. - -How to update agent options: - -1. In the top navigation, select your avatar and select **Settings**. Only users with the [admin role](https://fleetdm.com/docs/using-fleet/permissions) can access the pages in **Settings**. - -2. On the Organization settings page, select **Agent options** on the left side of the page. - -3. Use Fleet's YAML editor to configure your osquery options, decorators, or set command line flags. +You can use Fleet to query local SQLite databases as tables. For more information on creating ATC configuration from a SQLite database, check out the [Automatic Table Construction section](https://osquery.readthedocs.io/en/stable/deployment/configuration/#automatic-table-construction) of the osquery documentation. -4. Place your new setting one level below the `options` key. The new setting's key should be below and one tab to the right of `options`. +## script_execution_timeout -5. Select **Save**. +The `script_execution_timeout` allows you to change the default script execution timeout (default: `300`, maximum: `3600`). -The agents may take several seconds to update because Fleet has to wait for the hosts to check in. Additionally, hosts enrolled with removed enroll secrets must properly rotate their secret to have the new changes take effect. +#### Example -> When configuring a value for [`script_execution_timeout`](https://fleetdm.com/docs/configuration/agent-configuration#script-execution-timeout) in the UI, make sure to put the key at the top level of the YAML, _not_ as a child of `config`. +```yaml +agent_options: + script_execution_timeout: 600 +``` From 863a37a3e5f11a214931959d19dfe0723448c006 Mon Sep 17 00:00:00 2001 From: Jahziel Villasana-Espinoza Date: Thu, 9 Jan 2025 14:22:21 -0500 Subject: [PATCH 096/208] fix: update install script for FMAs to improve re-install process (#25238) > For #24148 # 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] 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] Confirmed that updating the timestamps is acceptable, and will not cause unwanted side effects. - [x] Ensured the correct collation is explicitly set for character columns (`COLLATE utf8mb4_unicode_ci`). - [x] Added/updated automated tests - [x] A detailed QA plan exists on the associated ticket (if it isn't there, work with the product group's QA engineer to add it) - [x] Manual QA for all new/changed functionality --- changes/24148-re-install-fma | 1 + .../20250109150150_UpdateFMAInstallScripts.go | 143 +++++++++++++++ ...0109150150_UpdateFMAInstallScripts_test.go | 165 ++++++++++++++++++ server/datastore/mysql/schema.sql | 4 +- server/mdm/maintainedapps/scripts.go | 9 +- .../scripts/1password_install.golden.sh | 41 +++++ .../scripts/brave-browser_install.golden.sh | 41 +++++ .../testdata/scripts/docker_install.golden.sh | 41 +++++ .../testdata/scripts/figma_install.golden.sh | 41 +++++ .../scripts/firefox_install.golden.sh | 41 +++++ .../scripts/google-chrome_install.golden.sh | 41 +++++ .../testdata/scripts/notion_install.golden.sh | 41 +++++ .../scripts/postman_install.golden.sh | 41 +++++ .../testdata/scripts/slack_install.golden.sh | 41 +++++ .../visual-studio-code_install.golden.sh | 41 +++++ .../scripts/whatsapp_install.golden.sh | 41 +++++ 16 files changed, 770 insertions(+), 3 deletions(-) create mode 100644 changes/24148-re-install-fma create mode 100644 server/datastore/mysql/migrations/tables/20250109150150_UpdateFMAInstallScripts.go create mode 100644 server/datastore/mysql/migrations/tables/20250109150150_UpdateFMAInstallScripts_test.go diff --git a/changes/24148-re-install-fma b/changes/24148-re-install-fma new file mode 100644 index 000000000000..e85f9fcb9861 --- /dev/null +++ b/changes/24148-re-install-fma @@ -0,0 +1 @@ +- Updated Fleet-maintained app install scripts for non-PKG-based installers to allow the apps to be installed over an existing installation. \ No newline at end of file diff --git a/server/datastore/mysql/migrations/tables/20250109150150_UpdateFMAInstallScripts.go b/server/datastore/mysql/migrations/tables/20250109150150_UpdateFMAInstallScripts.go new file mode 100644 index 000000000000..1238d6efd522 --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20250109150150_UpdateFMAInstallScripts.go @@ -0,0 +1,143 @@ +package tables + +import ( + "database/sql" + "errors" + "fmt" + "slices" + "strings" + + "github.com/jmoiron/sqlx" + "github.com/jmoiron/sqlx/reflectx" +) + +func init() { + MigrationClient.AddMigration(Up_20250109150150, Down_20250109150150) +} + +const quitApplicationFunc = ` + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} +` + +func Up_20250109150150(tx *sql.Tx) error { + var scriptsToModify []struct { + InstallScriptContents string `db:"contents"` + AppName string `db:"name"` + BundleID string `db:"bundle_identifier"` + ScriptContentID uint `db:"script_content_id"` + Token string `db:"token"` + } + + // Note: we're not updating any install scripts that have been edited by users, only the + // "original" script contents for FMAs that are created when the fleet_library_apps table is populated. + selectStmt := ` +SELECT + sc.contents AS contents, + fla.name AS name, + fla.bundle_identifier AS bundle_identifier, + sc.id AS script_content_id, + fla.token AS token +FROM + fleet_library_apps fla + JOIN script_contents sc + ON fla.install_script_content_id = sc.id +WHERE fla.token IN (?) +` + + // This is the list of Fleet-maintained apps we want to update ("token" is an ID found on the brew + // metadata) + appTokens := []string{"1password", "brave-browser", "docker", "figma", "google-chrome", "visual-studio-code", "firefox", "notion", "slack", "whatsapp", "postman"} + + stmt, args, err := sqlx.In(selectStmt, appTokens) + if err != nil { + return fmt.Errorf("building SQL in statement for selecting fleet maintained apps: %w", err) + } + + txx := sqlx.Tx{Tx: tx, Mapper: reflectx.NewMapperFunc("db", sqlx.NameMapper)} + if err := txx.Select(&scriptsToModify, stmt, args...); err != nil { + // if this migration is running on a brand-new Fleet deployment, then there won't be + // anything in the fleet_library_apps table, so we can just exit. + if errors.Is(err, sql.ErrNoRows) { + return nil + } + + return fmt.Errorf("selecting script contents: %w", err) + } + + for _, sc := range scriptsToModify { + lines := strings.Split(sc.InstallScriptContents, "\n") + // Find the line where we copy the new .app file into the Applications folder. We want to + // add our changes right before that line. + var copyLineNumber int + for i, l := range lines { + if strings.Contains(l, `sudo cp -R "$TMPDIR/`) { + copyLineNumber = i + break + } + } + + appFileName := fmt.Sprintf("%s.app", sc.AppName) + if sc.Token == "visual-studio-code" { + // VSCode has the name "Microsoft Visual Studio Code" in fleet_library_apps, but the + // .app name is "Visual Studio Code.app", so account for that here. + appFileName = "Visual Studio Code.app" + } + + // This line will move the old version of the .app (if it exists) to the temporary directory + lines = slices.Insert(lines, copyLineNumber, fmt.Sprintf(`sudo [ -d "$APPDIR/%[1]s" ] && sudo mv "$APPDIR/%[1]s" "$TMPDIR/%[1]s.bkp"`, appFileName)) + // Add a call to our "quit_application" function + lines = slices.Insert(lines, copyLineNumber, fmt.Sprintf("quit_application %s", sc.BundleID)) + // Add the "quit_application" function to the script + lines = slices.Insert(lines, 2, quitApplicationFunc) + + updatedScript := strings.Join(lines, "\n") + + checksum := md5ChecksumScriptContent(updatedScript) + + if _, err = tx.Exec(`UPDATE script_contents SET contents = ?, md5_checksum = UNHEX(?) WHERE id = ?`, strings.Join(lines, "\n"), checksum, sc.ScriptContentID); err != nil { + return fmt.Errorf("updating fma install script contents: %w", err) + } + } + + return nil +} + +func Down_20250109150150(tx *sql.Tx) error { + return nil +} diff --git a/server/datastore/mysql/migrations/tables/20250109150150_UpdateFMAInstallScripts_test.go b/server/datastore/mysql/migrations/tables/20250109150150_UpdateFMAInstallScripts_test.go new file mode 100644 index 000000000000..c319cbfe7c9b --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20250109150150_UpdateFMAInstallScripts_test.go @@ -0,0 +1,165 @@ +package tables + +import ( + "testing" + + "github.com/jmoiron/sqlx" + "github.com/jmoiron/sqlx/reflectx" + "github.com/stretchr/testify/require" +) + +func TestUp_20250109150150(t *testing.T) { + db := applyUpToPrev(t) + + // + // Insert data to test the migration + // + // ... + originalContents := ` +#!/bin/sh + +# variables +APPDIR="/Applications/" +TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") + +# extract contents +unzip "$INSTALLER_PATH" -d "$TMPDIR" +# copy to the applications folder +sudo cp -R "$TMPDIR/Figma.app" "$APPDIR" + ` + + tx, err := db.Begin() + require.NoError(t, err) + txx := sqlx.Tx{Tx: tx, Mapper: reflectx.NewMapperFunc("db", sqlx.NameMapper)} + installScriptID, err := getOrInsertScript(txx, originalContents) + require.NoError(t, err) + uninstallScriptID, err := getOrInsertScript(txx, "echo uninstall") + require.NoError(t, err) + boxInstallScriptID, err := getOrInsertScript(txx, "echo install") + require.NoError(t, err) + boxUninstallScriptID, err := getOrInsertScript(txx, "echo uninstall") + require.NoError(t, err) + err = tx.Commit() + require.NoError(t, err) + + // Insert Figma (one of our target FMAs) + execNoErr( + t, + db, + `INSERT INTO fleet_library_apps (name, token, version, platform, installer_url, sha256, bundle_identifier, install_script_content_id, uninstall_script_content_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + "Figma", + "figma", + "124.7.4", + "darwin", + "https://desktop.figma.com/mac-arm/Figma-124.7.4.zip", + "3160c0cac00b8b81b7b62375f04b9598b11cbd9e5d42a5ad532e8b98fecc6b15", + "com.figma.Desktop", + installScriptID, + uninstallScriptID, + ) + + // Insert Box Drive, should be unaffected + execNoErr( + t, + db, + `INSERT INTO fleet_library_apps (name, token, version, platform, installer_url, sha256, bundle_identifier, install_script_content_id, uninstall_script_content_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + "Box Drive", + "box-drive", + "2.42.212", + "darwin", + "https://e3.boxcdn.net/desktop/releases/mac/BoxDrive-2.42.212.pkg", + "93550756150c434bc058c30b82352c294a21e978caf436ac99e0a5f431adfb6e", + "com.box.desktop", + boxInstallScriptID, + boxUninstallScriptID, + ) + + // Apply current migration. + applyNext(t, db) + + // + // Check data, insert new entries, e.g. to verify migration is safe. + // + // ... + var scriptContents struct { + InstallScriptContents string `db:"contents"` + Checksum string `db:"md5_checksum"` + } + + selectStmt := ` +SELECT + sc.contents AS contents, + HEX(sc.md5_checksum) AS md5_checksum +FROM + fleet_library_apps fla + JOIN script_contents sc + ON fla.install_script_content_id = sc.id +WHERE fla.token = ?` + + err = sqlx.Get(db, &scriptContents, selectStmt, "figma") + require.NoError(t, err) + + expectedContents := ` +#!/bin/sh + + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} + + +# variables +APPDIR="/Applications/" +TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") + +# extract contents +unzip "$INSTALLER_PATH" -d "$TMPDIR" +# copy to the applications folder +quit_application com.figma.Desktop +sudo [ -d "$APPDIR/Figma.app" ] && sudo mv "$APPDIR/Figma.app" "$TMPDIR/Figma.app.bkp" +sudo cp -R "$TMPDIR/Figma.app" "$APPDIR" + ` + + expectedChecksum := md5ChecksumScriptContent(expectedContents) + + require.Equal(t, expectedContents, scriptContents.InstallScriptContents) + require.Equal(t, expectedChecksum, scriptContents.Checksum) + + err = sqlx.Get(db, &scriptContents, selectStmt, "box-drive") + require.NoError(t, err) + require.Equal(t, "echo install", scriptContents.InstallScriptContents) + require.Equal(t, md5ChecksumScriptContent("echo install"), scriptContents.Checksum) +} diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql index 50b32b038c83..aca88bc49ec7 100644 --- a/server/datastore/mysql/schema.sql +++ b/server/datastore/mysql/schema.sql @@ -1111,9 +1111,9 @@ CREATE TABLE `migration_status_tables` ( `is_applied` tinyint(1) NOT NULL, `tstamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) -) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=347 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=348 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; -INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20241002104104,1,'2020-01-01 01:01:01'),(314,20241002104105,1,'2020-01-01 01:01:01'),(315,20241002104106,1,'2020-01-01 01:01:01'),(316,20241002210000,1,'2020-01-01 01:01:01'),(317,20241003145349,1,'2020-01-01 01:01:01'),(318,20241004005000,1,'2020-01-01 01:01:01'),(319,20241008083925,1,'2020-01-01 01:01:01'),(320,20241009090010,1,'2020-01-01 01:01:01'),(321,20241017163402,1,'2020-01-01 01:01:01'),(322,20241021224359,1,'2020-01-01 01:01:01'),(323,20241022140321,1,'2020-01-01 01:01:01'),(324,20241025111236,1,'2020-01-01 01:01:01'),(325,20241025112748,1,'2020-01-01 01:01:01'),(326,20241025141855,1,'2020-01-01 01:01:01'),(327,20241110152839,1,'2020-01-01 01:01:01'),(328,20241110152840,1,'2020-01-01 01:01:01'),(329,20241110152841,1,'2020-01-01 01:01:01'),(330,20241116233322,1,'2020-01-01 01:01:01'),(331,20241122171434,1,'2020-01-01 01:01:01'),(332,20241125150614,1,'2020-01-01 01:01:01'),(333,20241203125346,1,'2020-01-01 01:01:01'),(334,20241203130032,1,'2020-01-01 01:01:01'),(335,20241205122800,1,'2020-01-01 01:01:01'),(336,20241209164540,1,'2020-01-01 01:01:01'),(337,20241210140021,1,'2020-01-01 01:01:01'),(338,20241219180042,1,'2020-01-01 01:01:01'),(339,20241220100000,1,'2020-01-01 01:01:01'),(340,20241220114903,1,'2020-01-01 01:01:01'),(341,20241220114904,1,'2020-01-01 01:01:01'),(342,20241224000000,1,'2020-01-01 01:01:01'),(343,20241230000000,1,'2020-01-01 01:01:01'),(344,20241231112624,1,'2020-01-01 01:01:01'),(345,20250102121439,1,'2020-01-01 01:01:01'),(346,20250107165731,1,'2020-01-01 01:01:01'); +INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20241002104104,1,'2020-01-01 01:01:01'),(314,20241002104105,1,'2020-01-01 01:01:01'),(315,20241002104106,1,'2020-01-01 01:01:01'),(316,20241002210000,1,'2020-01-01 01:01:01'),(317,20241003145349,1,'2020-01-01 01:01:01'),(318,20241004005000,1,'2020-01-01 01:01:01'),(319,20241008083925,1,'2020-01-01 01:01:01'),(320,20241009090010,1,'2020-01-01 01:01:01'),(321,20241017163402,1,'2020-01-01 01:01:01'),(322,20241021224359,1,'2020-01-01 01:01:01'),(323,20241022140321,1,'2020-01-01 01:01:01'),(324,20241025111236,1,'2020-01-01 01:01:01'),(325,20241025112748,1,'2020-01-01 01:01:01'),(326,20241025141855,1,'2020-01-01 01:01:01'),(327,20241110152839,1,'2020-01-01 01:01:01'),(328,20241110152840,1,'2020-01-01 01:01:01'),(329,20241110152841,1,'2020-01-01 01:01:01'),(330,20241116233322,1,'2020-01-01 01:01:01'),(331,20241122171434,1,'2020-01-01 01:01:01'),(332,20241125150614,1,'2020-01-01 01:01:01'),(333,20241203125346,1,'2020-01-01 01:01:01'),(334,20241203130032,1,'2020-01-01 01:01:01'),(335,20241205122800,1,'2020-01-01 01:01:01'),(336,20241209164540,1,'2020-01-01 01:01:01'),(337,20241210140021,1,'2020-01-01 01:01:01'),(338,20241219180042,1,'2020-01-01 01:01:01'),(339,20241220100000,1,'2020-01-01 01:01:01'),(340,20241220114903,1,'2020-01-01 01:01:01'),(341,20241220114904,1,'2020-01-01 01:01:01'),(342,20241224000000,1,'2020-01-01 01:01:01'),(343,20241230000000,1,'2020-01-01 01:01:01'),(344,20241231112624,1,'2020-01-01 01:01:01'),(345,20250102121439,1,'2020-01-01 01:01:01'),(346,20250107165731,1,'2020-01-01 01:01:01'),(347,20250109150150,1,'2020-01-01 01:01:01'); /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `mobile_device_management_solutions` ( diff --git a/server/mdm/maintainedapps/scripts.go b/server/mdm/maintainedapps/scripts.go index e5b5da70059f..43fd715ddd91 100644 --- a/server/mdm/maintainedapps/scripts.go +++ b/server/mdm/maintainedapps/scripts.go @@ -19,11 +19,15 @@ func installScriptForApp(app maintainedApp, cask *brewCask) (string, error) { formats := strings.Split(app.InstallerFormat, ":") sb.Extract(formats[0]) + var includeQuitFunc bool for _, artifact := range cask.Artifacts { switch { case len(artifact.App) > 0: sb.Write("# copy to the applications folder") + sb.Writef("quit_application '%s'", app.BundleIdentifier) + includeQuitFunc = true for _, appPath := range artifact.App { + sb.Writef(`sudo [ -d "$APPDIR/%[1]s" ] && sudo mv "$APPDIR/%[1]s" "$TMPDIR/%[1]s.bkp"`, appPath) sb.Copy(appPath, "$APPDIR") } @@ -55,6 +59,10 @@ func installScriptForApp(app maintainedApp, cask *brewCask) (string, error) { } } + if includeQuitFunc { + sb.AddFunction("quit_application", quitApplicationFunc) + } + return sb.String(), nil } @@ -252,7 +260,6 @@ func (s *scriptBuilder) Writef(format string, args ...any) { // Supported formats are "dmg" and "zip". It adds the necessary extraction // commands to the script. func (s *scriptBuilder) Extract(format string) { - switch format { case "dmg": s.Write("# extract contents") diff --git a/server/mdm/maintainedapps/testdata/scripts/1password_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/1password_install.golden.sh index 892fe48d9e3d..3655b01afce2 100644 --- a/server/mdm/maintainedapps/testdata/scripts/1password_install.golden.sh +++ b/server/mdm/maintainedapps/testdata/scripts/1password_install.golden.sh @@ -3,8 +3,49 @@ # variables APPDIR="/Applications/" TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") +# functions + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} + # extract contents unzip "$INSTALLER_PATH" -d "$TMPDIR" # copy to the applications folder +quit_application 'com.1password.1password' +sudo [ -d "$APPDIR/1Password.app" ] && sudo mv "$APPDIR/1Password.app" "$TMPDIR/1Password.app.bkp" sudo cp -R "$TMPDIR/1Password.app" "$APPDIR" diff --git a/server/mdm/maintainedapps/testdata/scripts/brave-browser_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/brave-browser_install.golden.sh index 4dae8ce08582..632e4313da29 100644 --- a/server/mdm/maintainedapps/testdata/scripts/brave-browser_install.golden.sh +++ b/server/mdm/maintainedapps/testdata/scripts/brave-browser_install.golden.sh @@ -3,6 +3,45 @@ # variables APPDIR="/Applications/" TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") +# functions + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} + # extract contents MOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX) @@ -10,4 +49,6 @@ hdiutil attach -plist -nobrowse -readonly -mountpoint "$MOUNT_POINT" "$INSTALLER sudo cp -R "$MOUNT_POINT"/* "$TMPDIR" hdiutil detach "$MOUNT_POINT" # copy to the applications folder +quit_application 'com.brave.Browser' +sudo [ -d "$APPDIR/Brave Browser.app" ] && sudo mv "$APPDIR/Brave Browser.app" "$TMPDIR/Brave Browser.app.bkp" sudo cp -R "$TMPDIR/Brave Browser.app" "$APPDIR" diff --git a/server/mdm/maintainedapps/testdata/scripts/docker_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/docker_install.golden.sh index 4d6dd3f76e06..dc2c755afbc2 100644 --- a/server/mdm/maintainedapps/testdata/scripts/docker_install.golden.sh +++ b/server/mdm/maintainedapps/testdata/scripts/docker_install.golden.sh @@ -3,6 +3,45 @@ # variables APPDIR="/Applications/" TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") +# functions + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} + # extract contents MOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX) @@ -10,6 +49,8 @@ hdiutil attach -plist -nobrowse -readonly -mountpoint "$MOUNT_POINT" "$INSTALLER sudo cp -R "$MOUNT_POINT"/* "$TMPDIR" hdiutil detach "$MOUNT_POINT" # copy to the applications folder +quit_application 'com.docker.docker' +sudo [ -d "$APPDIR/Docker.app" ] && sudo mv "$APPDIR/Docker.app" "$TMPDIR/Docker.app.bkp" sudo cp -R "$TMPDIR/Docker.app" "$APPDIR" /bin/ln -h -f -s -- "$APPDIR/Docker.app/Contents/Resources/bin/docker" "/usr/local/bin/docker" /bin/ln -h -f -s -- "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-desktop" "/usr/local/bin/docker-credential-desktop" diff --git a/server/mdm/maintainedapps/testdata/scripts/figma_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/figma_install.golden.sh index 0afbf1dbc286..5047f353146f 100644 --- a/server/mdm/maintainedapps/testdata/scripts/figma_install.golden.sh +++ b/server/mdm/maintainedapps/testdata/scripts/figma_install.golden.sh @@ -3,8 +3,49 @@ # variables APPDIR="/Applications/" TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") +# functions + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} + # extract contents unzip "$INSTALLER_PATH" -d "$TMPDIR" # copy to the applications folder +quit_application 'com.figma.Desktop' +sudo [ -d "$APPDIR/Figma.app" ] && sudo mv "$APPDIR/Figma.app" "$TMPDIR/Figma.app.bkp" sudo cp -R "$TMPDIR/Figma.app" "$APPDIR" diff --git a/server/mdm/maintainedapps/testdata/scripts/firefox_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/firefox_install.golden.sh index 5de47f50aa98..20def52dd6db 100644 --- a/server/mdm/maintainedapps/testdata/scripts/firefox_install.golden.sh +++ b/server/mdm/maintainedapps/testdata/scripts/firefox_install.golden.sh @@ -3,6 +3,45 @@ # variables APPDIR="/Applications/" TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") +# functions + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} + # extract contents MOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX) @@ -10,4 +49,6 @@ hdiutil attach -plist -nobrowse -readonly -mountpoint "$MOUNT_POINT" "$INSTALLER sudo cp -R "$MOUNT_POINT"/* "$TMPDIR" hdiutil detach "$MOUNT_POINT" # copy to the applications folder +quit_application 'org.mozilla.firefox' +sudo [ -d "$APPDIR/Firefox.app" ] && sudo mv "$APPDIR/Firefox.app" "$TMPDIR/Firefox.app.bkp" sudo cp -R "$TMPDIR/Firefox.app" "$APPDIR" diff --git a/server/mdm/maintainedapps/testdata/scripts/google-chrome_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/google-chrome_install.golden.sh index 8ce14c6b9136..ed8b43e90cd1 100644 --- a/server/mdm/maintainedapps/testdata/scripts/google-chrome_install.golden.sh +++ b/server/mdm/maintainedapps/testdata/scripts/google-chrome_install.golden.sh @@ -3,6 +3,45 @@ # variables APPDIR="/Applications/" TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") +# functions + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} + # extract contents MOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX) @@ -10,4 +49,6 @@ hdiutil attach -plist -nobrowse -readonly -mountpoint "$MOUNT_POINT" "$INSTALLER sudo cp -R "$MOUNT_POINT"/* "$TMPDIR" hdiutil detach "$MOUNT_POINT" # copy to the applications folder +quit_application 'com.google.Chrome' +sudo [ -d "$APPDIR/Google Chrome.app" ] && sudo mv "$APPDIR/Google Chrome.app" "$TMPDIR/Google Chrome.app.bkp" sudo cp -R "$TMPDIR/Google Chrome.app" "$APPDIR" diff --git a/server/mdm/maintainedapps/testdata/scripts/notion_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/notion_install.golden.sh index 99a374eb95e2..bb8166386f6d 100644 --- a/server/mdm/maintainedapps/testdata/scripts/notion_install.golden.sh +++ b/server/mdm/maintainedapps/testdata/scripts/notion_install.golden.sh @@ -3,6 +3,45 @@ # variables APPDIR="/Applications/" TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") +# functions + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} + # extract contents MOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX) @@ -10,4 +49,6 @@ hdiutil attach -plist -nobrowse -readonly -mountpoint "$MOUNT_POINT" "$INSTALLER sudo cp -R "$MOUNT_POINT"/* "$TMPDIR" hdiutil detach "$MOUNT_POINT" # copy to the applications folder +quit_application 'notion.id' +sudo [ -d "$APPDIR/Notion.app" ] && sudo mv "$APPDIR/Notion.app" "$TMPDIR/Notion.app.bkp" sudo cp -R "$TMPDIR/Notion.app" "$APPDIR" diff --git a/server/mdm/maintainedapps/testdata/scripts/postman_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/postman_install.golden.sh index 1b411372b58a..a72d0ac1a38d 100644 --- a/server/mdm/maintainedapps/testdata/scripts/postman_install.golden.sh +++ b/server/mdm/maintainedapps/testdata/scripts/postman_install.golden.sh @@ -3,8 +3,49 @@ # variables APPDIR="/Applications/" TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") +# functions + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} + # extract contents unzip "$INSTALLER_PATH" -d "$TMPDIR" # copy to the applications folder +quit_application 'com.postmanlabs.mac' +sudo [ -d "$APPDIR/Postman.app" ] && sudo mv "$APPDIR/Postman.app" "$TMPDIR/Postman.app.bkp" sudo cp -R "$TMPDIR/Postman.app" "$APPDIR" diff --git a/server/mdm/maintainedapps/testdata/scripts/slack_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/slack_install.golden.sh index 6ba8f9ecd2e0..2f69e88f1a1e 100644 --- a/server/mdm/maintainedapps/testdata/scripts/slack_install.golden.sh +++ b/server/mdm/maintainedapps/testdata/scripts/slack_install.golden.sh @@ -3,6 +3,45 @@ # variables APPDIR="/Applications/" TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") +# functions + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} + # extract contents MOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX) @@ -10,4 +49,6 @@ hdiutil attach -plist -nobrowse -readonly -mountpoint "$MOUNT_POINT" "$INSTALLER sudo cp -R "$MOUNT_POINT"/* "$TMPDIR" hdiutil detach "$MOUNT_POINT" # copy to the applications folder +quit_application 'com.tinyspeck.slackmacgap' +sudo [ -d "$APPDIR/Slack.app" ] && sudo mv "$APPDIR/Slack.app" "$TMPDIR/Slack.app.bkp" sudo cp -R "$TMPDIR/Slack.app" "$APPDIR" diff --git a/server/mdm/maintainedapps/testdata/scripts/visual-studio-code_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/visual-studio-code_install.golden.sh index ba1b1e7158a3..708a2f0bde84 100644 --- a/server/mdm/maintainedapps/testdata/scripts/visual-studio-code_install.golden.sh +++ b/server/mdm/maintainedapps/testdata/scripts/visual-studio-code_install.golden.sh @@ -3,8 +3,49 @@ # variables APPDIR="/Applications/" TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") +# functions + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} + # extract contents unzip "$INSTALLER_PATH" -d "$TMPDIR" # copy to the applications folder +quit_application 'com.microsoft.VSCode' +sudo [ -d "$APPDIR/Visual Studio Code.app" ] && sudo mv "$APPDIR/Visual Studio Code.app" "$TMPDIR/Visual Studio Code.app.bkp" sudo cp -R "$TMPDIR/Visual Studio Code.app" "$APPDIR" diff --git a/server/mdm/maintainedapps/testdata/scripts/whatsapp_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/whatsapp_install.golden.sh index 6a7bfccdf474..a60eab7ea62b 100644 --- a/server/mdm/maintainedapps/testdata/scripts/whatsapp_install.golden.sh +++ b/server/mdm/maintainedapps/testdata/scripts/whatsapp_install.golden.sh @@ -3,8 +3,49 @@ # variables APPDIR="/Applications/" TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") +# functions + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} + # extract contents unzip "$INSTALLER_PATH" -d "$TMPDIR" # copy to the applications folder +quit_application 'net.whatsapp.WhatsApp' +sudo [ -d "$APPDIR/WhatsApp.app" ] && sudo mv "$APPDIR/WhatsApp.app" "$TMPDIR/WhatsApp.app.bkp" sudo cp -R "$TMPDIR/WhatsApp.app" "$APPDIR" From f15e45ea26b4dd10473af6597c51e91cbb0ca3a1 Mon Sep 17 00:00:00 2001 From: RachelElysia <71795832+RachelElysia@users.noreply.github.com> Date: Thu, 9 Jan 2025 15:01:09 -0500 Subject: [PATCH 097/208] Fleet UI: Align update text baseline with neighboring text (#25298) --- frontend/components/SectionHeader/_styles.scss | 3 ++- frontend/components/TableContainer/_styles.scss | 6 +++++- .../pages/DashboardPage/components/InfoCard/_styles.scss | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/components/SectionHeader/_styles.scss b/frontend/components/SectionHeader/_styles.scss index 943ea5c9e163..5c2637996057 100644 --- a/frontend/components/SectionHeader/_styles.scss +++ b/frontend/components/SectionHeader/_styles.scss @@ -5,8 +5,9 @@ &__left-header { display: flex; - align-items: center; + align-items: baseline; gap: $pad-small; + &--vertical { flex-direction: column; } diff --git a/frontend/components/TableContainer/_styles.scss b/frontend/components/TableContainer/_styles.scss index b4ad02faea8c..f15fdf5824eb 100644 --- a/frontend/components/TableContainer/_styles.scss +++ b/frontend/components/TableContainer/_styles.scss @@ -146,7 +146,7 @@ &__results-count { display: flex; - align-items: center; + align-items: baseline; font-size: $x-small; font-weight: $bold; color: $core-fleet-black; @@ -154,6 +154,10 @@ height: 40px; gap: 12px; + > span { + line-height: 40px; // Match other header components' height but still align text baseline + } + .count-error { color: $ui-error; } diff --git a/frontend/pages/DashboardPage/components/InfoCard/_styles.scss b/frontend/pages/DashboardPage/components/InfoCard/_styles.scss index 6dc9ca53060e..bf95e898dd07 100644 --- a/frontend/pages/DashboardPage/components/InfoCard/_styles.scss +++ b/frontend/pages/DashboardPage/components/InfoCard/_styles.scss @@ -34,6 +34,7 @@ &__section-title-group { display: flex; gap: 0.75rem; + align-items: baseline; } &__section-title-detail { From 583907925238682bcdd1290d93440ff9d54e4f9a Mon Sep 17 00:00:00 2001 From: Sam Pfluger <108141731+Sampfluger88@users.noreply.github.com> Date: Thu, 9 Jan 2025 14:37:13 -0600 Subject: [PATCH 098/208] Make Noah DRI of product design (#25304) - Make Noah DRI of product-design page - Remove duplicative ` // GitHub issue templates` call-out FYI: @lukeheath and @noahtalerman, a lot of the time you two have better context on the changes than I do and I want to remove myself as any kind of perceived blocker. --- website/config/custom.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/website/config/custom.js b/website/config/custom.js index b415bdf99f9b..fcac76cba5cb 100644 --- a/website/config/custom.js +++ b/website/config/custom.js @@ -165,7 +165,7 @@ module.exports.custom = { 'handbook/company/testimonials.yml': 'mike-j-thomas', 'handbook/company/product-groups.md': 'lukeheath', 'handbook/engineering': 'lukeheath', - 'handbook/product-design': 'sampfluger88', + 'handbook/product-design': 'noahtalerman', // 🫧 Other brandfronts @@ -301,9 +301,6 @@ module.exports.custom = { // Standard operating procedures (SOP), etc that would be public handbook content except for that it's confidential. 'README.md': ['mikermcneil'],// « about this repo - // GitHub issue templates - '.github/ISSUE_TEMPLATE': ['mikermcneil', 'sampfluger88', 'lukeheath'],// FUTURE: Bust out individual maintainership for issue templates once relevant DRIs are GitHub, markdown, and content design-certified - }, fleetMdmGitopsGithubRepoMaintainersByPath: { From b45b6fa202eb4741a0813f28b23b6d6a67cbfdaa Mon Sep 17 00:00:00 2001 From: Sam Pfluger <108141731+Sampfluger88@users.noreply.github.com> Date: Thu, 9 Jan 2025 15:16:33 -0600 Subject: [PATCH 099/208] Add conversion rate re-computation to workiversary process (#25308) --- handbook/company/communications.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handbook/company/communications.md b/handbook/company/communications.md index 15447192ae87..90a8070c339a 100644 --- a/handbook/company/communications.md +++ b/handbook/company/communications.md @@ -914,7 +914,7 @@ We're happy you've ventured a trip around the sun with Fleet- let's celebrate! T ### Compensation changes -Fleet evaluates and (if relevant) updates compensation decisions yearly, shortly after the anniversary of a team member's start date. The Head of Digital Experience is responsible for the process to [update compensation](https://fleetdm.com/handbook/digital-experience#update-a-team-members-compensation) +Fleet benchmarks all team members based on the United States Dollar (USD) and if applicable, conversion rates at the time of offer for international fleeties. Conversion rates and compensation decisions are re-benchmarked yearly, shortly after the anniversary of a team member's start date. The Head of Digital Experience is responsible for the process to [update compensation](https://fleetdm.com/handbook/digital-experience#update-a-team-members-compensation). ### Relocating From 52fbb233eadf39f06da863db617775eba42615d3 Mon Sep 17 00:00:00 2001 From: Robert Parsimei Koikai Date: Fri, 10 Jan 2025 00:26:13 +0300 Subject: [PATCH 100/208] Feature/clarify package generation process (#24324) --- .../PlatformWrapper/PlatformWrapper.tsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx b/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx index 84d6f1828b1b..df207b9b084d 100644 --- a/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx +++ b/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx @@ -263,7 +263,7 @@ const PlatformWrapper = ({ > Fleet command-line tool {" "} - installed: + : )}{" "} @@ -338,6 +338,16 @@ const PlatformWrapper = ({ } }`, }; + const getHelpTextForPackageType = (): string => { + if (packageType === "deb") { + return "Install this package to add hosts to Fleet. For CentOS, Red Hat, and Fedora Linux, use --type=rpm."; + } else if (packageType === "msi") { + return "Install this package to add hosts to Fleet. For Windows, this generates an MSI package."; + } else if (packageType === "pkg") { + return "Install this package to add hosts to Fleet."; + } + return ""; + }; if (packageType === "chromeos") { return ( @@ -545,11 +555,7 @@ const PlatformWrapper = ({ label={renderLabel(packageType, renderInstallerString(packageType))} type="textarea" value={renderInstallerString(packageType)} - helpText={`Distribute your package to add hosts to Fleet.${ - packageType === "deb" - ? " For CentOS, Red Hat, and Fedora Linux, use --type=rpm." - : "" - }`} + helpText={`${getHelpTextForPackageType()}`} /> ); From d1a564e38763f352cbceb73a204fb446634b90e0 Mon Sep 17 00:00:00 2001 From: Rachael Shaw Date: Thu, 9 Jan 2025 15:52:34 -0600 Subject: [PATCH 101/208] Docs: Update get host example (#25310) + Update "Get host" example response to be a macOS host (in advance of API design for #23235, since certificates will be available for macOS/iOS/iPadOS) + Update formatting of "Get host by identifier" to move weightier items to the bottom --- docs/REST API/rest-api.md | 138 ++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 80 deletions(-) diff --git a/docs/REST API/rest-api.md b/docs/REST API/rest-api.md index a8f27e1db011..254150eddce5 100644 --- a/docs/REST API/rest-api.md +++ b/docs/REST API/rest-api.md @@ -2267,41 +2267,41 @@ If `after` is being used with `created_at` or `updated_at`, the table must be sp "policy_updated_at": "2023-06-26T18:33:15Z", "last_enrolled_at": "2023-02-26T22:33:12Z", "seen_time": "2020-11-05T06:03:39Z", - "hostname": "2ceca32fe484", + "hostname": "Annas-MacBook-Pro.local", "uuid": "392547dc-0000-0000-a87a-d701ff75bc65", - "platform": "centos", - "osquery_version": "2.7.0", - "os_version": "CentOS Linux 7", - "build": "", - "platform_like": "rhel fedora", + "platform": "darwin", + "osquery_version": "5.15.0", + "os_version": "macOS 15.2", + "build": "24C101", + "platform_like": "darwin", "code_name": "", "uptime": 8305000000000, "memory": 2084032512, - "cpu_type": "6", - "cpu_subtype": "142", - "cpu_brand": "Intel(R) Core(TM) i5-8279U CPU @ 2.40GHz", - "cpu_physical_cores": 4, - "cpu_logical_cores": 4, - "hardware_vendor": "", - "hardware_model": "", + "cpu_type": "arm64e", + "cpu_subtype": "ARM64E", + "cpu_brand": "Apple M1", + "cpu_physical_cores": 8, + "cpu_logical_cores": 8, + "hardware_vendor": "Apple Inc.", + "hardware_model": "MacBookPro17,1", "hardware_version": "", - "hardware_serial": "", - "computer_name": "2ceca32fe484", - "display_name": "2ceca32fe484", - "public_ip": "", - "primary_ip": "", - "primary_mac": "", + "hardware_serial": "C0124FXASD6G", + "computer_name": "Anna's MacBook Pro", + "display_name": "Anna's MacBook Pro", + "public_ip": "123.45.678.910", + "primary_ip": "192.12.345.678", + "primary_mac": "36:34:a5:6b:7b:5c", "distributed_interval": 10, "config_tls_refresh": 10, "logger_tls_period": 8, - "additional": {}, "status": "offline", - "display_text": "2ceca32fe484", + "display_text": "Annas-MacBook-Pro.local", "team_id": null, "team_name": null, "gigs_disk_space_available": 174.98, "percent_disk_space_available": 71, "gigs_total_disk_space": 246, + "additional": {}, "pack_stats": [ { "pack_id": 0, @@ -2684,9 +2684,9 @@ Returns the information of the specified host. "status": "online", "display_text": "23cfc9caacf0", "issues": { - "failing_policies_count": 1, - "critical_vulnerabilities_count": 2, // Available in Fleet Premium - "total_issues_count": 3 + "failing_policies_count": 1, + "critical_vulnerabilities_count": 2, // Available in Fleet Premium + "total_issues_count": 3 }, "batteries": [ { @@ -2791,25 +2791,6 @@ Returns the information of the specified host. } ], "software": [ - { - "id": 408, - "name": "osquery", - "version": "4.5.1", - "source": "rpm_packages", - "browser": "", - "generated_cpe": "", - "vulnerabilities": null, - "installed_paths": ["/usr/lib/some-path-1"] - }, - { - "id": 1146, - "name": "tar", - "version": "1.30", - "source": "rpm_packages", - "browser": "", - "generated_cpe": "", - "vulnerabilities": null - }, { "id": 321, "name": "SomeApp.app", @@ -2832,7 +2813,7 @@ Returns the information of the specified host. "device_status": "unlocked", "pending_action": "", "macos_settings": { - "disk_encryption": null, + "disk_encryption": "verified", "action_required": null }, "macos_setup": { @@ -2842,7 +2823,7 @@ Returns the information of the specified host. }, "os_settings": { "disk_encryption": { - "status": null, + "status": "verified", "detail": "" } }, @@ -2900,18 +2881,6 @@ If `hostname` is specified when there is more than one host with the same hostna "host": { "created_at": "2022-02-10T02:29:13Z", "updated_at": "2022-10-14T17:07:11Z", - "software": [ - { - "id": 16923, - "name": "Automat", - "version": "0.8.0", - "source": "python_packages", - "browser": "", - "generated_cpe": "", - "vulnerabilities": null, - "installed_paths": ["/usr/lib/some_path/"] - } - ], "id": 33, "detail_updated_at": "2022-10-14T17:07:12Z", "label_updated_at": "2022-10-14T17:07:12Z", @@ -2947,6 +2916,29 @@ If `hostname` is specified when there is more than one host with the same hostna "config_tls_refresh": 60, "logger_tls_period": 10, "team_id": 2, + "team_name": null, + "gigs_disk_space_available": 19.29, + "percent_disk_space_available": 74, + "gigs_total_disk_space": 192, + "issues": { + "failing_policies_count": 1, + "critical_vulnerabilities_count": 2, // Fleet Premium only + "total_issues_count": 3 + }, + "batteries": [ + { + "cycle_count": 999, + "health": "Normal" + } + ], + "geolocation": { + "country_iso": "US", + "city_name": "New York", + "geometry": { + "type": "point", + "coordinates": [40.6799, -74.0028] + } + }, "pack_stats": [ { "pack_id": 1, @@ -2972,15 +2964,6 @@ If `hostname` is specified when there is more than one host with the same hostna ] } ], - "team_name": null, - "gigs_disk_space_available": 19.29, - "percent_disk_space_available": 74, - "gigs_total_disk_space": 192, - "issues": { - "failing_policies_count": 1, - "critical_vulnerabilities_count": 2, // Fleet Premium only - "total_issues_count": 3 - }, "labels": [ { "created_at": "2021-09-14T05:11:02Z", @@ -3029,23 +3012,18 @@ If `hostname` is specified when there is more than one host with the same hostna "critical": false } ], - "batteries": [ + "software": [ { - "cycle_count": 999, - "health": "Normal" + "id": 16923, + "name": "Automat", + "version": "0.8.0", + "source": "python_packages", + "browser": "", + "generated_cpe": "", + "vulnerabilities": null, + "installed_paths": ["/usr/lib/some_path/"] } ], - "geolocation": { - "country_iso": "US", - "city_name": "New York", - "geometry": { - "type": "point", - "coordinates": [40.6799, -74.0028] - } - }, - "status": "online", - "display_text": "dogfood-ubuntu-box", - "display_name": "dogfood-ubuntu-box", "mdm": { "encryption_key_available": false, "enrollment_status": null, From 4980052f0bb1fd79b08482192121f26b6d2d7cd6 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 9 Jan 2025 16:28:22 -0600 Subject: [PATCH 102/208] Website: Update policy details page, add controls to policies in standard query library. (#25309) Related to: #23285 Changes: - Updated the policy details page to have a controls section that displays the `configuration_profile` and `script` values of policies - Added configuration profiles and scripts from https://github.com/ddribeiro/fleet-remediation-controls to macOS policies in the standard query library. --- .../standard-query-library.yml | 811 +++++++++++++++++- website/assets/styles/pages/query-detail.less | 9 +- website/views/pages/query-detail.ejs | 23 +- 3 files changed, 812 insertions(+), 31 deletions(-) diff --git a/docs/01-Using-Fleet/standard-query-library/standard-query-library.yml b/docs/01-Using-Fleet/standard-query-library/standard-query-library.yml index c44f17f4d21e..0ff23289f2de 100644 --- a/docs/01-Using-Fleet/standard-query-library/standard-query-library.yml +++ b/docs/01-Using-Fleet/standard-query-library/standard-query-library.yml @@ -45,7 +45,43 @@ spec: ); purpose: Informational tags: compliance, CIS, CIS_Level1, premium, - contributors: sharon-fdm + contributors: sharon-fdm,ddribeiro + configuration_profile: | + + + + + PayloadContent + + + PayloadDisplayName + Screensaver + PayloadIdentifier + com.apple.screensaver.AB633B1B-EAEF-4AB6-B5F6-DE67193267E9 + PayloadType + com.apple.screensaver + PayloadUUID + AB633B1B-EAEF-4AB6-B5F6-DE67193267E9 + PayloadVersion + 1 + askForPassword + + askForPasswordDelay + 0 + + + PayloadDisplayName + Require password after screensaver or sleep + PayloadIdentifier + com.fleetdm.password_policy + PayloadType + Configuration + PayloadUUID + 5A2DC0F2-C5FE-4808-9083-D9879684D7FA + PayloadVersion + 1 + + --- apiVersion: v1 kind: policy @@ -72,7 +108,41 @@ spec: ); purpose: Informational tags: compliance, CIS, CIS_Level1, premium - contributors: sharon-fdm + contributors: sharon-fdm,ddribeiro + configuration_profile: | + + + + + PayloadContent + + + AutomaticCheckEnabled + + PayloadDisplayName + Software Update + PayloadIdentifier + com.apple.SoftwareUpdate.8567CAE0-4F08-49B7-9DEE-EE7A1FB232E4 + PayloadType + com.apple.SoftwareUpdate + PayloadUUID + 8567CAE0-4F08-49B7-9DEE-EE7A1FB232E4 + PayloadVersion + 1 + + + PayloadDisplayName + Automatically install updates + PayloadIdentifier + com.fleetdm.automatically_install_updates + PayloadType + Configuration + PayloadUUID + C9797096-D3DD-4BB4-85B0-6679209BA78F + PayloadVersion + 1 + + --- apiVersion: v1 kind: policy @@ -575,7 +645,46 @@ spec: resolution: "To enable Gatekeeper, on the failing device, run the following command in the Terminal app: /usr/sbin/spctl --master-enable." tags: compliance, hardening, built-in, CIS, CIS2.5.2.1, critical platform: darwin - contributors: groob + contributors: groob,ddribeiro + script: | + #!/bin/sh + + ## command to enable gatekeeper + /usr/sbin/spctl --master-enable + configuration_profile: | + + + + + PayloadContent + + + EnableAssessment + + PayloadDisplayName + System Policy Control + PayloadIdentifier + com.apple.systempolicy.control.6CA698CD-1DBB-445C-BDA3-60E35FBBF0E9 + PayloadType + com.apple.systempolicy.control + PayloadUUID + 6CA698CD-1DBB-445C-BDA3-60E35FBBF0E9 + PayloadVersion + 1 + + + PayloadDisplayName + Enable Gatekeeper + PayloadIdentifier + com.fleetdm.enablegatekeeper.DF30A9A2-C9F9-421D-A26A-6FAA7216E72F + PayloadType + Configuration + PayloadUUID + DF30A9A2-C9F9-421D-A26A-6FAA7216E72F + PayloadVersion + 1 + + --- apiVersion: v1 kind: policy @@ -634,7 +743,41 @@ spec: resolution: "Contact your IT administrator to ensure your Mac is receiving a profile that disables automatic login." tags: MDM required, compliance, hardening, built-in, critical platform: darwin - contributors: groob + contributors: groob,ddribeiro + configuration_profile: | + + + + + PayloadContent + + + PayloadDisplayName + Login Window #1 + PayloadIdentifier + com.apple.loginwindow.CE506065-7C0E-434E-8B8C-12E164116C94 + PayloadType + com.apple.loginwindow + PayloadUUID + CE506065-7C0E-434E-8B8C-12E164116C94 + PayloadVersion + 1 + com.apple.login.mcx.DisableAutoLoginClient + + + + PayloadDisplayName + Disable Automatic Login + PayloadIdentifier + com.fleetdm.disableautomaticlogin.F07E2CB5-56CC-4699-B061-EAA253220BA8 + PayloadType + Configuration + PayloadUUID + F07E2CB5-56CC-4699-B061-EAA253220BA8 + PayloadVersion + 1 + + --- apiVersion: v1 kind: policy @@ -645,7 +788,41 @@ spec: resolution: "Contact your IT administrator to ensure your Mac is receiving a profile that enables secure keyboard entry for the Terminal application." tags: MDM required, compliance, hardening, built-in platform: darwin - contributors: groob + contributors: groob,ddribeiro + configuration_profile: | + + + + + PayloadContent + + + SecureKeyboardEntry + + PayloadDisplayName + Terminal + PayloadIdentifier + com.apple.Terminal.89C5FA0F-CA32-4CC7-99D9-931B13CB923B + PayloadType + com.apple.Terminal + PayloadUUID + 89C5FA0F-CA32-4CC7-99D9-931B13CB923B + PayloadVersion + 1 + + + PayloadDisplayName + Enable Terminal Secure Keyboard Entry + PayloadIdentifier + com.fleetdm.enableterminalsecurekeyboardentry.6E73E3DF-0D0A-4B17-BBCB-3E0906C78743 + PayloadType + Configuration + PayloadUUID + 6E73E3DF-0D0A-4B17-BBCB-3E0906C78743 + PayloadVersion + 1 + + --- apiVersion: v1 kind: query @@ -692,7 +869,43 @@ spec: updates. tags: compliance, malware, hardening, built-in, template platform: darwin - contributors: GuillaumeRoss + contributors: GuillaumeRoss,ddribeiro + configuration_profile: | + + + + + PayloadContent + + + ConfigDataInstall + + CriticalUpdateInstall + + PayloadDisplayName + Software Update + PayloadIdentifier + com.apple.SoftwareUpdate.C0292C9C-7506-4A51-9C19-52FF2DB632EC + PayloadType + com.apple.SoftwareUpdate + PayloadUUID + C0292C9C-7506-4A51-9C19-52FF2DB632EC + PayloadVersion + 1 + + + PayloadDisplayName + Enable automatic system data files and security updates + PayloadIdentifier + com.fleetdm.enableSystemDataFilesAndSecurityUpdates.1C24BCAF-E18D-434B-B5D2-70F886F19912 + PayloadType + Configuration + PayloadUUID + 1C24BCAF-E18D-434B-B5D2-70F886F19912 + PayloadVersion + 1 + + --- apiVersion: v1 kind: policy @@ -769,7 +982,41 @@ spec: resolution: "In System Preferences, open Security & Privacy, navigate to the Firewall tab and click Turn On Firewall." tags: hardening, compliance, built-in, CIS, CIS2.5.2.2 platform: darwin - contributors: GuillaumeRoss + contributors: GuillaumeRoss,ddribeiro + configuration_profile: | + + + + + PayloadContent + + + EnableFirewall + + PayloadDisplayName + Firewall + PayloadIdentifier + com.apple.security.firewall.84151DEA-D0E5-4334-91D4-4BBDFA38CD17 + PayloadType + com.apple.security.firewall + PayloadUUID + 84151DEA-D0E5-4334-91D4-4BBDFA38CD17 + PayloadVersion + 1 + + + PayloadDisplayName + Enable Firewall + PayloadIdentifier + com.fleetdm.enablefirewall.D93BF783-383E-41CD-97AA-1D3FD71045BE + PayloadType + Configuration + PayloadUUID + D93BF783-383E-41CD-97AA-1D3FD71045BE + PayloadVersion + 1 + + --- apiVersion: v1 kind: policy @@ -780,7 +1027,43 @@ spec: resolution: "Contact your IT administrator to ensure your Mac is receiving a profile that enables screen lock." tags: MDM required, compliance, hardening, built-in platform: darwin - contributors: GuillaumeRoss + contributors: GuillaumeRoss,ddribeiro + configuration_profile: | + + + + + PayloadContent + + + PayloadDisplayName + Screensaver + PayloadIdentifier + com.apple.screensaver.C3B911F5-A787-4B64-86D9-3DFE19B5F72F + PayloadType + com.apple.screensaver + PayloadUUID + C3B911F5-A787-4B64-86D9-3DFE19B5F72F + PayloadVersion + 1 + askForPassword + + askForPasswordDelay + 0 + + + PayloadDisplayName + Enable screen lock + PayloadIdentifier + com.fleetdm.enableScreenLock.5BFC3E0C-50E1-4D61-82D8-3A784D4DD200 + PayloadType + Configuration + PayloadUUID + 5BFC3E0C-50E1-4D61-82D8-3A784D4DD200 + PayloadVersion + 1 + + --- apiVersion: v1 kind: policy @@ -802,7 +1085,41 @@ spec: resolution: "Contact your IT administrator to make sure your Mac is receiving configuration profiles for password length." platform: darwin tags: compliance, hardening, built-in, CIS, CIS5.2.2 - contributors: GuillaumeRoss + contributors: GuillaumeRoss,ddribeiro + configuration_profile: | + + + + + PayloadContent + + + PayloadDisplayName + Passcode + PayloadIdentifier + com.apple.mobiledevice.passwordpolicy.0668AAD7-0A80-476C-AAF7-C5F63B5E8E3D + PayloadType + com.apple.mobiledevice.passwordpolicy + PayloadUUID + 0668AAD7-0A80-476C-AAF7-C5F63B5E8E3D + PayloadVersion + 1 + minLength + 10 + + + PayloadDisplayName + Require password of 10 or more characters + PayloadIdentifier + com.fleetdm.passwordPolicy10Characters.D3CDEDA2-DC77-484F-92F8-68A4902800AD + PayloadType + Configuration + PayloadUUID + D3CDEDA2-DC77-484F-92F8-68A4902800AD + PayloadVersion + 1 + + --- apiVersion: v1 kind: policy @@ -835,7 +1152,41 @@ spec: resolution: "Contact your IT administrator to ensure your Mac is receiving a profile that enables automatic update downloads." tags: MDM required, compliance, CIS, CIS1.3 platform: darwin - contributors: GuillaumeRoss + contributors: GuillaumeRoss,ddribeiro + configuration_profile: | + + + + + PayloadContent + + + AutomaticDownload + + PayloadDisplayName + Software Update + PayloadIdentifier + com.apple.SoftwareUpdate.79490A7F-FBE2-4E28-9E98-CE8232A87C6A + PayloadType + com.apple.SoftwareUpdate + PayloadUUID + 79490A7F-FBE2-4E28-9E98-CE8232A87C6A + PayloadVersion + 1 + + + PayloadDisplayName + Enable automatic update downloads + PayloadIdentifier + com.fleetdm.enableAutomaticUpdateDownloads.16F48F16-F1DC-44D6-9126-B9D1E1274C93 + PayloadType + Configuration + PayloadUUID + 16F48F16-F1DC-44D6-9126-B9D1E1274C93 + PayloadVersion + 1 + + --- apiVersion: v1 kind: policy @@ -846,7 +1197,41 @@ spec: resolution: "Contact your IT administrator to ensure your Mac is receiving a profile that enables automatic installation of application updates." tags: MDM required, compliance, CIS, CIS1.4 platform: darwin - contributors: GuillaumeRoss + contributors: GuillaumeRoss,ddribeiro + configuration_profile: | + + + + + PayloadContent + + + AutomaticallyInstallAppUpdates + + PayloadDisplayName + Software Update + PayloadIdentifier + com.apple.SoftwareUpdate.4E11DA65-B6D8-4C65-AB72-DAC3177FC487 + PayloadType + com.apple.SoftwareUpdate + PayloadUUID + 4E11DA65-B6D8-4C65-AB72-DAC3177FC487 + PayloadVersion + 1 + + + PayloadDisplayName + Enable automatic installation of application updates + PayloadIdentifier + com.fleetdm.enableAutomaticInstallationOfAppUpdates.66A51CA4-49DE-49B1-A8EE-DFB0D44D5C62 + PayloadType + Configuration + PayloadUUID + 66A51CA4-49DE-49B1-A8EE-DFB0D44D5C62 + PayloadVersion + 1 + + --- apiVersion: v1 kind: policy @@ -857,7 +1242,41 @@ spec: resolution: "Contact your IT administrator to ensure your Mac is receiving a profile that enables automatic security and data update installation." tags: MDM required, compliance, CIS, CIS1.5 platform: darwin - contributors: GuillaumeRoss + contributors: GuillaumeRoss,ddribeiro + configuration_profile: | + + + + + PayloadContent + + + CriticalUpdateInstall + + PayloadDisplayName + Software Update + PayloadIdentifier + com.apple.SoftwareUpdate.E53C14AB-3694-4D90-87A0-79E6496EC0E1 + PayloadType + com.apple.SoftwareUpdate + PayloadUUID + E53C14AB-3694-4D90-87A0-79E6496EC0E1 + PayloadVersion + 1 + + + PayloadDisplayName + Enable automatic security and data file updates + PayloadIdentifier + com.fleetdm.enableAutomaticSecurityAndDataFileUpdates.Dales-MacBook-Pro.50CE2929-89F7-4283-922A-F30C15D6B1FD + PayloadType + Configuration + PayloadUUID + 50CE2929-89F7-4283-922A-F30C15D6B1FD + PayloadVersion + 1 + + --- apiVersion: v1 kind: policy @@ -868,7 +1287,41 @@ spec: resolution: "Contact your IT administrator to ensure your Mac is receiving a profile that enables automatic installation of operating system updates." tags: MDM required, compliance, CIS, CIS1.6 platform: darwin - contributors: GuillaumeRoss + contributors: GuillaumeRoss,ddribeiro + configuration_profile: | + + + + + PayloadContent + + + AutomaticallyInstallMacOSUpdates + + PayloadDisplayName + Software Update + PayloadIdentifier + com.apple.SoftwareUpdate.B5EF9664-07BB-4775-B597-59F21F413878 + PayloadType + com.apple.SoftwareUpdate + PayloadUUID + B5EF9664-07BB-4775-B597-59F21F413878 + PayloadVersion + 1 + + + PayloadDisplayName + Enable automatic installation of OS updates + PayloadIdentifier + com.fleetdm.enableAutomaticOSUpddates.A5CA0F6B-02F8-42D0-805E-D13FDB9B093B + PayloadType + Configuration + PayloadUUID + A5CA0F6B-02F8-42D0-805E-D13FDB9B093B + PayloadVersion + 1 + + --- apiVersion: v1 kind: policy @@ -879,7 +1332,41 @@ spec: resolution: "Contact your IT administrator to ensure your Mac is receiving a profile that enables automatic time and date configuration." tags: MDM required, compliance, CIS, CIS2.2.1 platform: darwin - contributors: GuillaumeRoss + contributors: GuillaumeRoss,ddribeiro + configuration_profile: | + + + + + PayloadContent + + + PayloadDisplayName + Restrictions + PayloadIdentifier + com.apple.applicationaccess.B0EBDEA9-69D3-46CA-BB19-72B86A7111F5 + PayloadType + com.apple.applicationaccess + PayloadUUID + B0EBDEA9-69D3-46CA-BB19-72B86A7111F5 + PayloadVersion + 1 + forceAutomaticDateAndTime + + + + PayloadDisplayName + Automatically configure time and date + PayloadIdentifier + com.fleetdm.automaticallyConfigureTimeAndDate.BA0A14E0-22A2-4D59-A803-BB04F374F6A3 + PayloadType + Configuration + PayloadUUID + BA0A14E0-22A2-4D59-A803-BB04F374F6A3 + PayloadVersion + 1 + + --- apiVersion: v1 kind: policy @@ -890,7 +1377,45 @@ spec: resolution: "Contact your IT administrator to ensure your Mac is receiving a profile that enables the screen saver after inactivity of 20 minutes or less." tags: MDM required, compliance, CIS, CIS2.3.1, CIS5.8 platform: darwin - contributors: GuillaumeRoss + contributors: GuillaumeRoss,ddribeiro + configuration_profile: | + + + + + PayloadContent + + + PayloadDisplayName + Screensaver + PayloadIdentifier + com.apple.screensaver.FDC5E74E-C09E-484C-B3F3-FF04BF8AF9AB + PayloadType + com.apple.screensaver + PayloadUUID + FDC5E74E-C09E-484C-B3F3-FF04BF8AF9AB + PayloadVersion + 1 + askForPassword + + askForPasswordDelay + 60 + idleTime + 1140 + + + PayloadDisplayName + Lock screen after inactivity of 20 minutes + PayloadIdentifier + com.fleetdm.lockScreenAfter20Minutes.34DD0263-156C-48DB-B6B8-64D3112A1128 + PayloadType + Configuration + PayloadUUID + 34DD0263-156C-48DB-B6B8-64D3112A1128 + PayloadVersion + 1 + + --- apiVersion: v1 kind: policy @@ -901,7 +1426,41 @@ spec: resolution: "Contact your IT administrator to ensure your Mac is receiving a profile that prevents Internet sharing." tags: MDM required, compliance, CIS, CIS2.4.2 platform: darwin - contributors: GuillaumeRoss + contributors: GuillaumeRoss,ddribeiro + configuration_profile: | + + + + + PayloadContent + + + PayloadDisplayName + Managed Preferences + PayloadIdentifier + com.apple.MCX.7BE9B7E8-14E4-49CF-AEC5-CD7806957F5A + PayloadType + com.apple.MCX + PayloadUUID + 7BE9B7E8-14E4-49CF-AEC5-CD7806957F5A + PayloadVersion + 1 + forceInternetSharingOff + + + + PayloadDisplayName + Turn off internet sharing + PayloadIdentifier + com.fleetdm.turnOffInternetSharing.22125243-721F-4A26-862E-5B16F28977C0 + PayloadType + Configuration + PayloadUUID + 22125243-721F-4A26-862E-5B16F28977C0 + PayloadVersion + 1 + + --- apiVersion: v1 kind: policy @@ -912,7 +1471,41 @@ spec: resolution: "Contact your IT administrator to ensure your Mac is receiving a profile that disables content caching." tags: MDM required, compliance, CIS, CIS2.4.10 platform: darwin - contributors: GuillaumeRoss + contributors: GuillaumeRoss,ddribeiro + configuration_profile: | + + + + + PayloadContent + + + PayloadDisplayName + Restrictions + PayloadIdentifier + com.apple.applicationaccess.EEFDDF9B-F4D3-45FC-A832-F20096938668 + PayloadType + com.apple.applicationaccess + PayloadUUID + EEFDDF9B-F4D3-45FC-A832-F20096938668 + PayloadVersion + 1 + allowContentCaching + + + + PayloadDisplayName + Disable content caching + PayloadIdentifier + com.fleetdm.disableContentCaching.6154F973-CF2C-46A5-B38C-DCF44A3FFC65 + PayloadType + Configuration + PayloadUUID + 6154F973-CF2C-46A5-B38C-DCF44A3FFC65 + PayloadVersion + 1 + + --- apiVersion: v1 kind: policy @@ -923,7 +1516,41 @@ spec: resolution: "Contact your IT administrator to ensure your Mac is receiving a profile that disables advertisement tracking." tags: MDM required, compliance, CIS, CIS2.5.6 platform: darwin - contributors: GuillaumeRoss + contributors: GuillaumeRoss,ddribeiro + configuration_profile: | + + + + + PayloadContent + + + PayloadDisplayName + iCloud + PayloadIdentifier + com.apple.icloud.managed.19CEE0E2-2D04-43E7-AB98-B93B179A20ED + PayloadType + com.apple.icloud.managed + PayloadUUID + 19CEE0E2-2D04-43E7-AB98-B93B179A20ED + PayloadVersion + 1 + DisableCloudSync + + + + PayloadDisplayName + Limit ad tracking + PayloadIdentifier + com.fleetdm.disableiCloudDesktopAndDocumentsSync.9CEE4A9A-3BC6-4E2C-A093-8CC3B7F26EF8 + PayloadType + Configuration + PayloadUUID + 9CEE4A9A-3BC6-4E2C-A093-8CC3B7F26EF8 + PayloadVersion + 1 + + --- apiVersion: v1 kind: policy @@ -934,7 +1561,41 @@ spec: resolution: "Contact your IT administrator to ensure your Mac is receiving a profile to prevent iCloud Desktop and Documents sync." tags: MDM required, compliance, CIS, CIS2.6.1.4 platform: darwin - contributors: GuillaumeRoss + contributors: GuillaumeRoss,ddribeiro + configuration_profile: | + + + + + PayloadContent + + + PayloadDisplayName + iCloud + PayloadIdentifier + com.apple.icloud.managed.19CEE0E2-2D04-43E7-AB98-B93B179A20ED + PayloadType + com.apple.icloud.managed + PayloadUUID + 19CEE0E2-2D04-43E7-AB98-B93B179A20ED + PayloadVersion + 1 + DisableCloudSync + + + + PayloadDisplayName + Disable iCloud Desktop and Documents Sync + PayloadIdentifier + com.fleetdm.disableiCloudDesktopAndDocumentsSync.9CEE4A9A-3BC6-4E2C-A093-8CC3B7F26EF8 + PayloadType + Configuration + PayloadUUID + 9CEE4A9A-3BC6-4E2C-A093-8CC3B7F26EF8 + PayloadVersion + 1 + + --- apiVersion: v1 kind: policy @@ -945,18 +1606,88 @@ spec: resolution: "Contact your IT administrator to ensure your Mac is receiving a profile that enables firewall logging." tags: MDM required, compliance, CIS, CIS3.6 platform: darwin - contributors: GuillaumeRoss + contributors: GuillaumeRoss,ddribeiro + configuration_profile: | + + + + + PayloadContent + + + EnableFirewall + + EnableLogging + + PayloadDisplayName + Firewall + PayloadIdentifier + com.apple.security.firewall.E91C28D7-A35F-44DF-8656-07C738F8946E + PayloadType + com.apple.security.firewall + PayloadUUID + E91C28D7-A35F-44DF-8656-07C738F8946E + PayloadVersion + 1 + + + PayloadDisplayName + Enable firewall logging + PayloadIdentifier + com.fleetdm.enableFirewallLogging.A97BF2B6-968B-4C9B-B02C-331595377934 + PayloadType + Configuration + PayloadUUID + A97BF2B6-968B-4C9B-B02C-331595377934 + PayloadVersion + 1 + + --- apiVersion: v1 kind: policy spec: name: Guest account disabled (macOS) - query: SELECT 1 FROM managed_policies WHERE domain='com.apple.loginwindow' AND name='DisableGuestAccount' AND value='1' LIMIT 1; + query: SELECT 1 FROM managed_policies WHERE domain='com.apple.MCX' AND name='DisableGuestAccount' AND value='1' LIMIT 1; description: "Checks that a mobile device management (MDM) solution configures the Mac to prevent the use of a guest account." resolution: "Contact your IT administrator to ensure your Mac is receiving a profile that disables the guest account." tags: MDM required, compliance, CIS, CIS6.1.3 platform: darwin - contributors: GuillaumeRoss + contributors: GuillaumeRoss,ddribeiro + configuration_profile: | + + + + + PayloadContent + + + DisableGuestAccount + + PayloadDisplayName + Energy Saver, FileVault, Time Server, Mobile Accounts and Guest Account + PayloadIdentifier + com.apple.MCX.87E0D7FE-FDEF-4B61-8505-C009C975AFD4 + PayloadType + com.apple.MCX + PayloadUUID + 87E0D7FE-FDEF-4B61-8505-C009C975AFD4 + PayloadVersion + 1 + + + PayloadDisplayName + Disable guest account + PayloadIdentifier + com.fleetdm.disableGuestAccount.E29C0490-83B0-4AD1-AD50-AC9B63D1DD96 + PayloadType + Configuration + PayloadUUID + E29C0490-83B0-4AD1-AD50-AC9B63D1DD96 + PayloadVersion + 1 + + --- apiVersion: v1 kind: policy @@ -967,7 +1698,41 @@ spec: resolution: "Contact your IT administrator to ensure your Mac is receiving a profile that prevents guest access to shared folders." tags: MDM required, compliance, CIS, CIS6.1.4 platform: darwin - contributors: GuillaumeRoss + contributors: GuillaumeRoss,ddribeiro + configuration_profile: | + + + + + PayloadContent + + + guestAccess + + PayloadDisplayName + File Server + PayloadIdentifier + com.apple.AppleFileServer.0C0C0FED-098F-4BAA-8917-3313A8A1F3A1 + PayloadType + com.apple.AppleFileServer + PayloadUUID + 0C0C0FED-098F-4BAA-8917-3313A8A1F3A1 + PayloadVersion + 1 + + + PayloadDisplayName + Disable guest access to shared folders + PayloadIdentifier + com.fleetdm.disableGuestAccessToSharedFolders.819D93D8-E078-43A5-9661-F5E96F84F384 + PayloadType + Configuration + PayloadUUID + 819D93D8-E078-43A5-9661-F5E96F84F384 + PayloadVersion + 1 + + --- apiVersion: v1 kind: policy diff --git a/website/assets/styles/pages/query-detail.less b/website/assets/styles/pages/query-detail.less index 3e56e7d5add3..91ca8584fed2 100644 --- a/website/assets/styles/pages/query-detail.less +++ b/website/assets/styles/pages/query-detail.less @@ -309,7 +309,7 @@ code { color: #515774; &.has-linebreaks { - white-space: pre; + white-space: break-spaces; } &.no-linebreaks { white-space: normal; @@ -370,6 +370,13 @@ [purpose='breadcrumbs-and-search'] { margin-bottom: 32px; } + pre { + code { + &.has-linebreaks { + white-space: pre; + } + } + } } @media (max-width: 768px) { diff --git a/website/views/pages/query-detail.ejs b/website/views/pages/query-detail.ejs index 86d3d053d8e0..2c058b87c989 100644 --- a/website/views/pages/query-detail.ejs +++ b/website/views/pages/query-detail.ejs @@ -36,15 +36,24 @@

<%= query.contributors[0].name %>

<%- query.description %>

- + +
+

Create or edit the following script and configure it to run when the check fails:

+
+
+
<%= query.script %>
+
+
+

Check

Use the policy below to verify

From d6bcf6cb212aaaf5bd2feb3fec6a77329f73c7fd Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Thu, 9 Jan 2025 16:35:08 -0600 Subject: [PATCH 103/208] Update roadmap-preview-january-2025.md (#25284) fyi @Drew-P-drawers @noahtalerman @onasismunro --- articles/roadmap-preview-january-2025.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/articles/roadmap-preview-january-2025.md b/articles/roadmap-preview-january-2025.md index 11a0f3075008..2bd166e95c73 100644 --- a/articles/roadmap-preview-january-2025.md +++ b/articles/roadmap-preview-january-2025.md @@ -22,7 +22,7 @@ Big opportunities that Fleet is building towards in the near future (next 180 da - 🗓️ Native patching for apps and OS during maintenance windows - 🤖 AI-generated osquery queries -Any feedback or a questions? You can find us where we hang out in the [osquery // #fleet Slack channel](https://chat.osquery.io/c/fleet). +Any feedback or a questions? Contributions welcome! You can find us [where we hang out](https://fleetdm.com/support). From 4f68dca1a3c96e6a0526fa9021e4a5d8ebbe7b12 Mon Sep 17 00:00:00 2001 From: Rachael Shaw Date: Thu, 9 Jan 2025 16:46:24 -0600 Subject: [PATCH 104/208] Docs: Update get host example response (#25313) Couple changes in advance of API design for #23235 (since certificates will be available for macOS/iOS/iPadOS): + Update "Get host" example response to be a macOS host + Update "Get host by device token" example response to be a macOS host & move weightier items to the bottom for readability --- docs/REST API/rest-api.md | 212 +++++++++++++++++--------------------- 1 file changed, 97 insertions(+), 115 deletions(-) diff --git a/docs/REST API/rest-api.md b/docs/REST API/rest-api.md index 254150eddce5..495eac40c639 100644 --- a/docs/REST API/rest-api.md +++ b/docs/REST API/rest-api.md @@ -2643,31 +2643,31 @@ Returns the information of the specified host. "last_enrolled_at": "2021-08-19T02:02:22Z", "seen_time": "2021-08-19T21:14:58Z", "refetch_requested": false, - "hostname": "23cfc9caacf0", + "hostname": "Annas-MacBook-Pro.local", "uuid": "309a4b7d-0000-0000-8e7f-26ae0815ede8", - "platform": "rhel", - "osquery_version": "5.12.0", + "platform": "darwin", + "osquery_version": "5.15.0", "orbit_version": "1.22.0", "fleet_desktop_version": "1.22.0", "scripts_enabled": true, - "os_version": "CentOS Linux 8.3.2011", - "build": "", - "platform_like": "rhel", + "os_version": "macOS 15.2", + "build": "24C101", + "platform_like": "darwin", "code_name": "", "uptime": 210671000000000, "memory": 16788398080, - "cpu_type": "x86_64", - "cpu_subtype": "158", - "cpu_brand": "Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz", - "cpu_physical_cores": 12, - "cpu_logical_cores": 12, - "hardware_vendor": "", - "hardware_model": "", + "cpu_type": "arm64e", + "cpu_subtype": "ARM64E", + "cpu_brand": "Apple M1", + "cpu_physical_cores": 8, + "cpu_logical_cores": 8, + "hardware_vendor": "Apple Inc.", + "hardware_model": "MacBookPro17,1", "hardware_version": "", - "hardware_serial": "", - "computer_name": "23cfc9caacf0", - "display_name": "23cfc9caacf0", - "public_ip": "", + "hardware_serial": "C0124FXASD6G", + "computer_name": "Anna's MacBook Pro", + "display_name": "Anna's MacBook Pro", + "public_ip": "123.45.678.910", "primary_ip": "172.27.0.6", "primary_mac": "02:42:ac:1b:00:06", "distributed_interval": 10, @@ -2676,13 +2676,13 @@ Returns the information of the specified host. "team_id": null, "pack_stats": null, "team_name": null, - "additional": {}, - "gigs_disk_space_available": 46.1, - "percent_disk_space_available": 74, - "gigs_total_disk_space": 160, + "gigs_disk_space_available": 174.98, + "percent_disk_space_available": 71, + "gigs_total_disk_space": 246, "disk_encryption_enabled": true, "status": "online", - "display_text": "23cfc9caacf0", + "display_text": "Annas-MacBook-Pro.local", + "additional": {}, "issues": { "failing_policies_count": 1, "critical_vulnerabilities_count": 2, // Available in Fleet Premium @@ -2712,14 +2712,14 @@ Returns the information of the specified host. "username": "root", "type": "", "groupname": "root", - "shell": "/bin/bash" + "shell": "/bin/sh" }, { "uid": 1, - "username": "bin", + "username": "annachao", "type": "", - "groupname": "bin", - "shell": "/sbin/nologin" + "groupname": "staff", + "shell": "/bin/zsh" } ], "labels": [ @@ -2738,9 +2738,9 @@ Returns the information of the specified host. "created_at": "2021-08-19T02:02:17Z", "updated_at": "2021-08-19T02:02:17Z", "id": 9, - "name": "CentOS Linux", - "description": "All CentOS hosts", - "query": "SELECT 1 FROM os_version WHERE platform = 'centos' OR name LIKE '%centos%'", + "name": "macOS", + "description": "All macOS hosts", + "query": "select 1 from os_version where platform = 'darwin';", "platform": "", "label_type": "builtin", "label_membership_type": "dynamic" @@ -2749,11 +2749,11 @@ Returns the information of the specified host. "created_at": "2021-08-19T02:02:17Z", "updated_at": "2021-08-19T02:02:17Z", "id": 12, - "name": "All Linux", - "description": "All Linux distributions", - "query": "SELECT 1 FROM osquery_info WHERE build_platform LIKE '%ubuntu%' OR build_distro LIKE '%centos%';", + "name": "Hosts with Chrome installed", + "description": "", + "query": "SELECT * FROM apps WHERE name LIKE \"%Chrome%\"", "platform": "", - "label_type": "builtin", + "label_type": "regular", "label_membership_type": "dynamic" } ], @@ -3090,67 +3090,36 @@ This is the API route used by the **My device** page in Fleet desktop to display "host": { "created_at": "2021-08-19T02:02:22Z", "updated_at": "2021-08-19T21:14:58Z", - "software": [ - { - "id": 408, - "name": "osquery", - "version": "4.5.1", - "source": "rpm_packages", - "browser": "", - "generated_cpe": "", - "vulnerabilities": null - }, - { - "id": 1146, - "name": "tar", - "version": "1.30", - "source": "rpm_packages", - "browser": "", - "generated_cpe": "", - "vulnerabilities": null - }, - { - "id": 321, - "name": "SomeApp.app", - "version": "1.0", - "source": "apps", - "browser": "", - "bundle_identifier": "com.some.app", - "last_opened_at": "2021-08-18T21:14:00Z", - "generated_cpe": "", - "vulnerabilities": null - } - ], "id": 1, "detail_updated_at": "2021-08-19T21:07:53Z", "label_updated_at": "2021-08-19T21:07:53Z", "last_enrolled_at": "2021-08-19T02:02:22Z", "seen_time": "2021-08-19T21:14:58Z", "refetch_requested": false, - "hostname": "23cfc9caacf0", + "hostname": "Annas-MacBook-Pro.local", "uuid": "309a4b7d-0000-0000-8e7f-26ae0815ede8", - "platform": "rhel", - "osquery_version": "4.5.1", - "os_version": "CentOS Linux 8.3.2011", - "build": "", - "platform_like": "rhel", + "platform": "darwin", + "osquery_version": "5.15.0", + "os_version": "macOS 15.2", + "build": "24C101", + "platform_like": "darwin", "code_name": "", "uptime": 210671000000000, "memory": 16788398080, - "cpu_type": "x86_64", - "cpu_subtype": "158", - "cpu_brand": "Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz", - "cpu_physical_cores": 12, - "cpu_logical_cores": 12, - "hardware_vendor": "", - "hardware_model": "", + "cpu_type": "arm64e", + "cpu_subtype": "ARM64E", + "cpu_brand": "Apple M1", + "cpu_physical_cores": 8, + "cpu_logical_cores": 8, + "hardware_vendor": "Apple Inc.", + "hardware_model": "MacBookPro17,1", "hardware_version": "", "hardware_serial": "", - "computer_name": "23cfc9caacf0", - "display_name": "23cfc9caacf0", - "public_ip": "", - "primary_ip": "172.27.0.6", - "primary_mac": "02:42:ac:1b:00:06", + "computer_name": "Anna's MacBook Pro", + "display_name": "Anna's MacBook Pro", + "public_ip": "123.45.678.910", + "primary_ip": "192.12.345.678", + "primary_mac": "36:34:a5:6b:7b:5c", "distributed_interval": 10, "config_tls_refresh": 10, "logger_tls_period": 10, @@ -3158,25 +3127,44 @@ This is the API route used by the **My device** page in Fleet desktop to display "pack_stats": null, "team_name": null, "additional": {}, - "gigs_disk_space_available": 46.1, - "percent_disk_space_available": 74, - "gigs_total_disk_space": 160, + "gigs_disk_space_available": 174.98, + "percent_disk_space_available": 71, + "gigs_total_disk_space": 246, "disk_encryption_enabled": true, "dep_assigned_to_fleet": false, + "status": "online", + "display_text": "Annas-MacBook-Pro.local", + "self_service": true, + "org_logo_url": "https://example.com/logo.jpg", + "license": { + "tier": "free", + "expiration": "2031-01-01T00:00:00Z" + }, + "global_config": { + "mdm": { + "enabled_and_configured": false + } + }, + "batteries": [ + { + "cycle_count": 999, + "health": "Good" + } + ], "users": [ { "uid": 0, "username": "root", "type": "", "groupname": "root", - "shell": "/bin/bash" + "shell": "/bin/sh" }, { "uid": 1, - "username": "bin", + "username": "annachao", "type": "", - "groupname": "bin", - "shell": "/sbin/nologin" + "groupname": "staff", + "shell": "/bin/zsh" } ], "labels": [ @@ -3195,9 +3183,9 @@ This is the API route used by the **My device** page in Fleet desktop to display "created_at": "2021-08-19T02:02:17Z", "updated_at": "2021-08-19T02:02:17Z", "id": 9, - "name": "CentOS Linux", - "description": "All CentOS hosts", - "query": "SELECT 1 FROM os_version WHERE platform = 'centos' OR name LIKE '%centos%'", + "name": "macOS", + "description": "All macOS hosts", + "query": "select 1 from os_version where platform = 'darwin';", "platform": "", "label_type": "builtin", "label_membership_type": "dynamic" @@ -3206,23 +3194,28 @@ This is the API route used by the **My device** page in Fleet desktop to display "created_at": "2021-08-19T02:02:17Z", "updated_at": "2021-08-19T02:02:17Z", "id": 12, - "name": "All Linux", - "description": "All Linux distributions", - "query": "SELECT 1 FROM osquery_info WHERE build_platform LIKE '%ubuntu%' OR build_distro LIKE '%centos%';", + "name": "Hosts with Chrome installed", + "description": "", + "query": "SELECT * FROM apps WHERE name LIKE \"%Chrome%\"", "platform": "", - "label_type": "builtin", + "label_type": "regular", "label_membership_type": "dynamic" } ], - "packs": [], - "status": "online", - "display_text": "23cfc9caacf0", - "batteries": [ + "software": [ { - "cycle_count": 999, - "health": "Good" + "id": 321, + "name": "SomeApp.app", + "version": "1.0", + "source": "apps", + "browser": "", + "bundle_identifier": "com.some.app", + "last_opened_at": "2021-08-18T21:14:00Z", + "generated_cpe": "", + "vulnerabilities": null } ], + "packs": [], "mdm": { "encryption_key_available": true, "enrollment_status": "On (manual)", @@ -3230,7 +3223,7 @@ This is the API route used by the **My device** page in Fleet desktop to display "connected_to_fleet": true, "server_url": "https://acme.com/mdm/apple/mdm", "macos_settings": { - "disk_encryption": null, + "disk_encryption": "verified", "action_required": null }, "macos_setup": { @@ -3240,7 +3233,7 @@ This is the API route used by the **My device** page in Fleet desktop to display }, "os_settings": { "disk_encryption": { - "status": null, + "status": "verified", "detail": "" } }, @@ -3254,17 +3247,6 @@ 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", - "expiration": "2031-01-01T00:00:00Z" - }, - "global_config": { - "mdm": { - "enabled_and_configured": false - } } } ``` From 64cd45753a4e4f7ae458e03668604f93cd12e268 Mon Sep 17 00:00:00 2001 From: Allen Houchins <32207388+allenhouchins@users.noreply.github.com> Date: Thu, 9 Jan 2025 17:02:44 -0600 Subject: [PATCH 105/208] =?UTF-8?q?Add=201Password=20update=20profile=20to?= =?UTF-8?q?=20"=F0=9F=92=BB=20Workstations"=20team=20(#25291)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fleetdm/confidential#9041 Promoting change from "💻🐣 Workstations (canary)" to "💻 Workstations" --- it-and-security/teams/workstations.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/it-and-security/teams/workstations.yml b/it-and-security/teams/workstations.yml index e327ae748bcf..9ae9d70eb7ed 100644 --- a/it-and-security/teams/workstations.yml +++ b/it-and-security/teams/workstations.yml @@ -57,6 +57,9 @@ controls: - path: ../lib/macos/configuration-profiles/secure-terminal-keyboard.mobileconfig - path: ../lib/macos/declaration-profiles/passcode-settings.json - path: ../lib/macos/declaration-profiles/software-update-settings.json + - path: ../lib/macos/configuration-profiles/automatically-update-1Password.mobileconfig + labels_include_any: + - "Macs with 1Password8 installed" macos_setup: bootstrap_package: "" enable_end_user_authentication: true From aab320333a6f570cf3275fa013c08ab3e0c8f539 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 9 Jan 2025 17:13:42 -0600 Subject: [PATCH 106/208] Website: Update flags in regex used to find Vue templates in markdown content. (#25316) Changes: - Updated the flags on the regex used to detect Vue templates in MArkdown content. The regex was incorrectly matching double curly bracket-wrapped variables in markdown code blocks when it should not. --- website/scripts/build-static-content.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/scripts/build-static-content.js b/website/scripts/build-static-content.js index 0c0c7dff2d3f..b07382dfcc81 100644 --- a/website/scripts/build-static-content.js +++ b/website/scripts/build-static-content.js @@ -260,8 +260,8 @@ module.exports = { throw new Error(`A Markdown file (${pageSourcePath}) contains an HTML comment directly after a list that will cause rendering issues when converted to HTML. To resolve this error, add a blank newline before the start of the HTML comment in this file.`); } // Look for anything outside of code blocks in markdown content that could be interpreted as a Vue template when converted to HTML (e.g. {{ foo }}). If any are found, throw an error. - if (mdString.match(/(?\n+])\n+(>)/g, '$1$2'); // « Removes any newlines that might exist before the closing `>` when the compontent is added to markdown files. From 378b404421e2346ca351dbb9b58a04338d445121 Mon Sep 17 00:00:00 2001 From: Rachael Shaw Date: Thu, 9 Jan 2025 18:04:34 -0600 Subject: [PATCH 107/208] Documentation changes for v4.62.0 (#25315) Co-authored-by: Marko Lisica <83164494+marko-lisica@users.noreply.github.com> Co-authored-by: Konstantin Sykulev Co-authored-by: George Karr Co-authored-by: Victor Lyuboslavsky Co-authored-by: Ian Littman Co-authored-by: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Co-authored-by: Lucas Manuel Rodriguez Co-authored-by: Eric --- articles/deploy-software-packages.md | 21 +++- articles/secret-variables.md | 130 ++++++++++++++++++++++ docs/Configuration/yaml-files.md | 10 +- docs/Contributing/API-for-contributors.md | 75 +++++++++++-- docs/Contributing/Audit-logs.md | 9 +- docs/REST API/rest-api.md | 44 ++++++-- website/config/routes.js | 1 + 7 files changed, 262 insertions(+), 28 deletions(-) create mode 100644 articles/secret-variables.md diff --git a/articles/deploy-software-packages.md b/articles/deploy-software-packages.md index b40c4bde5c08..f5483bb5197a 100644 --- a/articles/deploy-software-packages.md +++ b/articles/deploy-software-packages.md @@ -28,12 +28,7 @@ Learn more about automatically installing software in a separate guide [here](ht * Choose a file to upload. `.pkg`, `.msi`, `.exe`, `.rpm`, and `.deb` files are supported. -> Software installer uploads will fail if Fleet is unable to extract information from the installer package such as bundle ID and version number. -> - [.pkg extractor code](https://github.com/fleetdm/fleet/blob/main/pkg/file/xar.go#:~:text=func%20ExtractXARMetadata) -> - [.msi extractor code](https://github.com/fleetdm/fleet/blob/main/pkg/file/msi.go#:~:text=func%20ExtractMSIMetadata) -> - [.exe extractor code](https://github.com/fleetdm/fleet/blob/main/pkg/file/pe.go#:~:text=func%20ExtractPEMetadata) -> - [.deb extractor code](https://github.com/fleetdm/fleet/blob/main/pkg/file/deb.go#:~:text=func%20ExtractDebMetadata) -> - [.rpm extractor code](https://github.com/fleetdm/fleet/blob/main/pkg/file/rpm.go#:~:text=func%20ExtractRPMMetadata) +* Select the hosts that you want to target with this software, under "Target". Select "All hosts" if you want the software to be available to all your hosts. Select "Custom" to scope the software to specific groups of hosts based on label membership. You can select "Include any", which will scope the software to hosts that have any of the labels you select, or "Exclude any", which will scope the software to hosts that do _not_ have the selected labels. * Select the hosts that you want to target with this software, under "Target". Select "All hosts" if you want the software to be available to all your hosts. Select "Custom" to scope the software to specific groups of hosts based on label membership. You can select "Include any", which will scope the software to hosts that have any of the labels you select, or "Exclude any", which will scope the software to hosts that do _not_ have the selected labels. @@ -43,6 +38,20 @@ Learn more about automatically installing software in a separate guide [here](ht > After the initial package upload, all options can be modified, including the self-service setting, pre-install query, scripts, and even the software package file. When replacing an installer package, the replacement package must be the same type and for the same software as the original package. +### Package metadata extraction + +The following metadata is used in uninstall scripts and policies that trigger automatic installation to check whether the software is already installed: +- bundle identifier (`.pkg`) +- product code (`.msi`) +- name (`.deb`, `.rpm`) + +Software installer uploads will fail if Fleet can't extract this metadata and version number. For more details check the extractor code for each package type. +- [.pkg extractor code](https://github.com/fleetdm/fleet/blob/main/pkg/file/xar.go#:~:text=func%20ExtractXARMetadata) +- [.msi extractor code](https://github.com/fleetdm/fleet/blob/main/pkg/file/msi.go#:~:text=func%20ExtractMSIMetadata) +- [.exe extractor code](https://github.com/fleetdm/fleet/blob/main/pkg/file/pe.go#:~:text=func%20ExtractPEMetadata) +- [.deb extractor code](https://github.com/fleetdm/fleet/blob/main/pkg/file/deb.go#:~:text=func%20ExtractDebMetadata) +- [.rpm extractor code](https://github.com/fleetdm/fleet/blob/main/pkg/file/rpm.go#:~:text=func%20ExtractRPMMetadata) + ### Pre-install query A pre-install query is a valid osquery SQL statement that will be evaluated on the host before installing the software. If provided, the installation will proceed only if the query returns any value. diff --git a/articles/secret-variables.md b/articles/secret-variables.md new file mode 100644 index 000000000000..d3ecd4a5d1d0 --- /dev/null +++ b/articles/secret-variables.md @@ -0,0 +1,130 @@ +# How to use secret variables in Fleet + +Fleet [v4.62.0](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.62.0) allows you to use secret variables in Fleet scripts, software install/uninstall scripts, and MDM configuration profiles. Secret variables are encrypted and stored securely in Fleet, enabling you to use sensitive information in your scripts and profiles without exposing it in plain text. Fleet secret variables cannot be retrieved via the Fleet API or UI. + +Examples of sensitive information include: +- API tokens +- Passwords +- Certificates +- Private keys +- Other sensitive data + +## Prerequisites + +- Fleet v4.62.0 + +## How to specify a secret variable + +A secret variable can be used in a script or MDM configuration profile by specifying the variable name in the format `$FLEET_SECRET_MYNAME` or `${FLEET_SECRET_MYNAME}`. When the script or profile is sent to the host, Fleet will replace the variable with the actual secret value. The prefix `FLEET_SECRET_` is required to indicate that the variable is a secret, and Fleet reserves this prefix for secret variables. + +**Example:** + +```xml + + + + + PayloadDisplayName + Certificate PKCS12 + PayloadIdentifier + com.example.certificate + PayloadType + Configuration + PayloadUUID + 918ee83d-ebd5-4192-bcd4-8b4feb750e4b + PayloadVersion + 1 + PayloadContent + + + Password + $FLEET_SECRET_CERT_PASSWORD + PayloadContent + $FLEET_SECRET_CERT_BASE64 + PayloadDisplayName + Certificate PKCS12 + PayloadIdentifier + com.example.certificate + PayloadType + com.apple.security.pkcs12 + PayloadUUID + 25cdd076-f1e7-4932-aa30-1d4240534fb0 + PayloadVersion + 1 + + + + +``` + +In the example above, we use `$FLEET_SECRET_CERT_PASSWORD` and `$FLEET_SECRET_CERT_BASE64` secret variables. + +## Using secret variables with GitOps + +You can configure Fleet using the [best practice GitOps workflow](https://fleetdm.com/docs/configuration/yaml-files). + +You must add the secret variables to your repository's secrets to use them in GitOps. + +For the GitHub GitOps flow, they must also be added to the `env` section of your workflow file, as shown below: + +```yaml + env: + FLEET_URL: ${{ secrets.FLEET_URL }} + FLEET_API_TOKEN: ${{ secrets.FLEET_API_TOKEN }} + FLEET_GLOBAL_ENROLL_SECRET: ${{ secrets.FLEET_GLOBAL_ENROLL_SECRET }} + FLEET_WORKSTATIONS_ENROLL_SECRET: ${{ secrets.FLEET_WORKSTATIONS_ENROLL_SECRET }} + FLEET_WORKSTATIONS_CANARY_ENROLL_SECRET: ${{ secrets.FLEET_WORKSTATIONS_CANARY_ENROLL_SECRET }} + FLEET_SECRET_CERT_PASSWORD: ${{ secrets.FLEET_SECRET_CERT_PASSWORD }} + FLEET_SECRET_CERT_BASE64: ${{ secrets.FLEET_SECRET_CERT_BASE64 }} +``` + +When GitOps syncs the configuration, it looks for secret variables in scripts and profiles, extracts the secret values from the environment, and uploads them to Fleet. + +On subsequent GitOps syncs, if a secret variable used by a configuration profile has been updated, the profile will be resent to the host device(s). + +_Note:_ Profiles with secret variables are not entirely validated during a GitOps dry run because secret variables may not be present/correct in the database during the dry run. Hence, there is an increased chance of GitOps non-dry run failure when using a profile with a secret variable. Try uploading this profile to a test team first. + +## Using secret variables with the Fleet API and UI + +Before uploading a script/profile with secret variables via the Fleet API or UI, you must create the secret variables in Fleet. You can do this with the new secret variables API endpoint. + +**Example:** + +```bash +curl \ +-H "Authorization: Bearer $FLEET_API_TOKEN" \ +-H 'Content-Type: application/json' \ +https://fleet.example.com/api/v1/fleet/spec/secret_variables \ +-X PUT --data-binary @- << EOF +{ "secrets": + [ + { + "name": "FLEET_SECRET_CERT_PASSWORD", + "value": "abc123" + }, + { + "name": "FLEET_SECRET_CERT_BASE64", + "value": "SGVsbG8gV29ybGQh" + } + ] +} +EOF +``` + +Afterward, you can upload the script/profile with secret variables via the Fleet API or UI. + +_Note:_ The checksum of Apple DDM profiles with secret variables now includes the timestamp of the last secrets update. + +## Known limitations and issues + +- Windows profiles are currently not re-sent to the device on fleetctl gitops update: [issue #25030](https://github.com/fleetdm/fleet/issues/25030) +- Fleet does not mask the secret in script results. DO NOT print/echo your secrets to the console output. +- There is no way to explicitly delete a secret variable. Instead, you can overwrite it with any value. +- Do not use deprecated API endpoint(s) to upload profiles containing secret variables. Use endpoints documented in [Fleet's REST API](https://fleetdm.com/docs/rest-api/rest-api). + + + + + + + diff --git a/docs/Configuration/yaml-files.md b/docs/Configuration/yaml-files.md index f232bd675c45..16fe872dec23 100644 --- a/docs/Configuration/yaml-files.md +++ b/docs/Configuration/yaml-files.md @@ -286,7 +286,7 @@ controls: Fleet supports adding [GitHub environment variables](https://docs.github.com/en/actions/learn-github-actions/variables#defining-environment-variables-for-a-single-workflow) in your configuration profiles. Use `$ENV_VARIABLE` format. Variables beginning with `$FLEET_VAR_` are reserved for Fleet server. The server will replace these variables with the actual values when profiles are sent to hosts. See supported variables in the guide [here](https://fleetdm.com/guides/ndes-scep-proxy). -Use `labels_include_all` to only apply (scope) profiles to hosts that have all those labels, `labels_include_any` to apply profiles to hosts that have any of those labels, or `labels_exclude_any` to apply profiles to hosts that don't have any of those labels. +Use `labels_include_all` to target hosts that have all labels in the array, `labels_include_any` to target hosts that have any label in the array, or `labels_exclude_any` to target hosts that don't have any of the labels in the array. Only one of `labels_include_all`, `labels_include_any`, or `labels_exclude_any` can be specified. If none are specified, all hosts are targeted. ### macos_setup @@ -329,11 +329,17 @@ Currently, one app for each of an App Store app's supported platforms are added. software: packages: - path: ../lib/software-name.package.yml + - path: ../lib/software-name2.package.yml + labels_include_any: + - Engineering + - Customer Support # path is relative to default.yml, teams/team-name.yml, or teams/no-team.yml app_store_apps: - app_store_id: '1091189122' ``` +Use `labels_include_any` to target hosts that have any label in the array or `labels_exclude_any` to target hosts that don't have any label in the array. Only one of `labels_include_any` or `labels_exclude_any` can be specified. If neither are specified, all hosts are targeted. + ### packages - `url` specifies the URL at which the software is located. Fleet will download the software and upload it to S3 (default: `""`). @@ -361,7 +367,7 @@ self_service: true > Make sure to include only the ID itself, and not the `id` prefix shown in the URL. The ID must be wrapped in quotes as shown in the example so that it is processed as a string. -`self_service` only applies to macOS, and is ignored for other platforms. For example, if the app is supported on macOS, iOS, and iPadOS, and `self_service` is set to `true`, it will be self-service on macOS workstations but not iPhones or iPads. +- `self_service` only applies to macOS, and is ignored for other platforms. For example, if the app is supported on macOS, iOS, and iPadOS, and `self_service` is set to `true`, it will be self-service on macOS workstations but not iPhones or iPads. ## org_settings and team_settings diff --git a/docs/Contributing/API-for-contributors.md b/docs/Contributing/API-for-contributors.md index f90663de3173..32a08b9190aa 100644 --- a/docs/Contributing/API-for-contributors.md +++ b/docs/Contributing/API-for-contributors.md @@ -1296,6 +1296,7 @@ These API routes are used by the `fleetctl` CLI tool. Users can manage Fleet wit - [Get label](#get-label) - [Get enroll secrets](#get-enroll-secrets) - [Modify enroll secrets](#modify-enroll-secrets) +- [Store secret variables](#store-secret-variables) ### Get queries @@ -1794,19 +1795,29 @@ If the `name` is not already associated with an existing team, this API route cr | name | string | body | **Required.** The team's name. | | agent_options | object | body | The agent options spec that is applied to the hosts assigned to the specified to team. These agent options completely override the global agent options specified in the [`GET /api/v1/fleet/config API route`](#get-configuration) | | features | object | body | The features that are applied to the hosts assigned to the specified to team. These features completely override the global features specified in the [`GET /api/v1/fleet/config API route`](#get-configuration) | -| secrets | list | body | A list of plain text strings is used as the enroll secrets. Existing secrets are replaced with this list, or left unmodified if this list is empty. Note that there is a limit of 50 secrets allowed. | +| secrets | array | body | A list of plain text strings is used as the enroll secrets. Existing secrets are replaced with this list, or left unmodified if this list is empty. Note that there is a limit of 50 secrets allowed. | | mdm | object | body | The team's MDM configuration options. | | mdm.macos_updates | object | body | The OS updates macOS configuration options for Nudge. | | mdm.macos_updates.minimum_version | string | body | The required minimum operating system version. | | mdm.macos_updates.deadline | string | body | The required installation date for Nudge to enforce the operating system version. | | mdm.macos_settings | object | body | The macOS-specific MDM settings. | -| mdm.macos_settings.custom_settings | list | body | The list of objects consists of a `path` to .mobileconfig or JSON file and `labels_include_all`, `labels_include_any`, or `labels_exclude_any` list of label names. | +| mdm.macos_settings.custom_settings | array | body | The list of objects consists of a `path` to .mobileconfig or JSON file and `labels_include_all`, `labels_include_any`, or `labels_exclude_any` list of label names. | | mdm.windows_settings | object | body | The Windows-specific MDM settings. | -| mdm.windows_settings.custom_settings | list | body | The list of objects consists of a `path` to XML files and `labels_include_all`, `labels_include_any`, or `labels_exclude_any` list of label names. | -| scripts | list | body | A list of script files to add to this team so they can be executed at a later time. | +| mdm.windows_settings.custom_settings | array | body | The list of objects consists of a `path` to XML files and `labels_include_all`, `labels_include_any`, or `labels_exclude_any` list of label names. | +| scripts | array | body | A list of script files to add to this team so they can be executed at a later time. | | software | object | body | The team's software that will be available for install. | -| software.packages | list | body | An array of 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, `post_install_script` - script that runs after software install, and `self_service` boolean. | -| software.app_store_apps | list | body | An array of objects. Each object consists of `app_store_id` - ID of the App Store app and `self_service` boolean. | +| software.packages | array | body | An array of objects with values below. | +| software.packages.url | string | body | URL to the software package (PKG, MSI, EXE or DEB). | +| software.packages.install_script | string | body | Command that Fleet runs to install software. | +| software.packages.pre_install_query | string | body | Condition query that determines if the install will proceed. | +| software.packages.post_install_script | string | body | Script that runs after software install. | +| software.packages.uninstall_script | string | body | Command that Fleet runs to uninstall software. | +| software.packages.self_service | boolean | body | If `true` lists software in the self-service. | +| software.packages.labels_include_any | array | body | Target hosts that have any label in the array. Only one of `labels_include_any` or `labels_exclude_any` can be included. If neither are included, all hosts are targeted. | +| software.packages.labels_exclude_any | array | body | Target hosts that don't have any label in the array. Only one of `labels_include_any` or `labels_exclude_any` can be included. If neither are included, all hosts are targeted. | +| software.app_store_apps | array | body | An array of objects with values below. | +| software.app_store_apps.app_store_id | string | body | ID of the App Store app. | +| software.app_store_apps.self_service | boolean | body | Specifies whether or not end users can install self-service. | | mdm.macos_settings.enable_disk_encryption | bool | body | Whether disk encryption should be enabled for hosts that belong to this team. | | force | bool | query | Force apply the spec even if there are (ignorable) validation errors. Those are unknown keys and agent options-related validations. | | dry_run | bool | query | Validate the provided JSON for unknown keys and invalid value types and return any validation errors, but do not apply the changes. | @@ -2137,6 +2148,41 @@ This replaces the active global enroll secrets with the secrets specified. `Status: 200` +### Store secret variables + +Stores secret variables prefixed with `$FLEET_SECRET_` to Fleet. + +`PUT /api/v1/fleet/spec/secret_variables` + +#### Parameters + +| Name | Type | In | Description | +| ------- | ---- | ---- | ---------------------------------------------------------------------------------------------------------------- | +| secrets | list | body | **Required.** List of objects consisting of fields: `name` and `value` +| dry_run | boolean | body | **Optional.** If true, validates the provided secrets and returns any validation errors, but does not apply the changes. + +#### Example + +`PUT /api/v1/fleet/spec/secret_variables` + +##### Request body + +```json +{ + "secrets": [ + { + "name": "FLEET_SECRET_SOME_API_TOKEN", + "value": "971ef02b93c74ca9b22b694a9251f1d6" + } + ] +} + +``` + +##### Default response + +`Status: 200` + --- ## Live query @@ -3946,7 +3992,10 @@ _Available in Fleet Premium_ | dry_run | bool | query | Validate the provided scripts and return any validation errors, but do not apply the changes. | | scripts | array | body | An array of objects with the scripts payloads. Each item must contain `name` with the script name and `script_contents` with the script contents encoded in base64 | -If both `team_id` and `team_name` parameters are included, this endpoint will respond with an error. If no `team_name` or `team_id` is provided, the scripts will be applied for **all hosts**. +If both `team_id` and `team_name` parameters are included, this endpoint will respond with an error. +If no `team_name` or `team_id` is provided, the scripts will be applied for **all hosts**. + +Script contents are uploaded verbatim, without CRLF -> LF conversion. > Note that this endpoint replaces all the active scripts for the specified team (or no team). Any existing script that is not included in the list will be removed, and existing scripts with the same name as a new script will be edited. Providing an empty list of scripts will remove existing scripts. @@ -4030,7 +4079,15 @@ This endpoint is asynchronous, meaning it will start a background process to dow | team_name | string | query | The name of the team to add the software package to. 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 | object | body | The team's software that will be available for install. | -| software.packages | list | body | An array of 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, `post_install_script` - script that runs after software install, and `uninstall_script` - command that Fleet runs to uninstall software. | +| software.packages | array | body | An array of objects with values below. | +| software.packages.url | string | body | URL to the software package (PKG, MSI, EXE or DEB). | +| software.packages.install_script | string | body | Command that Fleet runs to install software. | +| software.packages.pre_install_query | string | body | Condition query that determines if the install will proceed. | +| software.packages.post_install_script | string | body | Script that runs after software install. | +| software.packages.uninstall_script | string | body | Command that Fleet runs to uninstall software. | +| software.packages.self_service | boolean | body | Specifies whether or not end users can install self-service. | +| software.packages.labels_include_any | array | body | Target hosts that have any label in the array. Only one of `labels_include_any` or `labels_exclude_any` can be included. If neither are included, all hosts are targeted. | +| software.packages.labels_exclude_any | array | body | Target hosts that don't have any labels in the array. Only one of `labels_include_any` or `labels_exclude_any` can be included. If neither are included, all hosts are targeted. | #### Example @@ -4125,7 +4182,7 @@ _Available in Fleet Premium._ "app_store_apps": { { "app_store_id": "597799333", - "self_service": false, + "self_service": false }, { "app_store_id": "497799835", diff --git a/docs/Contributing/Audit-logs.md b/docs/Contributing/Audit-logs.md index 19dea07e2f81..10040a0c0859 100644 --- a/docs/Contributing/Audit-logs.md +++ b/docs/Contributing/Audit-logs.md @@ -1238,6 +1238,7 @@ Generated when a software installer is uploaded to Fleet. This activity contains the following fields: - "software_title": Name of the software. - "software_package": Filename of the installer. +- "software_title_id": ID of the added software title. - "team_name": Name of the team to which this software was added. `null` if it was added to no team." + - "team_id": The ID of the team to which this software was added. `null` if it was added to no team. - "self_service": Whether the software is available for installation by the end user. @@ -1251,6 +1252,7 @@ This activity contains the following fields: { "software_title": "Falcon.app", "software_package": "FalconSensor-6.44.pkg", + "software_title_id": 2344, "team_name": "Workstations", "team_id": 123, "self_service": true, @@ -1275,10 +1277,10 @@ Generated when a software installer is updated in Fleet. This activity contains the following fields: - "software_title": Name of the software. - "software_package": Filename of the installer as of this update (including if unchanged). +- "software_title_id": ID of the edited software title. - "team_name": Name of the team on which this software was updated. `null` if it was updated on no team. - "team_id": The ID of the team on which this software was updated. `null` if it was updated on no team. - "self_service": Whether the software is available for installation by the end user. -- "software_title_id": ID of the added software title. - "labels_include_any": Target hosts that have any label in the array. - "labels_exclude_any": Target hosts that don't have any label in the array. @@ -1288,10 +1290,10 @@ This activity contains the following fields: { "software_title": "Falcon.app", "software_package": "FalconSensor-6.44.pkg", + "software_title_id": 2344, "team_name": "Workstations", "team_id": 123, "self_service": true, - "software_title_id": 2234, "labels_include_any": [ { "name": "Engineering", @@ -1382,6 +1384,8 @@ This activity contains the following fields: - "self_service": App installation can be initiated by device owner. - "team_name": Name of the team to which this App Store app was added, or `null` if it was added to no team. - "team_id": ID of the team to which this App Store app was added, or `null`if it was added to no team. +- "software_title_id": ID of the edited software title. + #### Example @@ -1392,6 +1396,7 @@ This activity contains the following fields: "app_store_id": "1234567", "platform": "darwin", "self_service": false, + "software_title_id": 3537, "team_name": "Workstations", "team_id": 1 } diff --git a/docs/REST API/rest-api.md b/docs/REST API/rest-api.md index 495eac40c639..757798a38c8f 100644 --- a/docs/REST API/rest-api.md +++ b/docs/REST API/rest-api.md @@ -5051,9 +5051,11 @@ Add a configuration profile to enforce custom settings on macOS and Windows host | ------------------------- | -------- | ---- | ------------------------------------------------------------------------------------------------------------- | | profile | file | form | **Required.** The .mobileconfig and JSON for macOS or XML for Windows file containing the profile. | | team_id | string | form | _Available in Fleet Premium_. The team ID for the profile. If specified, the profile is applied to only hosts that are assigned to the specified team. If not specified, the profile is applied to only to hosts that are not assigned to any team. | -| labels_include_all | array | form | _Available in Fleet Premium_. Profile will only be applied to hosts that have all of these labels. Only one of either `labels_include_all`, `labels_include_any` or `labels_exclude_any` can be included in the request. | -| labels_include_any | array | form | _Available in Fleet Premium_. Profile will only be applied to hosts that have any of these labels. Only one of either `labels_include_all`, `labels_include_any` or `labels_exclude_any` can be included in the request. | -| labels_exclude_any | array | form | _Available in Fleet Premium_. Profile will be applied to hosts that don’t have any of these labels. Only one of either `labels_include_all`, `labels_include_any` or `labels_exclude_any` can be included in the request. | +| labels_include_all | array | form | _Available in Fleet Premium_. Target hosts that have all labels in the array. | +| labels_include_any | array | form | _Available in Fleet Premium_. Target hosts that have any label in the array. | +| labels_exclude_any | array | form | _Available in Fleet Premium_. Target hosts that that don’t have any label in the array. | + +Only one of `labels_include_all`, `labels_include_any`, or `labels_exclude_any` can be specified. If none are specified, all hosts are targeted. #### Example @@ -7093,7 +7095,7 @@ The semantics for creating a team policy are the same as for global policies, se | resolution | string | body | The resolution steps for the policy. | | platform | string | body | Comma-separated target platforms, currently supported values are "windows", "linux", "darwin". The default, an empty string means target all platforms. | | critical | boolean | body | _Available in Fleet Premium_. Mark policy as critical/high impact. | -| software_title_id | integer | body | _Available in Fleet Premium_. ID of software title to install if the policy fails. | +| software_title_id | integer | body | _Available in Fleet Premium_. ID of software title to install if the policy fails. If `software_title_id` is specified and the software has `labels_include_any` or `labels_exclude_any` defined, the policy will inherit this target in addition to specified `platform`. | | script_id | integer | body | _Available in Fleet Premium_. ID of script to run if the policy fails. | Either `query` or `query_id` must be provided. @@ -7203,8 +7205,8 @@ Either `query` or `query_id` must be provided. | platform | string | body | Comma-separated target platforms, currently supported values are "windows", "linux", "darwin". The default, an empty string means target all platforms. | | critical | boolean | body | _Available in Fleet Premium_. Mark policy as critical/high impact. | | calendar_events_enabled | boolean | body | _Available in Fleet Premium_. Whether to trigger calendar events when policy is failing. | -| software_title_id | integer | body | _Available in Fleet Premium_. ID of software title to install if the policy fails. Set to `0` to remove the automation. | -| script_id | integer | body | _Available in Fleet Premium_. ID of script to run if the policy fails. Set to `0` to remove the automation. | +| software_title_id | integer | body | _Available in Fleet Premium_. ID of software title to install if the policy fails. Set to `null` to remove the automation. | +| script_id | integer | body | _Available in Fleet Premium_. ID of script to run if the policy fails. Set to `null` to remove the automation. | #### Example @@ -7463,9 +7465,10 @@ Returns the query report specified by ID. #### Parameters -| Name | Type | In | Description | -| --------- | ------- | ----- | ------------------------------------------ | -| id | integer | path | **Required**. The ID of the desired query. | +| Name | Type | In | Description | +| --------- | ------- | ----- | ----------------------------------------------------------------------------------------- | +| id | integer | path | **Required**. The ID of the desired query. | +| team_id | integer | query | Filter the query report to only include hosts that are associated with the team specified | #### Example @@ -8458,6 +8461,9 @@ Uploads a script, making it available to run on hosts assigned to the specified | script | file | form | **Required**. The file containing the script. | | team_id | integer | form | _Available in Fleet Premium_. The team ID. If specified, the script will only be available to hosts assigned to this team. If not specified, the script will only be available to hosts on **no team**. | +Script line endings are automatically converted from [CRLF to LF](https://en.wikipedia.org/wiki/Newline) for compatibility with both +non-Windows shells and PowerShell. + #### Example `POST /api/v1/fleet/scripts` @@ -9013,6 +9019,12 @@ Returns information about the specified software. By default, `versions` are sor "post_install_script": "sudo /Applications/Falcon.app/Contents/Resources/falconctl license 0123456789ABCDEFGHIJKLMNOPQRSTUV-WX", "uninstall_script": "/Library/CS/falconctl uninstall", "self_service": true, + "labels_include_any": [ + { + "name": "Engineering", + "id": 294 + } + ], "automatic_install_policies": [ { "id": 343, @@ -9237,7 +9249,11 @@ Add a package (.pkg, .msi, .exe, .deb, .rpm) to install on macOS, Windows, or Li | pre_install_query | string | form | Query that is pre-install condition. If the query doesn't return any result, Fleet won't proceed to install. | | post_install_script | string | form | The contents of the script to run after install. If the specified script fails (exit code non-zero) software install will be marked as failed and rolled back. | | self_service | boolean | form | Self-service software is optional and can be installed by the end user. | +| labels_include_any | array | form | Target hosts that have any label in the array. | +| labels_exclude_any | array | form | Target hosts that don't have any label in the array. | +| automatic_install | boolean | form | Automatically create policy that triggers install if software isn't installed on the host. | +Only one of `labels_include_any` or `labels_exclude_any` can be specified. If neither are specified, all hosts are targeted. #### Example @@ -9300,8 +9316,13 @@ Update a package to install on macOS, Windows, or Linux (Ubuntu) hosts. | pre_install_query | string | form | Query that is pre-install condition. If the query doesn't return any result, the package will not be installed. | | post_install_script | string | form | The contents of the script to run after install. If the specified script fails (exit code non-zero) software install will be marked as failed and rolled back. | | self_service | boolean | form | Whether this is optional self-service software that can be installed by the end user. | +| labels_include_any | array | form | Target hosts that have any label in the array. Only one of either `labels_include_any` or `labels_exclude_any` can be specified. | +| labels_exclude_any | array | form | Target hosts that don't have any label in the array. | + +Only one of `labels_include_any` or `labels_exclude_any` can be specified. If neither are specified, all hosts are targeted. > Changes to the installer package will reset installation counts. Changes to any field other than `self_service` will cancel pending installs for the old package. + #### Example `PATCH /api/v1/fleet/software/titles/1/package` @@ -9451,6 +9472,7 @@ Add App Store (VPP) app purchased in Apple Business Manager. `Status: 200` + ### List Fleet-maintained apps > **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows. @@ -9559,6 +9581,10 @@ Add Fleet-maintained app so it's available for install. | pre_install_query | string | body | Query that is pre-install condition. If the query doesn't return any result, Fleet won't proceed to install. | | post_install_script | string | body | The contents of the script to run after install. If the specified script fails (exit code non-zero) software install will be marked as failed and rolled back. | | self_service | boolean | body | Self-service software is optional and can be installed by the end user. | +| labels_include_any | array | form | Target hosts that have any label in the array. | +| labels_exclude_any | array | form | Target hosts that don't have any label in the array. | + +Only one of `labels_include_any` or `labels_exclude_any` can be specified. If neither are specified, all hosts are targeted. #### Example diff --git a/website/config/routes.js b/website/config/routes.js index 2e8ef0c80487..df3b53383709 100644 --- a/website/config/routes.js +++ b/website/config/routes.js @@ -818,6 +818,7 @@ module.exports.routes = { 'GET /learn-more-about/exe-install-scripts': '/guides/exe-install-scripts', 'GET /learn-more-about/install-scripts': '/guides/deploy-software-packages#install-script', 'GET /learn-more-about/uninstall-scripts': '/guides/deploy-software-packages#uninstall-script', + 'GET /learn-more-about/package-metadata-extraction': '/guides/deploy-software-packages#package-metadata-extraction', 'GET /learn-more-about/read-package-version': '/guides/deploy-software-packages#add-a-software-package-to-a-team', 'GET /learn-more-about/fleetctl': '/guides/fleetctl', 'GET /feature-request': 'https://github.com/fleetdm/fleet/issues/new?assignees=&labels=~feature+fest%2C%3Aproduct&projects=&template=feature-request.md&title=', From 95862328d41b26dade8962fc3cf81e4e1e56b2fe Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Thu, 9 Jan 2025 19:10:53 -0500 Subject: [PATCH 108/208] Release article: Fleet 4.62.0 (#25255) Co-authored-by: Luke Heath Co-authored-by: Drew Baker <89049099+Drew-P-drawers@users.noreply.github.com> --- articles/fleet-4.62.0.md | 111 ++++++++++++++++++ .../articles/fleet-4.62.0-1600x900@2x.png | Bin 0 -> 53319 bytes 2 files changed, 111 insertions(+) create mode 100644 articles/fleet-4.62.0.md create mode 100644 website/assets/images/articles/fleet-4.62.0-1600x900@2x.png diff --git a/articles/fleet-4.62.0.md b/articles/fleet-4.62.0.md new file mode 100644 index 000000000000..b82d33db17b2 --- /dev/null +++ b/articles/fleet-4.62.0.md @@ -0,0 +1,111 @@ +# Fleet 4.62.0 | Custom targets and automatic policies for software, secrets in configuration profiles and scripts + +
+ +
+ +Fleet 4.62.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.62.0) or continue reading to get the highlights. +For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. + +## Highlights +- Custom targets for software installs +- Automatic policies for custom packages +- Hide secrets in configuration profiles and scripts + +### Custom targets for software installs + +IT admins can now install Fleet-maintained apps and custom packages only on macOS, Windows, and Linux hosts within specific labels. This lets you target installations more precisely, tailoring deployments by department, role, or hardware. Learn more about deploying software [here](https://fleetdm.com/guides/deploy-software-packages). + +### Automatic policies for custom packages + +Fleet now creates policies automatically when you add a custom package. This eliminates the need to manually write policies, making it faster and easier to deploy software across all your hosts. Learn more about automatically installing software [here](https://fleetdm.com/guides/automatic-software-install-in-fleet). + +### Hide secrets in configuration profiles and scripts + +Fleet ensures that GitHub or GitLab secrets, like API tokens and license keys used in scripts (Shell & PowerShell) and configuration profiles (macOS & Windows), are hidden when viewed or downloaded in Fleet. This protects sensitive information, keeping it secure until it’s deployed to the hosts. Learn more about secrets [here](https://fleetdm.com/secret-variables). + +## Changes + +## Endpoint operations +- Updated macos 13, 14 per latest CIS documents. Added macos 15 support. +- Updated queries API to support above targeted platform filtering. +- Updated UI queries page to filter, sort, paginate, etc. via query params in call to server. +- Added searchable query targets and cleaner UI for uses with many teams or labels. + +## Device management (MDM) +- Added ability to use secrets (`$FLEET_SECRET_YOURNAME`) in scripts and profiles. +- Added ability to scope Fleet-maintained apps and custom packages via labels in UI, API, and CLI. +- Added capability to automatically generate "trigger policies" for custom software packages. +- Added UI for scoping software via labels. +- Added validation to prevent label deletion if it is used to scope the hosts targeted by a software installer. +- Added ability to filter host software based on label scoping. +- Added support for Fleet secret validation in software installer scripts. +- Updated `fleetctl gitops` to support scope software installers by labels, with the `labels_include_any` or `labels_exclude_any` conditions. +- Updated `fleetctl gitops` to identify secrets in scripts and profiles and saves them on the Fleet server. +- Updated `fleetctl gitops` so that when it updates profiles, if the secret value has changed, the profile is updated on the host. +- Added `/fleet/spec/secret_variables` API endpoint. +- Added functionality for skipping automatic installs if the software is not scoped to the host via labels. +- Added the ability to click a software row on the my device page and see the details of that software's installation on the host. +- Allowed software uninstalls and script-based host lock/unlock/wipe to run while global scripts are disabled. + +## Vulnerability management +- Added missing vulncheck data from NVD feeds. +- Fixed MSI parsing for packages including long interned strings (e.g. licenses for the OpenVPN Connect installer). +- Fixed a panic (and resulting failure to load CVE details) on new installs when OS versions have not been populated yet. +- Fixed CVE-2024-10004 false positive on Fleet-supported platforms (vuln is iOS-only and iOS vuln checking is not supported). + +## Bug fixes and improvements +- Added license key validation on `fleetctl preview` if a license key is provided; fixes cases where an invalid license key would cause `fleetctl preview` to hang. +- Increased maximum length for installer URLs specified in GitOps to 4000 characters. +- Stopped older scheduled queries from filling logs with errors. +- Changed script upload endpoint (`POST /api/v1/fleet/scripts`) to automatically switch CRLF line endings to LF. +- Fleshed out server response from `queries` endpoint to include `count` and `meta` pagination information. +- Updated platform filtering on queries page to refer to targeted platforms instead of compatible platforms. +- Included osquery pre-releases in daily UI constant update GitHub Actions job. +- Updated to send alert via SNS when a scheduled "cron" job returns errors. +- SNS topic for job error alerts can be configured separately from the existing monitor alert by adding "cron_job_failure_monitoring" to sns_topic_arns_map, otherwise defaults to the using the same topic. +- Improved validation workflow on SMTP settings page. +- Allowed team policy endpoint (`PATCH /api/latest/fleet/teams/{team_id}/policies/{policy_id}`) to receive explicit `null` as a value for `script_id` or `software_title_id` to unset a script or software installer respectively. +- Aliased EAP versions of JetBrains IDEs to "last release version plus all fixes" (e.g. 2024.3 EAP -> 2024.2.99) to avoid vulnerability false positives. +- Removed server error if no private IP was found by detail_query_network_interface. +- Updated `fleetctl` dependencies that cause warnings. +- Added service annotation field to Helm Chart. +- Updated so that on policy deletion any associated pending software installer or scripts are deleted. +- Added fallback to FileVersion on EXE installers when FileVersion is set but ProductVersion isn't to allow more custom packages to be uploaded. +- Added Mastodon icon and URL to server email templates. +- Improved table text wrapper in UI. +- Added helpful tooltip for the install software setup experience page. +- Added offset to the tooltips on hover of the profile aggregate status indicators. +- Added the `software_title_id` field to the `added_software` activity details. +- Allow maintainers to manage install software or run scripts on policy automations. +- Removed duplicate software records from homebrew casks already reported in the osquery `apps` table to address false positive vulnerabilities due to lack of bundle_identifier. +- Added the `labels_include_any` and `labels_exclude_any` fields to the software installer activities. +- Updated the get host endpoint to include disk encryption stats for a linux host only if the setting is enabled. +- Updated Helm chart to support customization options such as the Google cloud_sql_proxy in the fleet-migration job. +- Updated example windows policies. +- Added a descriptive error when a GitOps file contains script references that are missing paths. +- Removed `invalid UUID` log message when validating Apple MDM UDID. +- Added validation Fleet secrets embedded into scripts and profiles on ingestion. +- Display the correct percentage of hosts online when there are no hosts online. +- Fixed bug when creating a label to preserve the selected team. +- Fixed export to CSV trimming leading zeros by treating those values as strings. +- Fixed reporting of software uninstall results after a host has been locked/unlocked. +- Fixed issue where minio software was not scanned for vulnerabilities correctly because of unexpected trailing characters in the version string. +- Fixed bug on the "Controls" page where incorrect timestamp information was displayed while the "Current versions" table was loading. +- Fixed policy truncation UI bug. +- Fixed cases where showing results of an inherited query viewed inside a team would include results from hosts not on thta team by adding an optional team_id parameter to queris report endpoint (`GET /api/latest/fleet/queries/{query_id}/report`). +- Fixed issue where deleted Apple config profiles were installing on devices because devices were offline when the profile was added. +- Fixed UI bug involving pagination of subsections within the "Controls" page. +- Fixed "Verifying" disk encryption status count and filter for macOS hosts to not include hosts where end-user action is required. +- Fixed a bug in determining sort type of query result columns by deducing that type from the data present in those columns. + +## Ready to upgrade? + +Visit our [Upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs for instructions on updating to Fleet 4.62.0. + + + + + + + diff --git a/website/assets/images/articles/fleet-4.62.0-1600x900@2x.png b/website/assets/images/articles/fleet-4.62.0-1600x900@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e90bb1c3f3c9ae7127115e58ccae003d6d32a654 GIT binary patch literal 53319 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 H$SAKRX literal 0 HcmV?d00001 From 940f190c3881274d3b075fd4f56cd86c393d0c56 Mon Sep 17 00:00:00 2001 From: Tim Lee Date: Thu, 9 Jan 2025 19:12:54 -0500 Subject: [PATCH 109/208] Docs: disk encryption (#25314) --- articles/enforce-disk-encryption.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/articles/enforce-disk-encryption.md b/articles/enforce-disk-encryption.md index d6ec9287183c..c87091e7c027 100644 --- a/articles/enforce-disk-encryption.md +++ b/articles/enforce-disk-encryption.md @@ -2,7 +2,7 @@ _Available in Fleet Premium_ -In Fleet, you can enforce disk encryption for your macOS and Windows hosts, and verify disk encryption for Ubuntu Linux and Fedora Linux hosts. +In Fleet, you can enforce disk encryption for your macOS and Windows hosts, and verify disk encryption for Ubuntu Linux, Kubuntu Linux and Fedora Linux hosts. > Apple calls this [FileVault](https://support.apple.com/en-us/HT204837), Microsoft calls this [BitLocker](https://learn.microsoft.com/en-us/windows/security/operating-system-security/data-protection/bitlocker/), and Linux typically uses [LUKS](https://en.wikipedia.org/wiki/Linux_Unified_Key_Setup) (Linux Unified Key Setup). @@ -48,9 +48,9 @@ You can click each status to view the list of hosts for that status. ## Enforce disk encryption on Linux -To enforce disk encryption on Ubuntu Linux and Fedora Linux devices, Fleet supports Linux Unified Key Setup (LUKS) for encrypting volumes. Support for Ubuntu 20.04 is coming soon. +Fleet supports Linux Unified Key Setup (LUKS) for encrypting volumes to enforce disk encryption on Ubuntu Linux, Kubuntu Linux, and Fedora Linux devices. Support for Ubuntu 20.04 will be available soon. -1. Share [this step-by-step guide](https://fleetdm.com/learn-more-about/encrypt-linux-device) with end users setting up a work computer running Ubuntu Linux or Fedora Linux. +1. Share [this step-by-step guide](https://fleetdm.com/learn-more-about/encrypt-linux-device) with end users setting up a work computer running Ubuntu Linux, Kubuntu Linux or Fedora Linux. > Note that full disk encryption can only enabled during operating system setup. If the operating system has already been installed, the end user will be required to re-install the OS to enable disk encryption. From 464d99dd192120f4557f9eb0bfaac3bf665aca1c Mon Sep 17 00:00:00 2001 From: Allen Houchins <32207388+allenhouchins@users.noreply.github.com> Date: Fri, 10 Jan 2025 08:56:34 -0600 Subject: [PATCH 110/208] Fixed broken link (#25323) --- articles/fleet-4.62.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/articles/fleet-4.62.0.md b/articles/fleet-4.62.0.md index b82d33db17b2..24926d2d927f 100644 --- a/articles/fleet-4.62.0.md +++ b/articles/fleet-4.62.0.md @@ -22,7 +22,7 @@ Fleet now creates policies automatically when you add a custom package. This eli ### Hide secrets in configuration profiles and scripts -Fleet ensures that GitHub or GitLab secrets, like API tokens and license keys used in scripts (Shell & PowerShell) and configuration profiles (macOS & Windows), are hidden when viewed or downloaded in Fleet. This protects sensitive information, keeping it secure until it’s deployed to the hosts. Learn more about secrets [here](https://fleetdm.com/secret-variables). +Fleet ensures that GitHub or GitLab secrets, like API tokens and license keys used in scripts (Shell & PowerShell) and configuration profiles (macOS & Windows), are hidden when viewed or downloaded in Fleet. This protects sensitive information, keeping it secure until it’s deployed to the hosts. Learn more about secrets [here](https://fleetdm.com/guides/secret-variables). ## Changes From 5873cb9ef7ad7cd609a4df876ddb51ddb2585021 Mon Sep 17 00:00:00 2001 From: RachelElysia <71795832+RachelElysia@users.noreply.github.com> Date: Fri, 10 Jan 2025 10:37:55 -0500 Subject: [PATCH 111/208] Fleet UI: Software headers more responsive (#25212) --- .../TableContainer/TableContainer.tsx | 21 ++--- .../components/TableContainer/_styles.scss | 64 ++++++--------- .../DropdownWrapper.stories.tsx | 81 ++++++++++++------- .../DropdownWrapper/DropdownWrapper.tsx | 26 +++++- .../SoftwareTable/SoftwareTable.tsx | 43 ++++++---- .../SoftwareTitles/SoftwareTable/_styles.scss | 22 +---- .../SoftwareVulnerabilitiesTable.tests.tsx | 7 +- .../SoftwareVulnerabilitiesTable.tsx | 20 +++-- .../SoftwareVulnerabilitiesTable/_styles.scss | 4 + .../SoftwareVulnerabilitiesTable/helpers.ts | 10 +-- .../SoftwareVulnerabilities/_styles.scss | 3 - 11 files changed, 161 insertions(+), 140 deletions(-) diff --git a/frontend/components/TableContainer/TableContainer.tsx b/frontend/components/TableContainer/TableContainer.tsx index db1a2196276f..648c6d7d47c6 100644 --- a/frontend/components/TableContainer/TableContainer.tsx +++ b/frontend/components/TableContainer/TableContainer.tsx @@ -83,7 +83,7 @@ interface ITableContainerProps { onQueryChange?: | ((queryData: ITableQueryData) => void) | ((queryData: ITableQueryData) => number); - customControl?: () => JSX.Element; + customControl?: () => JSX.Element | null; /** Filter button right of the search rendering alternative responsive design where search bar moves to new line but filter button remains inline with other table headers */ customFiltersButton?: () => JSX.Element; stackControls?: boolean; @@ -288,11 +288,11 @@ const TableContainer = ({ const opacity = isLoading ? { opacity: 0.4 } : { opacity: 1 }; // New preferred pattern uses grid container/box to allow for more dynamic responsiveness - // At low widths, search bar (3rd div of 4) moves above other 3 divs - if (customFiltersButton) { + // At low widths, right header stacks on top of left header + if (stackControls) { return (
-
+
{renderCount && !disableCount && (
({
)}
-
- {actionButton && !actionButton.hideButton && ( + + {actionButton && !actionButton.hideButton && ( +
- )} +
+ )} +
{customControl && customControl()} -
-
{searchable && !wideSearch && (
({
)} + {customFiltersButton && customFiltersButton()}
-
{customFiltersButton()}
); } diff --git a/frontend/components/TableContainer/_styles.scss b/frontend/components/TableContainer/_styles.scss index f15fdf5824eb..13805e126385 100644 --- a/frontend/components/TableContainer/_styles.scss +++ b/frontend/components/TableContainer/_styles.scss @@ -6,14 +6,14 @@ // Container is responsive design used when customFilters is rendered .container { display: grid; - grid-template-columns: 1fr auto auto; /* First column takes all remaining space */ - grid-template-rows: auto auto; /* Two rows */ + grid-template-columns: 1fr auto; /* First column takes all remaining space */ + grid-template-rows: auto auto; /* Two rows for smaller screens*/ width: 100%; height: max-content; gap: $pad-small $pad-medium; } - .box { + .stackable-header { min-width: max-content; align-content: center; display: flex; @@ -24,56 +24,46 @@ display: flex; flex-direction: row; } + + // only if in stackable header + .table-container__search { + width: 100%; + } } - .search { + .top-shift-header { grid-column: 1 / -1; /* Span across all columns */ grid-row: 1; /* Place in the first row */ - } - .box:nth-child(1) { - grid-column: 1 / span 2; /* Make Box 1 expand across two columns */ - grid-row: 2; + .Select-multi-value-wrapper { + height: 36px; // Fixes height issues + width: 236px; + } } - .box:nth-child(2) { - grid-column: 2; /* Place Box 2 in the second row, second column */ + .stackable-header:nth-child(1) { + grid-column: 1 / span 2; /* Make Header 1 expand across two columns */ grid-row: 2; - } - .box:nth-child(4) { - grid-column: 3; /* Place Box 4 in the second row, third column */ - grid-row: 2; - max-width: min-content; + .form-field--dropdown { + width: 235px; + } } /* Media query for larger screens */ - @media (min-width: $table-controls-break) { + @media (min-width: $break-md) { .container { - grid-template-columns: 1fr auto auto auto; /* First column takes all remaining space */ + grid-template-columns: 1fr auto; /* First column takes all remaining space */ grid-template-rows: auto; /* Single row */ } - .search { - grid-column: 1 / -1; /* Keep spanning across all columns if needed */ - grid-row: auto; + .top-shift-header { + grid-column: 2; /* Single row */ } - .box:nth-child(1) { - grid-column: 1; /* Ensure Box 1 stays in the first column */ - } - - .box:nth-child(2) { - grid-column: 2; /* Place Box 2 in the second column */ - } - - .box:nth-child(3) { - grid-column: 3; /* Place Box 3 in the third column */ - grid-row: 2; - } - - .box:nth-child(4) { - grid-column: 4; /* Place Box 4 in the fourth column */ + .stackable-header:nth-child(1) { + grid-column: 1; /* Ensure Header 1 stays in the first column */ + grid-row: 1; /* Single row */ } } @@ -138,10 +128,6 @@ justify-content: space-between; } } - - .Select-multi-value-wrapper { - height: 38px; // Fixes overlap with .Select outline - } } &__results-count { diff --git a/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.stories.tsx b/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.stories.tsx index ee0d223c58ca..4a869b7ca521 100644 --- a/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.stories.tsx +++ b/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.stories.tsx @@ -1,62 +1,81 @@ // stories/DropdownWrapper.stories.tsx import React from "react"; -import { Meta, Story } from "@storybook/react"; -import DropdownWrapper, { - IDropdownWrapper, - CustomOptionType, -} from "./DropdownWrapper"; +import type { Meta, StoryObj } from "@storybook/react"; +import DropdownWrapper, { CustomOptionType } from "./DropdownWrapper"; // Define metadata for the story -export default { +const meta: Meta = { title: "Components/DropdownWrapper", component: DropdownWrapper, argTypes: { onChange: { action: "changed" }, }, -} as Meta; + // Padding added to view tooltips + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export default meta; -// Define a template for the stories -const Template: Story = (args) => ( - -); +type Story = StoryObj; // Sample options to be used in the dropdown const sampleOptions: CustomOptionType[] = [ - { label: "Option 1", value: "option1", helpText: "Help text for option 1" }, { - label: "Option 2", + label: "Option 1 - just help text", + value: "option1", + helpText: "Help text for option 1", + }, + { + label: "Option 2 - just tooltip", value: "option2", tooltipContent: "Tooltip for option 2", }, - { label: "Option 3", value: "option3", isDisabled: true }, + { label: "Option 3 - just disabled", value: "option3", isDisabled: true }, + { + label: "Option 4 - help text, disabled, and tooltip", + value: "option4", + helpText: "Help text for option 4", + isDisabled: true, + tooltipContent: "Tooltip for option 4", + }, ]; // Default story -export const Default = Template.bind({}); -Default.args = { - options: sampleOptions, - name: "dropdown-example", - label: "Select an option", +export const Default: Story = { + args: { + options: sampleOptions, + name: "dropdown-example", + label: "Select an option", + }, }; // Disabled story -export const Disabled = Template.bind({}); -Disabled.args = { - ...Default.args, - isDisabled: true, +export const Disabled: Story = { + args: { + ...Default.args, + isDisabled: true, + }, }; // With Help Text story -export const WithHelpText = Template.bind({}); -WithHelpText.args = { - ...Default.args, - helpText: "This is some help text for the dropdown", +export const WithHelpText: Story = { + args: { + ...Default.args, + helpText: "This is some help text for the dropdown", + }, }; // With Error story -export const WithError = Template.bind({}); -WithError.args = { - ...Default.args, - error: "This is an error message", +export const WithError: Story = { + args: { + ...Default.args, + error: "This is an error message", + }, }; diff --git a/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tsx b/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tsx index 789d17679c3b..1b8e51f50941 100644 --- a/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tsx +++ b/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tsx @@ -26,6 +26,7 @@ import { PADDING } from "styles/var/padding"; import FormField from "components/forms/FormField"; import DropdownOptionTooltipWrapper from "components/forms/fields/Dropdown/DropdownOptionTooltipWrapper"; import Icon from "components/Icon"; +import { IconNames } from "components/icons"; const getOptionBackgroundColor = (state: any) => { return state.isSelected || state.isFocused @@ -39,6 +40,7 @@ export interface CustomOptionType { tooltipContent?: string; helpText?: string; isDisabled?: boolean; + iconName?: IconNames; } export interface IDropdownWrapper { @@ -53,6 +55,7 @@ export interface IDropdownWrapper { helpText?: JSX.Element | string; isSearchable?: boolean; isDisabled?: boolean; + iconName?: IconNames; placeholder?: string; /** E.g. scroll to view dropdown menu in a scrollable parent container */ onMenuOpen?: () => void; @@ -70,8 +73,9 @@ const DropdownWrapper = ({ error, label, helpText, - isSearchable, + isSearchable = false, isDisabled = false, + iconName, placeholder, onMenuOpen, }: IDropdownWrapper) => { @@ -120,7 +124,7 @@ const DropdownWrapper = ({ }; const CustomDropdownIndicator = ( - props: DropdownIndicatorProps + props: DropdownIndicatorProps ) => { const { isFocused, selectProps } = props; const color = @@ -142,6 +146,19 @@ const DropdownWrapper = ({ ); }; + const ValueContainer = ({ children, ...props }: any) => { + return ( + components.ValueContainer && ( + + {!!children && iconName && ( + + )} + {children} + + ) + ); + }; + const customStyles: StylesConfig = { container: (provided) => ({ ...provided, @@ -235,10 +252,13 @@ const DropdownWrapper = ({ menuList: (provided) => ({ ...provided, padding: PADDING["pad-small"], + maxHeight: "none", }), valueContainer: (provided) => ({ ...provided, padding: 0, + display: "flex", + gap: PADDING["pad-small"], }), option: (provided, state) => ({ ...provided, @@ -260,7 +280,6 @@ const DropdownWrapper = ({ color: COLORS["ui-fleet-black-50"], fontStyle: "italic", cursor: "not-allowed", - pointerEvents: "none", }), // Styles for custom option ".dropdown-wrapper__option": { @@ -323,6 +342,7 @@ const DropdownWrapper = ({ Option: CustomOption, DropdownIndicator: CustomDropdownIndicator, IndicatorSeparator: () => null, + ValueContainer, }} value={getCurrentValue()} onChange={handleChange} diff --git a/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareTable.tsx b/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareTable.tsx index 5af6fcee5f26..5bcb4cb75ed1 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareTable.tsx +++ b/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareTable.tsx @@ -21,8 +21,6 @@ import { } from "services/entities/software"; import { ISoftwareTitle, ISoftwareVersion } from "interfaces/software"; -// @ts-ignore -import Dropdown from "components/forms/fields/Dropdown"; import TableContainer from "components/TableContainer"; import Slider from "components/forms/fields/Slider"; import CustomLink from "components/CustomLink"; @@ -32,6 +30,9 @@ import TableCount from "components/TableContainer/TableCount"; import Button from "components/buttons/Button"; import Icon from "components/Icon"; import TooltipWrapper from "components/TooltipWrapper"; +import { SingleValue } from "react-select-5"; +import DropdownWrapper from "components/forms/fields/DropdownWrapper"; +import { CustomOptionType } from "components/forms/fields/DropdownWrapper/DropdownWrapper"; import EmptySoftwareTable from "pages/SoftwarePage/components/EmptySoftwareTable"; @@ -284,28 +285,36 @@ const SoftwareTable = ({ } /> )} + ); }; const renderCustomControls = () => { + // Hidden when viewing versions table + if (showVersions) { + return null; + } + return (
- {!showVersions && ( // Hidden when viewing versions table - - )} - ) => + newValue && + handleCustomFilterDropdownChange( + newValue.value as ISoftwareDropdownFilterVal + ) + } + iconName="filter" />
); diff --git a/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/_styles.scss b/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/_styles.scss index d6ae84038c3c..8b51fd4d4341 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/_styles.scss +++ b/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/_styles.scss @@ -1,22 +1,6 @@ .software-table { - &__vuln_dropdown { - .Select-menu-outer { - width: 250px; - max-height: 310px; - - .Select-menu { - max-height: none; - } - } - - .Select-value { - padding-left: $pad-medium; - padding-right: $pad-medium; - } - - .dropdown__custom-value-label { - width: 155px; // Override 105px for longer text options - } + &__filter-dropdown { + min-width: 213px; } &__filter-controls { @@ -63,7 +47,7 @@ width: 100%; // Search bar across entire table .input-icon-field__input { - width: 100%; + min-width: 213px; } @media (min-width: $table-controls-break) { diff --git a/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tests.tsx b/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tests.tsx index 2e869892d182..5c115465934c 100644 --- a/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tests.tsx +++ b/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tests.tsx @@ -2,10 +2,7 @@ import React from "react"; import { screen, waitFor } from "@testing-library/react"; import { createCustomRenderer } from "test/test-utils"; -import { - createMockVulnerabilitiesResponse, - createMockVulnerability, -} from "__mocks__/vulnerabilitiesMock"; +import { createMockVulnerabilitiesResponse } from "__mocks__/vulnerabilitiesMock"; import createMockUser from "__mocks__/userMock"; import SoftwareVulnerabilitiesTable from "./SoftwareVulnerabilitiesTable"; @@ -353,7 +350,7 @@ describe("Software Vulnerabilities table", () => { expect( screen.getByText("Exploited vulnerabilities").parentElement?.parentElement ?.parentElement - ).toHaveClass("is-disabled"); + ).toHaveClass("react-select__option--is-disabled"); await waitFor(() => { waitFor(() => { diff --git a/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tsx b/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tsx index b7c49b4b47ef..4783a5e25ebb 100644 --- a/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tsx +++ b/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tsx @@ -13,13 +13,14 @@ import { } from "utilities/constants"; import { isIncompleteQuoteQuery } from "utilities/strings/stringUtils"; -// @ts-ignore -import Dropdown from "components/forms/fields/Dropdown"; import CustomLink from "components/CustomLink"; import TableContainer from "components/TableContainer"; import LastUpdatedText from "components/LastUpdatedText"; import { ITableQueryData } from "components/TableContainer/TableContainer"; import TableCount from "components/TableContainer/TableCount"; +import { SingleValue } from "react-select-5"; +import DropdownWrapper from "components/forms/fields/DropdownWrapper"; +import { CustomOptionType } from "components/forms/fields/DropdownWrapper/DropdownWrapper"; import EmptyVulnerabilitiesTable from "pages/SoftwarePage/components/EmptyVulnerabilitiesTable"; @@ -165,7 +166,7 @@ const SoftwareVulnerabilitiesTable = ({ }, [data, router, teamId]); const handleExploitedVulnFilterDropdownChange = ( - isFilterExploited: boolean + isFilterExploited: string ) => { router.replace( getNextLocationPath({ @@ -176,7 +177,7 @@ const SoftwareVulnerabilitiesTable = ({ team_id: teamId, order_direction: orderDirection, order_key: orderKey, - exploit: isFilterExploited.toString(), + exploit: isFilterExploited, page: 0, // resets page index }, }) @@ -236,12 +237,14 @@ const SoftwareVulnerabilitiesTable = ({ // Exploited vulnerabilities is a premium feature const renderExploitedVulnerabilitiesDropdown = () => { return ( - ) => + newValue && handleExploitedVulnFilterDropdownChange(newValue.value) + } iconName="filter" /> ); @@ -280,6 +283,7 @@ const SoftwareVulnerabilitiesTable = ({ customControl={ searchable ? renderExploitedVulnerabilitiesDropdown : undefined } + stackControls renderCount={renderVulnerabilityCount} renderTableHelpText={renderTableHelpText} disableMultiRowSelect diff --git a/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/_styles.scss b/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/_styles.scss index 5ab1e1a438bd..7cb91b208d01 100644 --- a/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/_styles.scss +++ b/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/_styles.scss @@ -1,4 +1,8 @@ .software-vulnerabilities-table { + &__exploited-vulnerabilities-dropdown { + min-width: 250px; + } + &__count { display: flex; gap: 12px; diff --git a/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/helpers.ts b/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/helpers.ts index 39fba03fa415..08f06bed6e0a 100644 --- a/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/helpers.ts +++ b/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/helpers.ts @@ -5,18 +5,18 @@ export const getExploitedVulnerabilitiesDropdownOptions = ( return [ { - disabled: false, + isDisabled: false, label: "All vulnerabilities", - value: false, + value: "false", helpText: "All vulnerabilities detected on your hosts.", }, { - disabled: !isPremiumTier, + isDisabled: !isPremiumTier, label: "Exploited vulnerabilities", - value: true, + value: "true", helpText: "Vulnerabilities that have been actively exploited in the wild.", - tooltipContent: !isPremiumTier && disabledTooltipContent, + tooltipContent: !isPremiumTier ? disabledTooltipContent : undefined, }, ]; }; diff --git a/frontend/pages/SoftwarePage/SoftwareVulnerabilities/_styles.scss b/frontend/pages/SoftwarePage/SoftwareVulnerabilities/_styles.scss index fecd08d5b955..0257fb327d51 100644 --- a/frontend/pages/SoftwarePage/SoftwareVulnerabilities/_styles.scss +++ b/frontend/pages/SoftwarePage/SoftwareVulnerabilities/_styles.scss @@ -1,5 +1,2 @@ .software-vulnerabilities { - .dropdown__custom-value-label { - width: 260px; // Override 105px for longer text options - } } From 009f54bdda5e99caec34120c42cba2066ed1217a Mon Sep 17 00:00:00 2001 From: Lucas Manuel Rodriguez Date: Fri, 10 Jan 2025 14:27:30 -0300 Subject: [PATCH 112/208] Changes to migrate to new TUF repository (#23588) # Changes - orbit >= 1.38.0, when configured to connect to https://tuf.fleetctl.com (existing fleetd deployments) will now connect to https://updates.fleetdm.com and start using the metadata in path `/opt/orbit/updates-metadata.json`. - orbit >= 1.38.0, when configured to connect to some custom TUF (not Fleet's TUFs) will copy `/opt/orbit/tuf-metadata.json` to `/opt/orbit/updates-metadata.json` (if it doesn't exist) and start using the latter. - fleetctl `4.63.0` will now generate artifacts using https://updates.fleetdm.com by default (or a custom TUF if `--update-url` is set) and generate two (same file) metadata files `/opt/orbit/updates-metadata.json` and the legacy one to support downgrades `/opt/orbit/tuf-metadata.json`. - fleetctl `4.62.0` when configured to use custom TUF (not Fleet's TUF) will generate just the legacy metadata file `/opt/orbit/tuf-metadata.json`. ## User stories See "User stories" in https://github.com/fleetdm/confidential/issues/8488. - [x] Update `update.defaultRootMetadata` and `update.DefaultURL` when the new repository is ready. - [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] Added/updated tests - [X] Manual QA for all new/changed functionality - For Orbit and Fleet Desktop changes: - [X] Orbit runs on macOS, Linux and Windows. Check if the orbit feature/bugfix should only apply to one platform (`runtime.GOOS`). - [X] Manual QA must be performed in the three main OSs, macOS, Windows and Linux. - [X] Auto-update manual QA, from released version of component to new version (see [tools/tuf/test](../tools/tuf/test/README.md)). --- .github/workflows/check-automated-doc.yml | 6 +- cmd/fleetctl/package.go | 3 +- cmd/fleetctl/preview.go | 2 +- orbit/changes/8488-new-tuf-repository | 1 + orbit/cmd/orbit/orbit.go | 65 ++- orbit/cmd/orbit/shell.go | 2 +- orbit/pkg/packaging/packaging.go | 13 +- orbit/pkg/update/options_darwin.go | 4 +- orbit/pkg/update/options_linux_amd64.go | 4 +- orbit/pkg/update/options_linux_arm64.go | 4 +- orbit/pkg/update/options_windows.go | 4 +- orbit/pkg/update/runner.go | 20 + orbit/pkg/update/update.go | 183 +++++-- tools/tuf/README.md | 18 +- tools/tuf/migrate/README.md | 22 + tools/tuf/migrate/migrate.go | 207 +++++++ tools/tuf/releaser.sh | 42 +- tools/tuf/test/README.md | 3 +- tools/tuf/test/main.sh | 16 +- tools/tuf/test/migration/README.md | 21 + tools/tuf/test/migration/migration_test.sh | 597 +++++++++++++++++++++ tools/tuf/test/push_target.sh | 6 +- tools/tuf/test/run_server.sh | 12 +- 23 files changed, 1149 insertions(+), 106 deletions(-) create mode 100644 orbit/changes/8488-new-tuf-repository create mode 100644 tools/tuf/migrate/README.md create mode 100644 tools/tuf/migrate/migrate.go create mode 100644 tools/tuf/test/migration/README.md create mode 100755 tools/tuf/test/migration/migration_test.sh diff --git a/.github/workflows/check-automated-doc.yml b/.github/workflows/check-automated-doc.yml index d289c55318de..a3a638bb4f2a 100644 --- a/.github/workflows/check-automated-doc.yml +++ b/.github/workflows/check-automated-doc.yml @@ -51,7 +51,8 @@ jobs: make generate-doc if [[ $(git diff) ]]; then echo "❌ fail: uncommited changes" - echo "please run `make generate-doc` and commit the changes" + echo "please run 'make generate-doc' and commit the changes" + git --no-pager diff exit 1 fi @@ -62,6 +63,7 @@ jobs: ./node_modules/sails/bin/sails.js run generate-merged-schema if [[ $(git diff) ]]; then echo "❌ fail: uncommited changes" - echo "please run `cd website && npm install && ./node_modules/sails/bin/sails.js run generate-merged-schema` and commit the changes" + echo "please run 'cd website && npm install && ./node_modules/sails/bin/sails.js run generate-merged-schema' and commit the changes" + git --no-pager diff exit 1 fi diff --git a/cmd/fleetctl/package.go b/cmd/fleetctl/package.go index 281cec25e9a5..271203390693 100644 --- a/cmd/fleetctl/package.go +++ b/cmd/fleetctl/package.go @@ -13,6 +13,7 @@ import ( eefleetctl "github.com/fleetdm/fleet/v4/ee/fleetctl" "github.com/fleetdm/fleet/v4/orbit/pkg/packaging" + "github.com/fleetdm/fleet/v4/orbit/pkg/update" "github.com/fleetdm/fleet/v4/pkg/filepath_windows" "github.com/fleetdm/fleet/v4/server/fleet" "github.com/rs/zerolog" @@ -127,7 +128,7 @@ func packageCommand() *cli.Command { &cli.StringFlag{ Name: "update-url", Usage: "URL for update server", - Value: "https://tuf.fleetctl.com", + Value: update.DefaultURL, Destination: &opt.UpdateURL, }, &cli.StringFlag{ diff --git a/cmd/fleetctl/preview.go b/cmd/fleetctl/preview.go index 4cbc5a5d6c4c..e4286d940dc0 100644 --- a/cmd/fleetctl/preview.go +++ b/cmd/fleetctl/preview.go @@ -762,7 +762,7 @@ func previewResetCommand() *cli.Command { return fmt.Errorf("Failed to stop orbit: %w", err) } - if err := os.RemoveAll(filepath.Join(orbitDir, "tuf-metadata.json")); err != nil { + if err := os.RemoveAll(filepath.Join(orbitDir, update.MetadataFileName)); err != nil { return fmt.Errorf("failed to remove preview update metadata file: %w", err) } if err := os.RemoveAll(filepath.Join(orbitDir, "bin")); err != nil { diff --git a/orbit/changes/8488-new-tuf-repository b/orbit/changes/8488-new-tuf-repository new file mode 100644 index 000000000000..6b054b047f27 --- /dev/null +++ b/orbit/changes/8488-new-tuf-repository @@ -0,0 +1 @@ +* Added changes to migrate to new TUF repository from https://tuf.fleetctl.com to https://updates.fleetdm.com. diff --git a/orbit/cmd/orbit/orbit.go b/orbit/cmd/orbit/orbit.go index 2cf8138045fd..c8a561ad2c1e 100644 --- a/orbit/cmd/orbit/orbit.go +++ b/orbit/cmd/orbit/orbit.go @@ -471,7 +471,26 @@ func main() { } } - localStore, err := filestore.New(filepath.Join(c.String("root-dir"), "tuf-metadata.json")) + if updateURL := c.String("update-url"); updateURL != update.OldFleetTUFURL && updateURL != update.DefaultURL { + // Migrate agents running with a custom TUF to use the new metadata file. + // We'll keep the old metadata file to support downgrades. + newMetadataFilePath := filepath.Join(c.String("root-dir"), update.MetadataFileName) + ok, err := file.Exists(newMetadataFilePath) + if err != nil { + // If we cannot stat this file then we cannot do other operations on it thus we fail with fatal error. + log.Fatal().Err(err).Msg("failed to check for new metadata file path") + } + if !ok { + oldMetadataFilePath := filepath.Join(c.String("root-dir"), update.OldMetadataFileName) + err := file.Copy(oldMetadataFilePath, newMetadataFilePath, constant.DefaultFileMode) + if err != nil { + // If we cannot write to this file then we cannot do other operations on it thus we fail with fatal error. + log.Fatal().Err(err).Msg("failed to copy new metadata file path") + } + } + } + + localStore, err := filestore.New(filepath.Join(c.String("root-dir"), update.MetadataFileName)) if err != nil { log.Fatal().Err(err).Msg("create local metadata store") } @@ -503,6 +522,29 @@ func main() { opt.RootDirectory = c.String("root-dir") opt.ServerURL = c.String("update-url") + checkAccessToNewTUF := false + if opt.ServerURL == update.OldFleetTUFURL { + // + // This only gets executed on orbit 1.38.0+ + // when it is configured to connect to the old TUF server + // (fleetd instances packaged before the migration, + // built by fleetctl previous to v4.63.0). + // + + if ok := update.HasAccessToNewTUFServer(opt); ok { + // orbit 1.38.0+ will use the new TUF server if it has access to the new TUF repository. + opt.ServerURL = update.DefaultURL + } else { + // orbit 1.38.0+ will use the old TUF server and old metadata path if it does not have access + // to the new TUF repository. During its execution (update.Runner) it will exit once it finds + // out it can access the new TUF server. + localStore, err = filestore.New(filepath.Join(c.String("root-dir"), update.OldMetadataFileName)) + if err != nil { + log.Fatal().Err(err).Msg("create local old metadata store") + } + checkAccessToNewTUF = true + } + } opt.LocalStore = localStore opt.InsecureTransport = c.Bool("insecure") opt.ServerCertificatePath = c.String("update-tls-certificate") @@ -545,13 +587,12 @@ func main() { var updater *update.Updater var updateRunner *update.Runner if !c.Bool("disable-updates") || c.Bool("dev-mode") { - updater, err = update.NewUpdater(opt) + updater, err := update.NewUpdater(opt) if err != nil { return fmt.Errorf("create updater: %w", err) } - if err := updater.UpdateMetadata(); err != nil { - log.Info().Err(err).Msg("update metadata, using saved metadata") + log.Info().Err(err).Msg("update metadata") } signaturesExpiredAtStartup := updater.SignaturesExpired() @@ -571,6 +612,7 @@ func main() { CheckInterval: c.Duration("update-interval"), Targets: targets, SignaturesExpiredAtStartup: signaturesExpiredAtStartup, + CheckAccessToNewTUF: checkAccessToNewTUF, }) if err != nil { return err @@ -1394,10 +1436,17 @@ func getFleetdComponentPaths( log.Error().Err(err).Msg("update metadata before getting components") } - // "root", "targets", or "snapshot" signatures have expired, thus - // we attempt to get local paths for the targets (updater.Get will fail - // because of the expired signatures). - if updater.SignaturesExpired() { + // + // updater.SignaturesExpired(): + // "root", "targets", or "snapshot" signatures have expired, thus + // we attempt to get local paths for the targets (updater.Get will fail + // because of the expired signatures). + // + // updater.LookupsFail(): + // Any of the targets fails to load thus we resort to the local executables we have. + // This could happen if the new TUF server is down during the first run of the TUF migration. + // + if updater.SignaturesExpired() || updater.LookupsFail() { log.Error().Err(err).Msg("expired metadata, using local targets") // Attempt to get local path of osqueryd. diff --git a/orbit/cmd/orbit/shell.go b/orbit/cmd/orbit/shell.go index 5acf521122ad..7a7d0900f71d 100644 --- a/orbit/cmd/orbit/shell.go +++ b/orbit/cmd/orbit/shell.go @@ -47,7 +47,7 @@ var shellCommand = &cli.Command{ return fmt.Errorf("initialize root dir: %w", err) } - localStore, err := filestore.New(filepath.Join(c.String("root-dir"), "tuf-metadata.json")) + localStore, err := filestore.New(filepath.Join(c.String("root-dir"), update.MetadataFileName)) if err != nil { log.Fatal().Err(err).Msg("failed to create local metadata store") } diff --git a/orbit/pkg/packaging/packaging.go b/orbit/pkg/packaging/packaging.go index 39ba97e4d880..279da1dc5e71 100644 --- a/orbit/pkg/packaging/packaging.go +++ b/orbit/pkg/packaging/packaging.go @@ -172,7 +172,7 @@ func (u UpdatesData) String() string { } func InitializeUpdates(updateOpt update.Options) (*UpdatesData, error) { - localStore, err := filestore.New(filepath.Join(updateOpt.RootDirectory, "tuf-metadata.json")) + localStore, err := filestore.New(filepath.Join(updateOpt.RootDirectory, update.MetadataFileName)) if err != nil { return nil, fmt.Errorf("failed to create local metadata store: %w", err) } @@ -236,6 +236,17 @@ func InitializeUpdates(updateOpt update.Options) (*UpdatesData, error) { } } + // Copy the new metadata file to the old location (pre-migration) to + // support orbit downgrades to 1.37.0 or lower. + // + // Once https://tuf.fleetctl.com is brought down (which means downgrades to 1.37.0 or + // lower won't be possible), we can remove this copy. + oldMetadataPath := filepath.Join(updateOpt.RootDirectory, update.OldMetadataFileName) + newMetadataPath := filepath.Join(updateOpt.RootDirectory, update.MetadataFileName) + if err := file.Copy(newMetadataPath, oldMetadataPath, constant.DefaultFileMode); err != nil { + return nil, fmt.Errorf("failed to create %s copy: %w", oldMetadataPath, err) + } + return &UpdatesData{ OrbitPath: orbitPath, OrbitVersion: orbitCustom.Version, diff --git a/orbit/pkg/update/options_darwin.go b/orbit/pkg/update/options_darwin.go index 54f243b64685..1d0c1365cab9 100644 --- a/orbit/pkg/update/options_darwin.go +++ b/orbit/pkg/update/options_darwin.go @@ -6,8 +6,8 @@ import ( var defaultOptions = Options{ RootDirectory: "/opt/orbit", - ServerURL: defaultURL, - RootKeys: defaultRootKeys, + ServerURL: DefaultURL, + RootKeys: defaultRootMetadata, LocalStore: client.MemoryLocalStore(), InsecureTransport: false, Targets: DarwinTargets, diff --git a/orbit/pkg/update/options_linux_amd64.go b/orbit/pkg/update/options_linux_amd64.go index 69e002e7c103..c3ab3b3089a6 100644 --- a/orbit/pkg/update/options_linux_amd64.go +++ b/orbit/pkg/update/options_linux_amd64.go @@ -6,8 +6,8 @@ import ( var defaultOptions = Options{ RootDirectory: "/opt/orbit", - ServerURL: defaultURL, - RootKeys: defaultRootKeys, + ServerURL: DefaultURL, + RootKeys: defaultRootMetadata, LocalStore: client.MemoryLocalStore(), InsecureTransport: false, Targets: LinuxTargets, diff --git a/orbit/pkg/update/options_linux_arm64.go b/orbit/pkg/update/options_linux_arm64.go index 5ed37667ddcc..aa431f2662b1 100644 --- a/orbit/pkg/update/options_linux_arm64.go +++ b/orbit/pkg/update/options_linux_arm64.go @@ -6,8 +6,8 @@ import ( var defaultOptions = Options{ RootDirectory: "/opt/orbit", - ServerURL: defaultURL, - RootKeys: defaultRootKeys, + ServerURL: DefaultURL, + RootKeys: defaultRootMetadata, LocalStore: client.MemoryLocalStore(), InsecureTransport: false, Targets: LinuxArm64Targets, diff --git a/orbit/pkg/update/options_windows.go b/orbit/pkg/update/options_windows.go index efee81416ebd..98a63b71ab67 100644 --- a/orbit/pkg/update/options_windows.go +++ b/orbit/pkg/update/options_windows.go @@ -9,8 +9,8 @@ import ( var defaultOptions = Options{ RootDirectory: `C:\Program Files\Orbit`, - ServerURL: defaultURL, - RootKeys: defaultRootKeys, + ServerURL: DefaultURL, + RootKeys: defaultRootMetadata, LocalStore: client.MemoryLocalStore(), InsecureTransport: false, Targets: WindowsTargets, diff --git a/orbit/pkg/update/runner.go b/orbit/pkg/update/runner.go index ae30f9c81cf2..498451e1d16a 100644 --- a/orbit/pkg/update/runner.go +++ b/orbit/pkg/update/runner.go @@ -39,6 +39,11 @@ type RunnerOptions struct { // An expired signature for the "timestamp" role does not cause issues // at start up (the go-tuf libary allows loading the targets). SignaturesExpiredAtStartup bool + + // CheckAccessToNewTUF, if set to true, will perform a check of access to the new Fleet TUF + // server on every update interval (once the access is confirmed it will store the confirmation + // of access to disk and will exit to restart). + CheckAccessToNewTUF bool } // Runner is a specialized runner for an Updater. It is designed with Execute and @@ -121,6 +126,14 @@ func NewRunner(updater *Updater, opt RunnerOptions) (*Runner, error) { return runner, nil } + if _, err := updater.Lookup(constant.OrbitTUFTargetName); errors.Is(err, client.ErrNoLocalSnapshot) { + // Return early and skip optimization, this will cause an unnecessary auto-update of orbit + // but allows orbit to start up if there's no local metadata AND if the TUF server is down + // (which may be the case during the migration from https://tuf.fleetctl.com to + // https://updates.fleetdm.com). + return runner, nil + } + // Initialize the hashes of the local files for all tracked targets. // // This is an optimization to not compute the hash of the local files every opt.CheckInterval @@ -204,6 +217,13 @@ func (r *Runner) Execute() error { case <-ticker.C: ticker.Reset(r.opt.CheckInterval) + if r.opt.CheckAccessToNewTUF { + if HasAccessToNewTUFServer(r.updater.opt) { + log.Info().Msg("detected access to new TUF repository, exiting") + return nil + } + } + if r.opt.SignaturesExpiredAtStartup { if r.updater.SignaturesExpired() { log.Debug().Msg("signatures still expired") diff --git a/orbit/pkg/update/update.go b/orbit/pkg/update/update.go index 51277c9d429d..79ce594f0792 100644 --- a/orbit/pkg/update/update.go +++ b/orbit/pkg/update/update.go @@ -21,7 +21,9 @@ import ( "github.com/fleetdm/fleet/v4/orbit/pkg/build" "github.com/fleetdm/fleet/v4/orbit/pkg/constant" "github.com/fleetdm/fleet/v4/orbit/pkg/platform" + "github.com/fleetdm/fleet/v4/orbit/pkg/update/filestore" "github.com/fleetdm/fleet/v4/pkg/certificate" + "github.com/fleetdm/fleet/v4/pkg/file" "github.com/fleetdm/fleet/v4/pkg/fleethttp" "github.com/fleetdm/fleet/v4/pkg/retry" "github.com/fleetdm/fleet/v4/pkg/secure" @@ -34,9 +36,32 @@ import ( const ( binDir = "bin" stagingDir = "staging" +) + +const ( + // + // For users using Fleet's TUF: + // - orbit 1.38.0+ we migrate TUF from https://tuf.fleetctl.com to https://updates.fleetdm.com. + // - orbit 1.38.0+ will start using `updates-metadata.json` instead of `tuf-metadata.json`. If it is missing + // (which will be the case for the first run after the auto-update) then it will generate it from the new pinned roots. + // + // For users using a custom TUF: + // - orbit 1.38.0+ will start using `updates-metadata.json` instead of `tuf-metadata.json` (if it is missing then + // it will perform a copy). + // + // For both Fleet's TUF and custom TUF, fleetd packages built with fleetctl 4.63.0+ will contain both files + // `updates-metadata.json` and `tuf-metadata.json` (same content) to support downgrades to orbit 1.37.0 or lower. - defaultURL = "https://tuf.fleetctl.com" - defaultRootKeys = `{"signed":{"_type":"root","spec_version":"1.0","version":4,"expires":"2024-10-06T17:47:49Z","keys":{"0cd79ade57d278957069e03a0fca6b975b95c2895fb20bdc3075f71fc19a4474":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"4627d9071a4b4a78c5ee867ea70439583b08dbe4ff23514e3bcb0a292de9406f"}},"1a4d9beb826d1ff4e036d757cfcd6e36d0f041e58d25f99ef3a20ae3f8dd71e3":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"1083b5fedbcaf8f98163f2f7083bbb2761a334b2ba8de44df7be3feb846725f6"}},"3c1fbd1f3b3429d8ccadfb1abfbae5826d0cf74b0a6bcd384c3045d2fe27613c":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"b07555d05d4260410bdf12de7f76be905e288e801071877c7ca3d7f0459bee0f"}},"5003eae9f614f7e2a6c94167d20803eabffc6f65b8731e828e56d068f1b1d834":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"5d91bdfddc381e03d109e3e2f08413ed4ba181d98766eb97802967fb6cf2b87d"}},"5b99d0520321d0177d66b84136f3fc800dde1d36a501c12e28aa12d59a239315":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"8113955a28517752982ed6521e2162cf207605cfd316b8cba637c0a9b7a72856"}},"5f42172605e1a317c6bdae3891b4312a952759185941490624e052889821c929":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"86e26b13b9a64f7de4ad24b47e2bb9779a8628cae0e1afa61e56f2003c2ab586"}},"6c0e404295d4bf8915b46754b5f4546ab0d11ff7d83804d4aa2d178cfc38eafc":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"3782135dcec329bcd0e1eefc1acead987dc6a7d629db62c9fdde8bc8ff3fa781"}},"7cbbc9772d4d6acea33b7edf5a4bc52c85ff283475d428ffee73f9dbd0f62c89":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"f79d0d357aaa534a251abc7b0604725ba7b035eb53d1bdf5cc3173d73e3d9678"}},"7ea5cd46d58ac97ec1424007b7a6b0b3403308bb8aa8de885a75841f6f1d50dd":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"978cdddce95311d56b7fed39419a31019a38a1dab179cddb541ffaf99f442f1b"}},"94ca5921eb097bb871272c1cc3ea2cad833cb8d4c2dea4a826646be656059640":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"6512498c7596f55a23405889539fadbcefecd0909e4af0b54e29f45d49f9b9f7"}},"ae943cb8be8a849b37c66ed46bdd7e905ba3118c0c051a6ee3cd30625855a076":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"e7ffa6355dedd0cd34defc903dfac05a7a8c1855d63be24cecb5577cfde1f990"}},"d940df08b59b12c30f95622a05cc40164b78a11dd7d408395ee4f79773331b30":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"64d15cc3cbaac7eccfd9e0de5a56a0789aadfec3d02e77bf9180b8090a2c48d6"}},"efb4e9bd7a7d9e045edf6f5140c9835dbcbb7770850da44bf15a800b248c810e":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"0b8b28b30b44ddb733c7457a7c0f75fcbac563208ea1fe7179b5888a4f1d2237"}}},"roles":{"root":{"keyids":["5f42172605e1a317c6bdae3891b4312a952759185941490624e052889821c929"],"threshold":1},"snapshot":{"keyids":["94ca5921eb097bb871272c1cc3ea2cad833cb8d4c2dea4a826646be656059640","1a4d9beb826d1ff4e036d757cfcd6e36d0f041e58d25f99ef3a20ae3f8dd71e3","7ea5cd46d58ac97ec1424007b7a6b0b3403308bb8aa8de885a75841f6f1d50dd","5003eae9f614f7e2a6c94167d20803eabffc6f65b8731e828e56d068f1b1d834"],"threshold":1},"targets":{"keyids":["0cd79ade57d278957069e03a0fca6b975b95c2895fb20bdc3075f71fc19a4474","ae943cb8be8a849b37c66ed46bdd7e905ba3118c0c051a6ee3cd30625855a076","6c0e404295d4bf8915b46754b5f4546ab0d11ff7d83804d4aa2d178cfc38eafc","3c1fbd1f3b3429d8ccadfb1abfbae5826d0cf74b0a6bcd384c3045d2fe27613c"],"threshold":1},"timestamp":{"keyids":["efb4e9bd7a7d9e045edf6f5140c9835dbcbb7770850da44bf15a800b248c810e","d940df08b59b12c30f95622a05cc40164b78a11dd7d408395ee4f79773331b30","7cbbc9772d4d6acea33b7edf5a4bc52c85ff283475d428ffee73f9dbd0f62c89","5b99d0520321d0177d66b84136f3fc800dde1d36a501c12e28aa12d59a239315"],"threshold":1}},"consistent_snapshot":false},"signatures":[{"keyid":"39a1db745ca254d8f8eb27493df8867264d9fb394572ecee76876f4d7e9cb753","sig":"841a44dcd98bbd78727f0b4b2a6e7dbb6d54e8469ca14965c9c5f9f7bb792dfe792f05e90a2724c75e966c007928ff7e7809de4608aab0bd27771f7b049c230f"},{"keyid":"5f42172605e1a317c6bdae3891b4312a952759185941490624e052889821c929","sig":"f6a16446edbbb632521649d21c2188b11eafeacb826caf2b8f3e2b8e9a343b573bca0a786c16ed2aeade25471c6d5103aac810ee05c50b044acd98d4b31d190c"},{"keyid":"63b4cb9241c93bca9218c67334da3651394de4cf36c44bb1320bad7111df7bba","sig":"62b5effddc00f7c9c06f4227cc1bfd4c09c47326a6c388451df28af798386d0e8d93412850bcc55f89147f439b5511bb63581ad09cd9ca215f72086348f9260b"},{"keyid":"656c44011cf8b80a4da765bec1516ee9598ffca5fa7ccb51f0a9feb04e6e6cbd","sig":"7b786c3825b206ed0c43fdfc852ebc5d363f7547a2f4940965c4c3eb89a8be069a5eddc942f8e796e645eea76b321dbbafc7f4c8d153181070da84d7a39bbe03"},{"keyid":"950097b911794bb554d7e83aa20c8aad11efcdc98f54b775fda76ac39eafa8fb","sig":"14e281d44c3384928e80a458214e4773f6c6c068a8d53e7458e8615fa5d1fe8f3daff11f736bec614cdba9e62d6f43850c6746cf2af7615445703af3ddeddb03"},{"keyid":"d6e90309d70431729bf722b089a8049efaf449230d94dc90bafa1cfc12d2b36f","sig":"bb7278ba1affc0c2bcbd952b7678ffa95268722121011df9ac18c19c1901e9c17ee3a1048a8471ca7c833ce86ecb054dc446c1ae473f118c1dc81a6e9b1dfb04"},{"keyid":"e5d1873c4d5268f650a26ea3c6ffb4bec1e523875888ebb6303fac2bfd578cd0","sig":"82019b8aba472b25f90899944db0ce94fd4ae1314f6336e2828bb30d592a9e3e34e6a66a75b1d310e0e85119a826bff345b99fe8647515057315da32e9847b04"}]}` + // + // The following variables are used by `fleetctl package` and by orbit in the migration to the new TUF repository. + // + + DefaultURL = `https://updates.fleetdm.com` + MetadataFileName = "updates-metadata.json" + defaultRootMetadata = `{"signed":{"_type":"root","spec_version":"1.0","version":6,"expires":"2026-01-08T22:23:47Z","keys":{"13f738181c9c50798d66316afaccf117658481b4db7b38715c358f522dc3bc40":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"34684874493cce2ac307c0dca88c241a287180c3eec9225c5f3e29bc4aeae080"}},"1ab0b9598e8b6ea653a24112f69b3eb3a84152c6a73d8dfdf43de4f63a93d3af":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"47a38623303bbe7b4ce47948011042b9387d7ec184642c156e06459d8fb6411b"}},"216e2dae905e73df943e003c428c7d340f21280750edb9d9ce2b15beeada667a":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"5c73ff497dc14380861892706c23bba0e3272c77c7f6f9524238b3a6b808b844"}},"2a83f45d24101ba91f669dca42981f97fc8bcde7cdf29c1887bc55c485103c49":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"e0bae1fae56d87e7f30f6359fd0a8cbfed231b19f58882e155c091c3bdb13c40"}},"3bc9408c1bcd999e69ba56b39e747656c6ebdafbd1e2c3e378c53e96e4477a64":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"8899adaa7ccd5bceb6a8c5f552fe4a9e59eb67e2a364db6638e06bbcf6f6eaeb"}},"4c0a5f49dc9665f13329d8157a2184985bd183503990469d06e32ad1bd6e70ee":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"eceeea79c6a353f5c7ed3be552a6144458ecf5fe78972eba88a77a45a230c58b"}},"57227c64d19605636d0afbab41d0887455de4287c6f328c5f69650005f793de0":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"d962cdf1d3e974f6c2b3d602260c87e0647fd54372afe7c31238f26a56a75443"}},"61e70c06858064c5e33e5009734222000610013e26fb6846ee17f55ddfb22da3":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"7e42b715cd9eedd8252a6b715fcfb8ef789531782ed19027a3c2ae11a2b0243b"}},"79a257e77793cb26d5d0cc0af6b2d2c94e7e8ca8b875dc504eb10fb343753f94":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"93409c7be4e3942ecff111d36cd097cda5778cb4f53305a07f20855b08f26071"}},"868858a9723ce357e8e251617ae11f7d3ae8a348588872cb2ce4149ee70ba155":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"f91c5b1fcb4ed3a1a65b956fa9025a89f458cea9036259b8cdfa276bc04faf45"}},"91629787db6e18b226027587733b2f667a7982eed9509c2e39dfeaf4cfb1a17a":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"3e2ac750e2e0eb22f87f35ad5309932b7b081c40891d249493fa9e2cef28066b"}},"97b3353aa23d09f88323e63cdc587a368df0a8818da67b91720b2cab00e68297":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"6b92f54f51eb617069a963a41aed75b4a23fca45e4c9ca8fc6d748d9b58b0451"}},"bc711f19576de2d71d1ca41eeccf7412f12c3ee4971185cd69066f8dc51d1ce6":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"7e39afe9a0310e7645ed389f243dc7156069d6972505cfbb460f8147949343cd"}},"c1ce9675f7302d2f09514f78ec7b3bdc00758d69b659e00c1c6731a4d0836bb9":{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"a97c44dc10ee979ead46beb3be22f3407238e72b68240a41f92e65051eb27cb1"}}},"roles":{"root":{"keyids":["bc711f19576de2d71d1ca41eeccf7412f12c3ee4971185cd69066f8dc51d1ce6","4c0a5f49dc9665f13329d8157a2184985bd183503990469d06e32ad1bd6e70ee","57227c64d19605636d0afbab41d0887455de4287c6f328c5f69650005f793de0"],"threshold":1},"snapshot":{"keyids":["1ab0b9598e8b6ea653a24112f69b3eb3a84152c6a73d8dfdf43de4f63a93d3af","868858a9723ce357e8e251617ae11f7d3ae8a348588872cb2ce4149ee70ba155","97b3353aa23d09f88323e63cdc587a368df0a8818da67b91720b2cab00e68297"],"threshold":1},"targets":{"keyids":["3bc9408c1bcd999e69ba56b39e747656c6ebdafbd1e2c3e378c53e96e4477a64","c1ce9675f7302d2f09514f78ec7b3bdc00758d69b659e00c1c6731a4d0836bb9","2a83f45d24101ba91f669dca42981f97fc8bcde7cdf29c1887bc55c485103c49"],"threshold":1},"timestamp":{"keyids":["13f738181c9c50798d66316afaccf117658481b4db7b38715c358f522dc3bc40","79a257e77793cb26d5d0cc0af6b2d2c94e7e8ca8b875dc504eb10fb343753f94","91629787db6e18b226027587733b2f667a7982eed9509c2e39dfeaf4cfb1a17a","61e70c06858064c5e33e5009734222000610013e26fb6846ee17f55ddfb22da3","216e2dae905e73df943e003c428c7d340f21280750edb9d9ce2b15beeada667a"],"threshold":1}},"consistent_snapshot":false},"signatures":[{"keyid":"bc711f19576de2d71d1ca41eeccf7412f12c3ee4971185cd69066f8dc51d1ce6","sig":"7a0d8eda3e6058bf10f21bdda4b876c499b4182335ab943737c2121603d0e2ec707222e92eace3d10051264988705d9c51e2159d13e234d57441e60ba1a3c10a"}]}` + + OldFleetTUFURL = "https://tuf.fleetctl.com" + OldMetadataFileName = "tuf-metadata.json" ) // Updater is responsible for managing update state. @@ -122,32 +147,10 @@ func NewUpdater(opt Options) (*Updater, error) { return nil, errors.New("opt.LocalStore must be non-nil") } - tlsConfig := &tls.Config{ - InsecureSkipVerify: opt.InsecureTransport, - } - - if opt.ServerCertificatePath != "" { - rootCAs, err := certificate.LoadPEM(opt.ServerCertificatePath) - if err != nil { - return nil, fmt.Errorf("loading server root CA: %w", err) - } - tlsConfig.RootCAs = rootCAs - } - - if opt.ClientCertificate != nil { - tlsConfig.Certificates = []tls.Certificate{*opt.ClientCertificate} - } - - httpClient := fleethttp.NewClient(fleethttp.WithTLSClientConfig(tlsConfig)) - - remoteOpt := &client.HTTPRemoteOptions{ - UserAgent: fmt.Sprintf("orbit/%s (%s %s)", build.Version, runtime.GOOS, runtime.GOARCH), - } - remoteStore, err := client.HTTPRemoteStore(opt.ServerURL, remoteOpt, httpClient) + remoteStore, err := createTUFRemoteStore(opt, opt.ServerURL) if err != nil { - return nil, fmt.Errorf("init remote store: %w", err) + return nil, fmt.Errorf("get tls config: %w", err) } - tufClient := client.NewClient(opt.LocalStore, remoteStore) // TODO(lucas): Related to the NOTE below. @@ -164,10 +167,13 @@ func NewUpdater(opt Options) (*Updater, error) { return nil, fmt.Errorf("read metadata: %w", err) } if meta["root.json"] == nil { - // NOTE: This path is currently only used when (1) packaging Orbit (`fleetctl package`) and - // (2) in the edge-case when Orbit's metadata JSON local file is removed for some reason. - // When edge-case (2) happens, Orbit will attempt to use Fleet DM's root JSON + // NOTE: This path is currently only used when (1) packaging Orbit (`fleetctl package`), or + // (2) in the edge-case when orbit's metadata JSON local file is removed for some reason, or + // (3) first run on TUF migration from https://tuf.fleetctl.com to https://updates.fleetdm.com. + // + // When edge-case (2) happens, orbit will attempt to use Fleet DM's root JSON // (which may be unexpected on custom TUF Orbit deployments). + log.Info().Msg("initialize TUF from embedded root keys") if err := tufClient.Init([]byte(opt.RootKeys)); err != nil { return nil, fmt.Errorf("client init with configuration metadata: %w", err) } @@ -203,7 +209,7 @@ func NewDisabled(opt Options) *Updater { // UpdateMetadata downloads and verifies remote repository metadata. func (u *Updater) UpdateMetadata() error { if _, err := u.client.Update(); err != nil { - return fmt.Errorf("update metadata: %w", err) + return fmt.Errorf("client update: %w", err) } return nil } @@ -225,6 +231,16 @@ func (u *Updater) SignaturesExpired() bool { return IsExpiredErr(err) } +// LookupsFail returns true if lookups are failing for any of the targets. +func (u *Updater) LookupsFail() bool { + for target := range u.opt.Targets { + if _, err := u.Lookup(target); err != nil { + return true + } + } + return false +} + // repoPath returns the path of the target in the remote repository. func (u *Updater) repoPath(target string) (string, error) { u.mu.Lock() @@ -736,3 +752,110 @@ func CanRun(rootDirPath, targetName string, targetInfo TargetInfo) bool { return true } + +// HasAccessToNewTUFServer will verify if the agent has access to Fleet's new TUF +// by downloading the metadata and the orbit stable target. +// +// The metadata and the test target files will be downloaded to a temporary directory +// that will be removed before this method returns. +func HasAccessToNewTUFServer(opt Options) bool { + fp := filepath.Join(opt.RootDirectory, "new-tuf-checked") + ok, err := file.Exists(fp) + if err != nil { + log.Error().Err(err).Msg("failed to check new-tuf-checked file exists") + return false + } + if ok { + return true + } + tmpDir, err := os.MkdirTemp(opt.RootDirectory, "tuf-tmp*") + if err != nil { + log.Error().Err(err).Msg("failed to create tuf-tmp directory") + return false + } + defer os.RemoveAll(tmpDir) + localStore, err := filestore.New(filepath.Join(tmpDir, "tuf-tmp.json")) + if err != nil { + log.Error().Err(err).Msg("failed to create tuf-tmp local store") + return false + } + remoteStore, err := createTUFRemoteStore(opt, DefaultURL) + if err != nil { + log.Error().Err(err).Msg("failed to create TUF remote store") + return false + } + tufClient := client.NewClient(localStore, remoteStore) + if err := tufClient.Init([]byte(opt.RootKeys)); err != nil { + log.Error().Err(err).Msg("failed to pin root keys") + return false + } + if _, err := tufClient.Update(); err != nil { + // Logging as debug to not fill logs until users allow connections to new TUF server. + log.Debug().Err(err).Msg("failed to update metadata from new TUF") + return false + } + tmpFile, err := secure.OpenFile( + filepath.Join(tmpDir, "orbit"), + os.O_CREATE|os.O_WRONLY, + constant.DefaultFileMode, + ) + if err != nil { + log.Error().Err(err).Msg("failed open temp file for download") + return false + } + defer tmpFile.Close() + // We are using the orbit stable target as the test target. + var ( + platform string + executable string + ) + switch runtime.GOOS { + case "darwin": + platform = "macos" + executable = "orbit" + case "windows": + platform = "windows" + executable = "orbit.exe" + case "linux": + platform = "linux" + executable = "orbit" + } + if err := tufClient.Download(fmt.Sprintf("orbit/%s/stable/%s", platform, executable), &fileDestination{tmpFile}); err != nil { + // Logging as debug to not fill logs until users allow connections to new TUF server. + log.Debug().Err(err).Msg("failed to download orbit from TUF") + return false + } + + if err := os.WriteFile(fp, []byte("new-tuf-checked"), constant.DefaultFileMode); err != nil { + // We log the error and return success below anyway because the access check was successful. + log.Error().Err(err).Msg("failed to write new-tuf-checked file") + } + // We assume access to the whole repository + // if the orbit macOS stable target is downloaded successfully. + return true +} + +func createTUFRemoteStore(opt Options, serverURL string) (client.RemoteStore, error) { + tlsConfig := &tls.Config{ + InsecureSkipVerify: opt.InsecureTransport, + } + if opt.ServerCertificatePath != "" { + rootCAs, err := certificate.LoadPEM(opt.ServerCertificatePath) + if err != nil { + return nil, fmt.Errorf("loading server root CA: %w", err) + } + tlsConfig.RootCAs = rootCAs + } + if opt.ClientCertificate != nil { + tlsConfig.Certificates = []tls.Certificate{*opt.ClientCertificate} + } + remoteOpt := &client.HTTPRemoteOptions{ + UserAgent: fmt.Sprintf("orbit/%s (%s %s)", build.Version, runtime.GOOS, runtime.GOARCH), + } + httpClient := fleethttp.NewClient(fleethttp.WithTLSClientConfig(tlsConfig)) + remoteStore, err := client.HTTPRemoteStore(serverURL, remoteOpt, httpClient) + if err != nil { + return nil, fmt.Errorf("init remote store: %w", err) + } + return remoteStore, nil +} diff --git a/tools/tuf/README.md b/tools/tuf/README.md index 3915fdb89278..f89140131362 100644 --- a/tools/tuf/README.md +++ b/tools/tuf/README.md @@ -69,18 +69,16 @@ AWS_PROFILE=tuf aws sso login > You can skip this step if you already have authorized keys to sign and publish updates. To release updates to our TUF repository you need the `root` role (ask in Slack who has such `root` role) to sign your signing keys. -First, run the following script + +1. First, run the following script ```sh -AWS_PROFILE=tuf \ -ACTION=generate-signing-keys \ -TUF_DIRECTORY=/Users/foobar/tuf3.fleetctl.com \ -TARGETS_PASSPHRASE_1PASSWORD_PATH="Private/TUF TARGETS/password" \ -SNAPSHOT_PASSPHRASE_1PASSWORD_PATH="Private/TUF SNAPSHOT/password" \ -TIMESTAMP_PASSPHRASE_1PASSWORD_PATH="Private/TUF TIMESTAMP/password" \ -./tools/tuf/releaser.sh +tuf gen-key targets && echo +tuf gen-key snapshot && echo +tuf gen-key timestamp && echo ``` - -The human with the `root` role will run the following commands to sign the provided `staged/root.json`: +2. Store the '$TUF_DIRECTORY/keys' folder (that contains the encrypted keys) on a USB flash drive that you will ONLY use for releasing fleetd updates. +3. Share '$TUF_DIRECTORY/staged/root.json' with Fleet member with the 'root' role, who will sign with its root key and push it to the remote repository. +4. The human with the `root` role will run the following commands to sign the provided `staged/root.json`: ```sh tuf sign tuf snapshot diff --git a/tools/tuf/migrate/README.md b/tools/tuf/migrate/README.md new file mode 100644 index 000000000000..c3e2e9aba1a4 --- /dev/null +++ b/tools/tuf/migrate/README.md @@ -0,0 +1,22 @@ +# migrate + +This tool will be used to migrate all current targets (except unused ones) from https://tuf.fleetctl.com to https://updates.fleetdm.com. + +Usage: +```sh +# The tool requires the 'targets', 'snapshot' and 'timestamp' roles of the new repository. +export FLEET_TARGETS_PASSPHRASE=p4ssphr4s3 +export FLEET_SNAPSHOT_PASSPHRASE=p4ssphr4s3 +export FLEET_TIMESTAMP_PASSPHRASE=p4ssphr4s3 + +# +# It assumes the following: +# - https://tuf.fleetctl.com was fully fetched into -source-repository-directory. +# - https://updates.fleetdm.com was fully fetched into -dest-repository-directory. +# +# Migration may take several minutes due to sha512 verification after targets are +# added to the new repository. +go run ./tools/tuf/migrate/migrate.go \ + -source-repository-directory ./source-tuf-directory \ + -dest-repository-directory ./dest-tuf-directory +``` diff --git a/tools/tuf/migrate/migrate.go b/tools/tuf/migrate/migrate.go new file mode 100644 index 000000000000..d891d505b9ce --- /dev/null +++ b/tools/tuf/migrate/migrate.go @@ -0,0 +1,207 @@ +// Package main contains an executable that migrates all targets from one source TUF repository +// to a destination TUF repository. It migrates all targets except a few known unused targets. +package main + +import ( + "crypto/sha512" + "encoding/hex" + "encoding/json" + "errors" + "flag" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" +) + +func main() { + if runtime.GOOS == "windows" { + log.Fatalf("%s is not supported on windows", os.Args[0]) + } + + sourceRepositoryDirectory := flag.String("source-repository-directory", "", "Absolute path directory for the source TUF") + destRepositoryDirectory := flag.String("dest-repository-directory", "", "Absolute path directory for the destination TUF") + + flag.Parse() + + if *sourceRepositoryDirectory == "" { + log.Fatal("missing --source-repository-directory") + } + if *destRepositoryDirectory == "" { + log.Fatal("missing --dest-repository-directory") + } + + type targetEntry struct { + sha512 string + length int + } + + // Perform addition of targets by iterating source repository. + sourceEntries := make(map[string]targetEntry) + iterateRepository(*sourceRepositoryDirectory, func(target, targetPath, platform, targetName, version, channel, hashSHA512 string, length int) error { + cmd := exec.Command("fleetctl", "updates", "add", //nolint:gosec + "--path", *destRepositoryDirectory, + "--target", targetPath, + "--platform", platform, + "--name", targetName, + "--version", version, + "-t", channel, + ) + log.Print(cmd.String()) + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + + if err := cmd.Run(); err != nil { + log.Fatalf("target: %q: failed to add target: %s", target, err) + } + + sourceEntries[target] = targetEntry{ + sha512: hashSHA512, + length: length, + } + + return nil + }) + + // Perform validation of destination repository. + iterateRepository(*destRepositoryDirectory, func(target, targetPath, platform, targetName, version, channel, hashSHA512 string, length int) error { + sourceEntry, ok := sourceEntries[target] + if !ok { + return errors.New("entry not found in source directory") + } + + // It seems this very old version has invalid length and sha256. + // Validation fails with: + // 2025/01/07 18:11:40 target: "desktop/macos/1.11.0/desktop.app.tar.gz": failed to process target: mismatch length: 10518528 vs 30373384 + if target == "desktop/macos/1.11.0/desktop.app.tar.gz" { + log.Printf("Skipping %s (old version) due to invalid length and sha256", target) + return nil + } + + if sourceEntry.length != length { + return fmt.Errorf("mismatch length: %d vs %d", length, sourceEntry.length) + } + if sourceEntry.sha512 != hashSHA512 { + return fmt.Errorf("mismatch sha512: %s vs %s", hashSHA512, sourceEntry.sha512) + } + + targetBytes, err := os.ReadFile(targetPath) + if err != nil { + return fmt.Errorf("failed to read file: %w", err) + } + h := sha512.New() + if _, err := h.Write(targetBytes); err != nil { + return fmt.Errorf("failed to hash file: %w", err) + } + fileHash := hex.EncodeToString(h.Sum(nil)) + if fileHash != sourceEntry.sha512 { + return fmt.Errorf("mismatch sha512 and file contents: %s vs %s", fileHash, sourceEntry.sha512) + } + + return nil + }) +} + +func iterateRepository(repositoryDirectory string, fn func(target, targetPath, platform, targetName, version, channel, sha512 string, length int) error) { + repositoryPath := filepath.Join(repositoryDirectory, "repository") + targetsFile := filepath.Join(repositoryPath, "targets.json") + targetsBytes, err := os.ReadFile(targetsFile) + if err != nil { + log.Fatal("failed to read the source targets.json file") + } + + var targetsJSON map[string]interface{} + if err := json.Unmarshal(targetsBytes, &targetsJSON); err != nil { + log.Fatal("failed to parse the source targets.json file") + } + + signed_ := targetsJSON["signed"] + if signed_ == nil { + log.Fatal("missing signed key in targets.json file") + } + signed, ok := signed_.(map[string]interface{}) + if !ok { + log.Fatalf("invalid signed key in targets.json file: %T, expected map", signed_) + } + targets_ := signed["targets"] + if targets_ == nil { + log.Fatal("missing signed.targets key in targets.json file") + } + targets, ok := targets_.(map[string]interface{}) + if !ok { + log.Fatalf("invalid signed.targets key in targets.json file: %T, expected map", targets_) + } + + for target, metadata_ := range targets { + targetPath := filepath.Join(repositoryPath, "targets", target) + + parts := strings.Split(target, "/") + if len(parts) != 4 { + log.Fatalf("target %q: invalid number of parts, expected 4", target) + } + + targetName := parts[0] + platform := parts[1] + channel := parts[2] + executable := parts[3] + + // Unused targets (probably accidentally pushed). + if targetName == "desktop.tar.gz" || // correct target name is just "desktop". + (targetName == "desktop" && executable == "desktop") { // correct executable for Linux is "desktop.tar.gz". + continue + } + + metadata, ok := metadata_.(map[string]interface{}) + if !ok { + log.Fatalf("target: %q: invalid metadata field: %T, expected map", target, metadata_) + } + custom_ := metadata["custom"] + if custom_ == nil { + log.Fatalf("target: %q: missing custom field", target) + } + custom, ok := custom_.(map[string]interface{}) + if !ok { + log.Fatalf("target: %q: invalid custom field: %T, expected map", target, custom_) + } + version_ := custom["version"] + if version_ == nil { + log.Fatalf("target: %q: missing custom.version field", target) + } + version, ok := version_.(string) + if !ok { + log.Fatalf("target: %q: invalid custom.version field: %T", target, version_) + } + length_ := metadata["length"] + if length_ == nil { + log.Fatalf("target: %q: missing length field", target) + } + lengthf, ok := length_.(float64) + if !ok { + log.Fatalf("target: %q: invalid length field: %T", target, length_) + } + length := int(lengthf) + hashes_ := metadata["hashes"] + if hashes_ == nil { + log.Fatalf("target: %q: missing hashes field", target) + } + hashes, ok := hashes_.(map[string]interface{}) + if !ok { + log.Fatalf("target: %q: invalid hashes field: %T", target, hashes_) + } + sha512_ := hashes["sha512"] + if sha512_ == nil { + log.Fatalf("target: %q: missing hashes.sha512 field", target) + } + hashSHA512, ok := sha512_.(string) + if !ok { + log.Fatalf("target: %q: invalid hashes.sha512 field: %T", target, sha512_) + } + + if err := fn(target, targetPath, platform, targetName, version, channel, hashSHA512, length); err != nil { + log.Fatalf("target: %q: failed to process target: %s", target, err) + } + } +} diff --git a/tools/tuf/releaser.sh b/tools/tuf/releaser.sh index 746c7f7d9d6c..f56e4fed75e6 100755 --- a/tools/tuf/releaser.sh +++ b/tools/tuf/releaser.sh @@ -277,41 +277,6 @@ prompt () { done } -setup_to_become_publisher () { - echo "Running setup to become publisher..." - - REPOSITORY_DIRECTORY=$TUF_DIRECTORY/repository - STAGED_DIRECTORY=$TUF_DIRECTORY/staged - KEYS_DIRECTORY=$TUF_DIRECTORY/keys - mkdir -p "$REPOSITORY_DIRECTORY" - mkdir -p "$STAGED_DIRECTORY" - mkdir -p "$KEYS_DIRECTORY" - if ! aws sts get-caller-identity &> /dev/null; then - aws sso login - prompt "AWS SSO login was successful." - fi - # These need to be exported for use by `tuf` commands. - FLEET_TARGETS_PASSPHRASE=$(op read "op://$TARGETS_PASSPHRASE_1PASSWORD_PATH") - export TUF_TARGETS_PASSPHRASE=$FLEET_TARGETS_PASSPHRASE - FLEET_SNAPSHOT_PASSPHRASE=$(op read "op://$SNAPSHOT_PASSPHRASE_1PASSWORD_PATH") - export TUF_SNAPSHOT_PASSPHRASE=$FLEET_SNAPSHOT_PASSPHRASE - FLEET_TIMESTAMP_PASSPHRASE=$(op read "op://$TIMESTAMP_PASSPHRASE_1PASSWORD_PATH") - export TUF_TIMESTAMP_PASSPHRASE=$FLEET_TIMESTAMP_PASSPHRASE -} - -if [[ $ACTION == "generate-signing-keys" ]]; then - setup_to_become_publisher - pull_from_remote - cd "$TUF_DIRECTORY" - tuf gen-key targets && echo - tuf gen-key snapshot && echo - tuf gen-key timestamp && echo - echo "Keys have been generated, now do the following actions:" - echo "- Share '$TUF_DIRECTORY/staged/root.json' with Fleet member with the 'root' role, who will sign with its root key and push it to the remote repository." - echo "- Store the '$TUF_DIRECTORY/keys' folder (that contains the encrypted keys) on a USB flash drive that you will ONLY use for releasing fleetd updates." - exit 0 -fi - print_reminder () { if [[ $ACTION == "release-to-edge" ]]; then if [[ $COMPONENT == "fleetd" ]]; then @@ -333,8 +298,15 @@ print_reminder () { fi } +fleetctl_version_check () { + which fleetctl + fleetctl --version + prompt "Make sure the fleetctl executable and version are correct." +} + trap clean_up EXIT print_reminder +fleetctl_version_check setup pull_from_remote diff --git a/tools/tuf/test/README.md b/tools/tuf/test/README.md index e22b5642e2a3..d7b207035930 100644 --- a/tools/tuf/test/README.md +++ b/tools/tuf/test/README.md @@ -61,7 +61,8 @@ LINUX_TEST_EXTENSIONS="./tools/test_extensions/hello_world/linux/hello_world_lin To build for a specific architecture, you can pass the `GOARCH` environment variable: ``` shell [...] -GOARCH=arm64 # defaults to amd64 +# defaults to amd64 +GOARCH=arm64 \ [...] ./tools/tuf/test/main.sh ``` diff --git a/tools/tuf/test/main.sh b/tools/tuf/test/main.sh index 65c179e5fe16..88ff63a025ad 100755 --- a/tools/tuf/test/main.sh +++ b/tools/tuf/test/main.sh @@ -6,10 +6,19 @@ export FLEET_ROOT_PASSPHRASE=p4ssphr4s3 export FLEET_TARGETS_PASSPHRASE=p4ssphr4s3 export FLEET_SNAPSHOT_PASSPHRASE=p4ssphr4s3 export FLEET_TIMESTAMP_PASSPHRASE=p4ssphr4s3 -export TUF_PATH=test_tuf export NUDGE=1 -if ( [ -n "$GENERATE_PKG" ] || [ -n "$GENERATE_DEB" ] || [ -n "$GENERATE_RPM" ] || [ -n "$GENERATE_MSI" ] ) && [ -z "$ENROLL_SECRET" ]; then +if [ -z "$TUF_PATH" ]; then + TUF_PATH=test_tuf +fi +export TUF_PATH + +if [ -z "$TUF_PORT" ]; then + TUF_PORT=8081 +fi +export TUF_PORT + +if { [ -n "$GENERATE_PKG" ] || [ -n "$GENERATE_DEB" ] || [ -n "$GENERATE_RPM" ] || [ -n "$GENERATE_MSI" ] ; } && [ -z "$ENROLL_SECRET" ]; then echo "Error: To generate packages you must set ENROLL_SECRET variable." exit 1 fi @@ -30,7 +39,8 @@ fi make fleetctl ./tools/tuf/test/create_repository.sh -export ROOT_KEYS=$(./build/fleetctl updates roots --path $TUF_PATH) +ROOT_KEYS=$(./build/fleetctl updates roots --path "$TUF_PATH") +export ROOT_KEYS echo "#########" echo "To generate packages set the following options in 'fleetctl package':" diff --git a/tools/tuf/test/migration/README.md b/tools/tuf/test/migration/README.md new file mode 100644 index 000000000000..3e92c8ace83c --- /dev/null +++ b/tools/tuf/test/migration/README.md @@ -0,0 +1,21 @@ +# `migration_test.sh` + +This script is used to test the migration from one local TUF repository to a new local TUF repository (with new roots). + +> Currently supports running on macOS only. + +The script is interactive and assumes the user will use a Windows and Ubuntu VM to install fleetd and test the changes on those platforms too. + +Usage: +```sh +FLEET_URL=https://host.docker.internal:8080 \ +NO_TEAM_ENROLL_SECRET=... \ +WINDOWS_HOST_HOSTNAME=DESKTOP-USFLJ3H \ +LINUX_HOST_HOSTNAME=foobar-ubuntu \ +./tools/tuf/test/migration/migration_test.sh +``` + +To simulate an outage of the TUF during the migration run the above with: +```sh +SIMULATE_NEW_TUF_OUTAGE=1 \ +``` diff --git a/tools/tuf/test/migration/migration_test.sh b/tools/tuf/test/migration/migration_test.sh new file mode 100755 index 000000000000..8b21454cac81 --- /dev/null +++ b/tools/tuf/test/migration/migration_test.sh @@ -0,0 +1,597 @@ +#!/bin/bash + +# Script used to test the migration from a TUF repository to a new one. +# It assumes the following: +# - User runs the script on macOS +# - User has a Ubuntu 22.04 and a Windows 10/11 VM (running on the same macOS host script runs on). +# - Fleet is running on the macOS host. +# - `fleetctl login` was ran on the localhost Fleet instance (to be able to run `fleectl query` commands). +# - host.docker.internal points to localhost on the macOS host. +# - host.docker.internal points to the macOS host on the two VMs (/etc/hosts on Ubuntu and C:\Windows\System32\Drivers\etc\hosts on Windows). +# - 1.37.0 is the last version of orbit that uses the old TUF repository +# - 1.38.0 is the new version of orbit that will use the new TUF repository. +# - Old TUF repository directory is ./test_tuf_old and server listens on 8081 (runs on the macOS host). +# - New TUF repository directory is ./test_tuf_new and server listens on 8082 (runs on the macOS host). + +set -e + +if [ -z "$FLEET_URL" ]; then + echo "Missing FLEET_URL" + exit 1 +fi +if [ -z "$NO_TEAM_ENROLL_SECRET" ]; then + echo "Missing NO_TEAM_ENROLL_SECRET" + exit 1 +fi + +if [ -z "$WINDOWS_HOST_HOSTNAME" ]; then + echo "Missing WINDOWS_HOST_HOSTNAME" + exit 1 +fi + +if [ -z "$LINUX_HOST_HOSTNAME" ]; then + echo "Missing LINUX_HOST_HOSTNAME" + exit 1 +fi + +prompt () { + printf "%s\n" "$1" + printf "Type 'yes' to continue... " + while read -r word; + do + if [[ "$word" == "yes" ]]; then + printf "\n" + return + fi + done +} + +echo "Uinstalling fleetd from macOS..." +sudo orbit/tools/cleanup/cleanup_macos.sh +prompt "Please manually uninstall fleetd from $WINDOWS_HOST_HOSTNAME and $LINUX_HOST_HOSTNAME." + +OLD_TUF_PORT=8081 +OLD_TUF_URL=http://host.docker.internal:$OLD_TUF_PORT +OLD_TUF_PATH=test_tuf_old +OLD_FULL_VERSION=1.37.0 +OLD_MINOR_VERSION=1.37 + +NEW_TUF_PORT=8082 +NEW_TUF_URL=http://host.docker.internal:$NEW_TUF_PORT +NEW_TUF_PATH=test_tuf_new +NEW_FULL_VERSION=1.38.0 +NEW_MINOR_VERSION=1.38 +NEW_PATCH_VERSION=1.38.1 + +echo "Cleaning up existing directories and file servers..." +rm -rf "$OLD_TUF_PATH" +rm -rf "$NEW_TUF_PATH" +pkill file-server || true + +echo "Restoring update_channels for \"No team\" to 'stable' defaults..." +cat << EOF > upgrade.yml +--- +apiVersion: v1 +kind: config +spec: + agent_options: + config: + options: + pack_delimiter: / + distributed_plugin: tls + disable_distributed: false + logger_tls_endpoint: /api/v1/osquery/log + distributed_interval: 10 + distributed_tls_max_attempts: 3 + distributed_denylist_duration: 10 + decorators: + load: + - SELECT uuid AS host_uuid FROM system_info; + - SELECT hostname AS hostname FROM system_info; + update_channels: + orbit: stable + desktop: stable + osqueryd: stable +EOF +fleetctl apply -f upgrade.yml + +echo "Generating a TUF repository on $OLD_TUF_PATH (aka \"old\")..." +SYSTEMS="macos linux windows" \ +TUF_PATH=$OLD_TUF_PATH \ +TUF_PORT=$OLD_TUF_PORT \ +FLEET_DESKTOP=1 \ +./tools/tuf/test/main.sh + +export FLEET_ROOT_PASSPHRASE=p4ssphr4s3 +export FLEET_TARGETS_PASSPHRASE=p4ssphr4s3 +export FLEET_SNAPSHOT_PASSPHRASE=p4ssphr4s3 +export FLEET_TIMESTAMP_PASSPHRASE=p4ssphr4s3 + +echo "Downloading and pushing latest released orbit from https://tuf.fleetctl.com to the old repository..." +curl https://tuf.fleetctl.com/targets/orbit/macos/$OLD_FULL_VERSION/orbit --output orbit-darwin +./build/fleetctl updates add --path $OLD_TUF_PATH --target ./orbit-darwin --platform macos --name orbit --version $OLD_FULL_VERSION -t $OLD_MINOR_VERSION -t 1 -t stable +curl https://tuf.fleetctl.com/targets/orbit/linux/$OLD_FULL_VERSION/orbit --output orbit-linux +./build/fleetctl updates add --path $OLD_TUF_PATH --target ./orbit-linux --platform linux --name orbit --version $OLD_FULL_VERSION -t $OLD_MINOR_VERSION -t 1 -t stable +curl https://tuf.fleetctl.com/targets/orbit/windows/$OLD_FULL_VERSION/orbit.exe --output orbit.exe +./build/fleetctl updates add --path $OLD_TUF_PATH --target ./orbit.exe --platform windows --name orbit --version $OLD_FULL_VERSION -t $OLD_MINOR_VERSION -t 1 -t stable + +echo "Building fleetd packages using old repository and old fleetctl version..." +curl -L https://github.com/fleetdm/fleet/releases/download/fleet-v4.60.0/fleetctl_v4.60.0_macos.tar.gz --output ./build/fleetctl_v4.60.0_macos.tar.gz +cd ./build +tar zxf fleetctl_v4.60.0_macos.tar.gz +cp fleetctl_v4.60.0_macos/fleetctl fleetctl-v4.60.0 +cd .. +chmod +x ./build/fleetctl-v4.60.0 +ROOT_KEYS1=$(./build/fleetctl-v4.60.0 updates roots --path $OLD_TUF_PATH) +declare -a pkgTypes=("pkg" "deb" "msi") +for pkgType in "${pkgTypes[@]}"; do + ./build/fleetctl-v4.60.0 package --type="$pkgType" \ + --enable-scripts \ + --fleet-desktop \ + --fleet-url="$FLEET_URL" \ + --enroll-secret="$NO_TEAM_ENROLL_SECRET" \ + --fleet-certificate=./tools/osquery/fleet.crt \ + --debug \ + --update-roots="$ROOT_KEYS1" \ + --update-url=$OLD_TUF_URL \ + --disable-open-folder \ + --disable-keystore \ + --update-interval=30s +done + +# Install fleetd generated with old fleetctl and using old TUF on devices. +echo "Installing fleetd package on macOS..." +sudo installer -pkg fleet-osquery.pkg -verbose -target / +CURRENT_DIR=$(pwd) +prompt "Please install $CURRENT_DIR/fleet-osquery.msi and $CURRENT_DIR/fleet-osquery_${OLD_FULL_VERSION}_amd64.deb." + +echo "Generating a new TUF repository from scratch on $NEW_TUF_PATH..." +./build/fleetctl updates init --path $NEW_TUF_PATH + +echo "Migrating all targets from old to new repository..." +go run ./tools/tuf/migrate/migrate.go \ + -source-repository-directory "$OLD_TUF_PATH" \ + -dest-repository-directory "$NEW_TUF_PATH" + +echo "Serving new TUF repository..." +TUF_PORT=$NEW_TUF_PORT TUF_PATH=$NEW_TUF_PATH ./tools/tuf/test/run_server.sh + +echo "Building the new orbit that will perform the migration..." +ROOT_KEYS2=$(./build/fleetctl updates roots --path $NEW_TUF_PATH) +CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build \ + -o orbit-darwin \ + -ldflags="-X github.com/fleetdm/fleet/v4/orbit/pkg/build.Version=$NEW_FULL_VERSION \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.DefaultURL=$NEW_TUF_URL \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.defaultRootMetadata=$ROOT_KEYS2 \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.OldFleetTUFURL=$OLD_TUF_URL" \ + ./orbit/cmd/orbit +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ + -o orbit-linux \ + -ldflags="-X github.com/fleetdm/fleet/v4/orbit/pkg/build.Version=$NEW_FULL_VERSION \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.DefaultURL=$NEW_TUF_URL \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.defaultRootMetadata=$ROOT_KEYS2 \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.OldFleetTUFURL=$OLD_TUF_URL" \ + ./orbit/cmd/orbit +CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build \ + -o orbit.exe \ + -ldflags="-X github.com/fleetdm/fleet/v4/orbit/pkg/build.Version=$NEW_FULL_VERSION \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.DefaultURL=$NEW_TUF_URL \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.defaultRootMetadata=$ROOT_KEYS2 \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.OldFleetTUFURL=$OLD_TUF_URL" \ + ./orbit/cmd/orbit + +echo "Pushing new orbit to new repository on stable channel..." +./build/fleetctl updates add --path $NEW_TUF_PATH --target ./orbit-darwin --platform macos --name orbit --version $NEW_FULL_VERSION -t $NEW_MINOR_VERSION -t 1 -t stable +./build/fleetctl updates add --path $NEW_TUF_PATH --target ./orbit-linux --platform linux --name orbit --version $NEW_FULL_VERSION -t $NEW_MINOR_VERSION -t 1 -t stable +./build/fleetctl updates add --path $NEW_TUF_PATH --target ./orbit.exe --platform windows --name orbit --version $NEW_FULL_VERSION -t $NEW_MINOR_VERSION -t 1 -t stable + +if [ "$SIMULATE_NEW_TUF_OUTAGE" = "1" ]; then + echo "Simulating outage of the new TUF repository by killing the new TUF server..." + # We kill the two servers and bring back the old one. + pkill file-server || true + TUF_PORT=$OLD_TUF_PORT TUF_PATH=$OLD_TUF_PATH ./tools/tuf/test/run_server.sh +fi + +echo "Pushing new orbit to old repository!..." +./build/fleetctl updates add --path $OLD_TUF_PATH --target ./orbit-darwin --platform macos --name orbit --version $NEW_FULL_VERSION -t $NEW_MINOR_VERSION -t 1 -t stable +./build/fleetctl updates add --path $OLD_TUF_PATH --target ./orbit-linux --platform linux --name orbit --version $NEW_FULL_VERSION -t $NEW_MINOR_VERSION -t 1 -t stable +./build/fleetctl updates add --path $OLD_TUF_PATH --target ./orbit.exe --platform windows --name orbit --version $NEW_FULL_VERSION -t $NEW_MINOR_VERSION -t 1 -t stable + +if [ "$SIMULATE_NEW_TUF_OUTAGE" = "1" ]; then + echo "Checking version of updated orbit (to check device is responding even if TUF server is down)..." + THIS_HOSTNAME=$(hostname) + declare -a hostnames=("$THIS_HOSTNAME" "$WINDOWS_HOST_HOSTNAME" "$LINUX_HOST_HOSTNAME") + for host_hostname in "${hostnames[@]}"; do + ORBIT_VERSION="" + until [ "$ORBIT_VERSION" = "\"$NEW_FULL_VERSION\"" ]; do + sleep 1 + ORBIT_VERSION=$(fleetctl query --hosts "$host_hostname" --exit --query 'SELECT * FROM orbit_info;' 2>/dev/null | jq '.rows[0].version') + done + done + + prompt "Please check for errors in orbit logs that new TUF server is unavailable (network errors). Errors should be shown every 10s." + + echo "Bring new TUF server back but still unavailable (404s errors)." + mkdir -p $NEW_TUF_PATH/tmp + mv $NEW_TUF_PATH/repository/targets/* $NEW_TUF_PATH/tmp/ + + TUF_PORT=$NEW_TUF_PORT TUF_PATH=$NEW_TUF_PATH ./tools/tuf/test/run_server.sh + + prompt "Please check for errors in orbit logs that new TUF server is still unavailable (404s errors). Errors should be shown every 10s." + + echo "Checking version of orbit (to check device is responding even if TUF server is down)..." + for host_hostname in "${hostnames[@]}"; do + ORBIT_VERSION="" + until [ "$ORBIT_VERSION" = "\"$NEW_FULL_VERSION\"" ]; do + sleep 1 + ORBIT_VERSION=$(fleetctl query --hosts "$host_hostname" --exit --query 'SELECT * FROM orbit_info;' 2>/dev/null | jq '.rows[0].version') + done + done + + # We kill the two servers and bring back the old one. + pkill file-server || true + TUF_PORT=$OLD_TUF_PORT TUF_PATH=$OLD_TUF_PATH ./tools/tuf/test/run_server.sh + # Restore files on the new repository. + mv $NEW_TUF_PATH/tmp/* $NEW_TUF_PATH/repository/targets/ + + if [ "$ORBIT_PATCH_IN_OLD_TUF" = "1" ]; then + echo "Build and push a new update to orbit to old and new repository (to test patching an invalid 1.38.0 would work for customers without access to new TUF)" + ROOT_KEYS2=$(./build/fleetctl updates roots --path $NEW_TUF_PATH) + CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build \ + -o orbit-darwin \ + -ldflags="-X github.com/fleetdm/fleet/v4/orbit/pkg/build.Version=$NEW_PATCH_VERSION \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.DefaultURL=$NEW_TUF_URL \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.defaultRootMetadata=$ROOT_KEYS2 \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.OldFleetTUFURL=$OLD_TUF_URL" \ + ./orbit/cmd/orbit + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ + -o orbit-linux \ + -ldflags="-X github.com/fleetdm/fleet/v4/orbit/pkg/build.Version=$NEW_PATCH_VERSION \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.DefaultURL=$NEW_TUF_URL \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.defaultRootMetadata=$ROOT_KEYS2 \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.OldFleetTUFURL=$OLD_TUF_URL" \ + ./orbit/cmd/orbit + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build \ + -o orbit.exe \ + -ldflags="-X github.com/fleetdm/fleet/v4/orbit/pkg/build.Version=$NEW_PATCH_VERSION \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.DefaultURL=$NEW_TUF_URL \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.defaultRootMetadata=$ROOT_KEYS2 \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.OldFleetTUFURL=$OLD_TUF_URL" \ + ./orbit/cmd/orbit + ./build/fleetctl updates add --path $OLD_TUF_PATH --target ./orbit-darwin --platform macos --name orbit --version $NEW_PATCH_VERSION -t $NEW_MINOR_VERSION -t 1 -t stable + ./build/fleetctl updates add --path $OLD_TUF_PATH --target ./orbit-linux --platform linux --name orbit --version $NEW_PATCH_VERSION -t $NEW_MINOR_VERSION -t 1 -t stable + ./build/fleetctl updates add --path $OLD_TUF_PATH --target ./orbit.exe --platform windows --name orbit --version $NEW_PATCH_VERSION -t $NEW_MINOR_VERSION -t 1 -t stable + ./build/fleetctl updates add --path $NEW_TUF_PATH --target ./orbit-darwin --platform macos --name orbit --version $NEW_PATCH_VERSION -t $NEW_MINOR_VERSION -t 1 -t stable + ./build/fleetctl updates add --path $NEW_TUF_PATH --target ./orbit-linux --platform linux --name orbit --version $NEW_PATCH_VERSION -t $NEW_MINOR_VERSION -t 1 -t stable + ./build/fleetctl updates add --path $NEW_TUF_PATH --target ./orbit.exe --platform windows --name orbit --version $NEW_PATCH_VERSION -t $NEW_MINOR_VERSION -t 1 -t stable + + echo "Checking orbit has auto-updated to $NEW_PATCH_VERSION using old TUF..." + for host_hostname in "${hostnames[@]}"; do + ORBIT_VERSION="" + until [ "$ORBIT_VERSION" = "\"$NEW_PATCH_VERSION\"" ]; do + sleep 1 + ORBIT_VERSION=$(fleetctl query --hosts "$host_hostname" --exit --query 'SELECT * FROM orbit_info;' 2>/dev/null | jq '.rows[0].version') + done + done + + # Now the next patch version will be 1.38.2. + NEW_FULL_VERSION=1.38.1 + NEW_PATCH_VERSION=1.38.2 + fi + + echo "Restoring new TUF repository..." + TUF_PORT=$NEW_TUF_PORT TUF_PATH=$NEW_TUF_PATH ./tools/tuf/test/run_server.sh + + prompt "Please check that devices have restarted and started communicating with the new TUF (now that it's available)" +fi + +echo "Checking version of updated orbit..." +THIS_HOSTNAME=$(hostname) +declare -a hostnames=("$THIS_HOSTNAME" "$WINDOWS_HOST_HOSTNAME" "$LINUX_HOST_HOSTNAME") +for host_hostname in "${hostnames[@]}"; do + ORBIT_VERSION="" + until [ "$ORBIT_VERSION" = "\"$NEW_FULL_VERSION\"" ]; do + sleep 1 + ORBIT_VERSION=$(fleetctl query --hosts "$host_hostname" --exit --query 'SELECT * FROM orbit_info;' 2>/dev/null | jq '.rows[0].version') + done +done + +echo "Restarting fleetd on the macOS host..." +sudo launchctl unload /Library/LaunchDaemons/com.fleetdm.orbit.plist && sudo launchctl load /Library/LaunchDaemons/com.fleetdm.orbit.plist + +prompt "Please restart fleetd on the Linux and Windows host." + +echo "Checking version of updated orbit..." +THIS_HOSTNAME=$(hostname) +for host_hostname in "${hostnames[@]}"; do + ORBIT_VERSION="" + until [ "$ORBIT_VERSION" = "\"$NEW_FULL_VERSION\"" ]; do + sleep 1 + ORBIT_VERSION=$(fleetctl query --hosts "$host_hostname" --exit --query 'SELECT * FROM orbit_info;' 2>/dev/null | jq '.rows[0].version') + done +done + +echo "Building and pushing a new update to orbit on the new repository (to test upgrades are working)..." +ROOT_KEYS2=$(./build/fleetctl updates roots --path $NEW_TUF_PATH) +CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build \ + -o orbit-darwin \ + -ldflags="-X github.com/fleetdm/fleet/v4/orbit/pkg/build.Version=$NEW_PATCH_VERSION \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.DefaultURL=$NEW_TUF_URL \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.defaultRootMetadata=$ROOT_KEYS2 \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.OldFleetTUFURL=$OLD_TUF_URL" \ + ./orbit/cmd/orbit +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ + -o orbit-linux \ + -ldflags="-X github.com/fleetdm/fleet/v4/orbit/pkg/build.Version=$NEW_PATCH_VERSION \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.DefaultURL=$NEW_TUF_URL \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.defaultRootMetadata=$ROOT_KEYS2 \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.OldFleetTUFURL=$OLD_TUF_URL" \ + ./orbit/cmd/orbit +CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build \ + -o orbit.exe \ + -ldflags="-X github.com/fleetdm/fleet/v4/orbit/pkg/build.Version=$NEW_PATCH_VERSION \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.DefaultURL=$NEW_TUF_URL \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.defaultRootMetadata=$ROOT_KEYS2 \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.OldFleetTUFURL=$OLD_TUF_URL" \ + ./orbit/cmd/orbit +./build/fleetctl updates add --path $NEW_TUF_PATH --target ./orbit-darwin --platform macos --name orbit --version $NEW_PATCH_VERSION -t $NEW_MINOR_VERSION -t 1 -t stable +./build/fleetctl updates add --path $NEW_TUF_PATH --target ./orbit-linux --platform linux --name orbit --version $NEW_PATCH_VERSION -t $NEW_MINOR_VERSION -t 1 -t stable +./build/fleetctl updates add --path $NEW_TUF_PATH --target ./orbit.exe --platform windows --name orbit --version $NEW_PATCH_VERSION -t $NEW_MINOR_VERSION -t 1 -t stable + +echo "Waiting until update happens..." +for host_hostname in "${hostnames[@]}"; do + ORBIT_VERSION="" + until [ "$ORBIT_VERSION" = "\"$NEW_PATCH_VERSION\"" ]; do + sleep 1 + ORBIT_VERSION=$(fleetctl query --hosts "$host_hostname" --exit --query 'SELECT * FROM orbit_info;' 2>/dev/null | jq '.rows[0].version') + done +done + +echo "Downgrading to $OLD_FULL_VERSION..." +cat << EOF > downgrade.yml +--- +apiVersion: v1 +kind: config +spec: + agent_options: + config: + options: + pack_delimiter: / + distributed_plugin: tls + disable_distributed: false + logger_tls_endpoint: /api/v1/osquery/log + distributed_interval: 10 + distributed_tls_max_attempts: 3 + distributed_denylist_duration: 10 + decorators: + load: + - SELECT uuid AS host_uuid FROM system_info; + - SELECT hostname AS hostname FROM system_info; + update_channels: + orbit: '$OLD_FULL_VERSION' + desktop: stable + osqueryd: stable +EOF +fleetctl apply -f downgrade.yml + +echo "Waiting until downgrade happens..." +for host_hostname in "${hostnames[@]}"; do + ORBIT_VERSION="" + until [ "$ORBIT_VERSION" = "\"$OLD_FULL_VERSION\"" ]; do + sleep 1 + ORBIT_VERSION=$(fleetctl query --hosts "$host_hostname" --exit --query 'SELECT * FROM orbit_info;' 2>/dev/null | jq '.rows[0].version') + done +done + +echo "Restoring to latest orbit version..." +cat << EOF > upgrade.yml +--- +apiVersion: v1 +kind: config +spec: + agent_options: + config: + options: + pack_delimiter: / + distributed_plugin: tls + disable_distributed: false + logger_tls_endpoint: /api/v1/osquery/log + distributed_interval: 10 + distributed_tls_max_attempts: 3 + distributed_denylist_duration: 10 + decorators: + load: + - SELECT uuid AS host_uuid FROM system_info; + - SELECT hostname AS hostname FROM system_info; + update_channels: + orbit: stable + desktop: stable + osqueryd: stable +EOF +fleetctl apply -f upgrade.yml + +echo "Waiting until upgrade happens..." +for host_hostname in "${hostnames[@]}"; do + ORBIT_VERSION="" + until [ "$ORBIT_VERSION" = "\"$NEW_PATCH_VERSION\"" ]; do + sleep 1 + ORBIT_VERSION=$(fleetctl query --hosts "$host_hostname" --exit --query 'SELECT * FROM orbit_info;' 2>/dev/null | jq '.rows[0].version') + done +done + +echo "Building fleetd packages using old repository and old fleetctl version that should auto-update to new orbit that talks to new repository..." +for pkgType in "${pkgTypes[@]}"; do + ./build/fleetctl-v4.60.0 package --type="$pkgType" \ + --enable-scripts \ + --fleet-desktop \ + --fleet-url="$FLEET_URL" \ + --enroll-secret="$NO_TEAM_ENROLL_SECRET" \ + --fleet-certificate=./tools/osquery/fleet.crt \ + --debug \ + --update-roots="$ROOT_KEYS1" \ + --update-url=$OLD_TUF_URL \ + --disable-open-folder \ + --disable-keystore \ + --update-interval=30s +done + +echo "Installing fleetd package on macOS..." +sudo installer -pkg fleet-osquery.pkg -verbose -target / + +CURRENT_DIR=$(pwd) +prompt "Please install $CURRENT_DIR/fleet-osquery.msi and $CURRENT_DIR/fleet-osquery_${NEW_FULL_VERSION}_amd64.deb." + +echo "Waiting until installation and auto-update to new repository happens..." +for host_hostname in "${hostnames[@]}"; do + ORBIT_VERSION="" + until [ "$ORBIT_VERSION" = "\"$NEW_PATCH_VERSION\"" ]; do + sleep 1 + ORBIT_VERSION=$(fleetctl query --hosts "$host_hostname" --exit --query 'SELECT * FROM orbit_info;' 2>/dev/null | jq '.rows[0].version') + done +done + +echo "Downgrading to $OLD_FULL_VERSION..." +cat << EOF > downgrade.yml +--- +apiVersion: v1 +kind: config +spec: + agent_options: + config: + options: + pack_delimiter: / + distributed_plugin: tls + disable_distributed: false + logger_tls_endpoint: /api/v1/osquery/log + distributed_interval: 10 + distributed_tls_max_attempts: 3 + distributed_denylist_duration: 10 + decorators: + load: + - SELECT uuid AS host_uuid FROM system_info; + - SELECT hostname AS hostname FROM system_info; + update_channels: + orbit: '$OLD_FULL_VERSION' + desktop: stable + osqueryd: stable +EOF +fleetctl apply -f downgrade.yml + +echo "Waiting until downgrade happens..." +for host_hostname in "${hostnames[@]}"; do + ORBIT_VERSION="" + until [ "$ORBIT_VERSION" = "\"$OLD_FULL_VERSION\"" ]; do + sleep 1 + ORBIT_VERSION=$(fleetctl query --hosts "$host_hostname" --exit --query 'SELECT * FROM orbit_info;' 2>/dev/null | jq '.rows[0].version') + done +done + +echo "Restoring to latest orbit version..." +cat << EOF > upgrade.yml +--- +apiVersion: v1 +kind: config +spec: + agent_options: + config: + options: + pack_delimiter: / + distributed_plugin: tls + disable_distributed: false + logger_tls_endpoint: /api/v1/osquery/log + distributed_interval: 10 + distributed_tls_max_attempts: 3 + distributed_denylist_duration: 10 + decorators: + load: + - SELECT uuid AS host_uuid FROM system_info; + - SELECT hostname AS hostname FROM system_info; + update_channels: + orbit: stable + desktop: stable + osqueryd: stable +EOF +fleetctl apply -f upgrade.yml + +echo "Waiting until upgrade happens..." +for host_hostname in "${hostnames[@]}"; do + ORBIT_VERSION="" + until [ "$ORBIT_VERSION" = "\"$NEW_PATCH_VERSION\"" ]; do + sleep 1 + ORBIT_VERSION=$(fleetctl query --hosts "$host_hostname" --exit --query 'SELECT * FROM orbit_info;' 2>/dev/null | jq '.rows[0].version') + done +done + + +echo "Building fleetd packages using new repository and new fleetctl version..." + +CGO_ENABLED=0 go build \ + -o ./build/fleetctl \ + -ldflags="-X github.com/fleetdm/fleet/v4/orbit/pkg/update.defaultRootMetadata=$ROOT_KEYS2 \ + -X github.com/fleetdm/fleet/v4/orbit/pkg/update.DefaultURL=$NEW_TUF_URL" \ + ./cmd/fleetctl + +for pkgType in "${pkgTypes[@]}"; do + ./build/fleetctl package --type="$pkgType" \ + --enable-scripts \ + --fleet-desktop \ + --fleet-url="$FLEET_URL" \ + --enroll-secret="$NO_TEAM_ENROLL_SECRET" \ + --fleet-certificate=./tools/osquery/fleet.crt \ + --debug \ + --disable-open-folder \ + --disable-keystore \ + --update-interval=30s +done + +echo "Installing fleetd package on macOS..." +sudo installer -pkg fleet-osquery.pkg -verbose -target / + +CURRENT_DIR=$(pwd) +prompt "Please install $CURRENT_DIR/fleet-osquery.msi and $CURRENT_DIR/fleet-osquery_${NEW_PATCH_VERSION}_amd64.deb." + +echo "Waiting until installation and auto-update to new repository happens..." +for host_hostname in "${hostnames[@]}"; do + ORBIT_VERSION="" + until [ "$ORBIT_VERSION" = "\"$NEW_PATCH_VERSION\"" ]; do + sleep 1 + ORBIT_VERSION=$(fleetctl query --hosts "$host_hostname" --exit --query 'SELECT * FROM orbit_info;' 2>/dev/null | jq '.rows[0].version') + done +done + +cat << EOF > downgrade.yml +--- +apiVersion: v1 +kind: config +spec: + agent_options: + config: + options: + pack_delimiter: / + distributed_plugin: tls + disable_distributed: false + logger_tls_endpoint: /api/v1/osquery/log + distributed_interval: 10 + distributed_tls_max_attempts: 3 + distributed_denylist_duration: 10 + decorators: + load: + - SELECT uuid AS host_uuid FROM system_info; + - SELECT hostname AS hostname FROM system_info; + update_channels: + orbit: '$OLD_FULL_VERSION' + desktop: stable + osqueryd: stable +EOF +fleetctl apply -f downgrade.yml + +echo "Waiting until downgrade happens..." +for host_hostname in "${hostnames[@]}"; do + ORBIT_VERSION="" + until [ "$ORBIT_VERSION" = "\"$OLD_FULL_VERSION\"" ]; do + sleep 1 + ORBIT_VERSION=$(fleetctl query --hosts "$host_hostname" --exit --query 'SELECT * FROM orbit_info;' 2>/dev/null | jq '.rows[0].version') + done +done + +echo "Migration testing completed." diff --git a/tools/tuf/test/push_target.sh b/tools/tuf/test/push_target.sh index a29b3da17de2..08771ece6e4f 100755 --- a/tools/tuf/test/push_target.sh +++ b/tools/tuf/test/push_target.sh @@ -4,7 +4,11 @@ system=$1 target_name=$2 target_path=$3 major_version=$4 -TUF_PATH=test_tuf + +if [ -z "$TUF_PATH" ]; then + TUF_PATH=test_tuf +fi +export TUF_PATH export FLEET_ROOT_PASSPHRASE=p4ssphr4s3 export FLEET_TARGETS_PASSPHRASE=p4ssphr4s3 diff --git a/tools/tuf/test/run_server.sh b/tools/tuf/test/run_server.sh index cb87a7d2ca83..b734d0a2975c 100755 --- a/tools/tuf/test/run_server.sh +++ b/tools/tuf/test/run_server.sh @@ -2,10 +2,14 @@ set -e -pkill file-server || true -echo "Running TUF server" -go run ./tools/file-server 8081 "${TUF_PATH}/repository" & -until curl --silent -o /dev/null http://localhost:8081/root.json; do +if curl --silent -o /dev/null "http://localhost:$TUF_PORT/root.json" ; then + echo "TUF server already running" + exit 0 +fi + +echo "Start TUF server" +go run ./tools/file-server "$TUF_PORT" "${TUF_PATH}/repository" & +until curl --silent -o /dev/null "http://localhost:$TUF_PORT/root.json"; do sleep 1 done echo "TUF server started" \ No newline at end of file From 147c5542e8fc6169e2b11d70a3cccdbe682324f4 Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Fri, 10 Jan 2025 11:40:46 -0600 Subject: [PATCH 113/208] fix path to artifacts json (#25331) --- .github/workflows/goreleaser-fleet.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/goreleaser-fleet.yaml b/.github/workflows/goreleaser-fleet.yaml index 4bb67abbb293..09f138d70850 100644 --- a/.github/workflows/goreleaser-fleet.yaml +++ b/.github/workflows/goreleaser-fleet.yaml @@ -106,8 +106,8 @@ jobs: continue-on-error: true id: image_digests run: | - echo "digest_fleet=$(cat ./dist/artifact.json | jq -r '.[]|select(.type == "Published Docker Image" and (.name | contains("fleetdm/fleet:${{ steps.commit.outputs.short_commit }}"))) | select(. != null)|.extra.Digest')" >> "$GITHUB_OUTPUT" - echo "digest_fleetctl=$(cat ./dist/artifact.json | jq -r '.[]|select(.type == "Published Docker Image" and (.name | contains("fleetdm/fleetctl:${{ steps.commit.outputs.short_commit }}"))) | select(. != null)|.extra.Digest')" >> "$GITHUB_OUTPUT" + echo "digest_fleet=$(cat ./dist/artifacts.json | jq -r '.[]|select(.type == "Published Docker Image" and (.name | contains("fleetdm/fleet:${{ steps.commit.outputs.short_commit }}"))) | select(. != null)|.extra.Digest')" >> "$GITHUB_OUTPUT" + echo "digest_fleetctl=$(cat ./dist/artifacts.json | jq -r '.[]|select(.type == "Published Docker Image" and (.name | contains("fleetdm/fleetctl:${{ steps.commit.outputs.short_commit }}"))) | select(. != null)|.extra.Digest')" >> "$GITHUB_OUTPUT" - name: Attest Fleet image uses: actions/attest-build-provenance@619dbb2e03e0189af0c55118e7d3c5e129e99726 # v2.0 From 7e419f97cb36eb50c30e843848ade565d8b1b38d Mon Sep 17 00:00:00 2001 From: Lucas Manuel Rodriguez Date: Fri, 10 Jan 2025 14:42:55 -0300 Subject: [PATCH 114/208] Fix missing docs and yaml (#25333) --- .github/workflows/check-automated-doc.yml | 2 -- docs/Contributing/Audit-logs.md | 9 ++------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/check-automated-doc.yml b/.github/workflows/check-automated-doc.yml index a3a638bb4f2a..ae07734cbc26 100644 --- a/.github/workflows/check-automated-doc.yml +++ b/.github/workflows/check-automated-doc.yml @@ -38,8 +38,6 @@ jobs: - name: Checkout Code uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2 - with: - fetch-depth: 0 - name: Install Go uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 diff --git a/docs/Contributing/Audit-logs.md b/docs/Contributing/Audit-logs.md index 10040a0c0859..19dea07e2f81 100644 --- a/docs/Contributing/Audit-logs.md +++ b/docs/Contributing/Audit-logs.md @@ -1238,7 +1238,6 @@ Generated when a software installer is uploaded to Fleet. This activity contains the following fields: - "software_title": Name of the software. - "software_package": Filename of the installer. -- "software_title_id": ID of the added software title. - "team_name": Name of the team to which this software was added. `null` if it was added to no team." + - "team_id": The ID of the team to which this software was added. `null` if it was added to no team. - "self_service": Whether the software is available for installation by the end user. @@ -1252,7 +1251,6 @@ This activity contains the following fields: { "software_title": "Falcon.app", "software_package": "FalconSensor-6.44.pkg", - "software_title_id": 2344, "team_name": "Workstations", "team_id": 123, "self_service": true, @@ -1277,10 +1275,10 @@ Generated when a software installer is updated in Fleet. This activity contains the following fields: - "software_title": Name of the software. - "software_package": Filename of the installer as of this update (including if unchanged). -- "software_title_id": ID of the edited software title. - "team_name": Name of the team on which this software was updated. `null` if it was updated on no team. - "team_id": The ID of the team on which this software was updated. `null` if it was updated on no team. - "self_service": Whether the software is available for installation by the end user. +- "software_title_id": ID of the added software title. - "labels_include_any": Target hosts that have any label in the array. - "labels_exclude_any": Target hosts that don't have any label in the array. @@ -1290,10 +1288,10 @@ This activity contains the following fields: { "software_title": "Falcon.app", "software_package": "FalconSensor-6.44.pkg", - "software_title_id": 2344, "team_name": "Workstations", "team_id": 123, "self_service": true, + "software_title_id": 2234, "labels_include_any": [ { "name": "Engineering", @@ -1384,8 +1382,6 @@ This activity contains the following fields: - "self_service": App installation can be initiated by device owner. - "team_name": Name of the team to which this App Store app was added, or `null` if it was added to no team. - "team_id": ID of the team to which this App Store app was added, or `null`if it was added to no team. -- "software_title_id": ID of the edited software title. - #### Example @@ -1396,7 +1392,6 @@ This activity contains the following fields: "app_store_id": "1234567", "platform": "darwin", "self_service": false, - "software_title_id": 3537, "team_name": "Workstations", "team_id": 1 } From d27d6796f73270d64601a3b68a5b5773669734eb Mon Sep 17 00:00:00 2001 From: Rachael Shaw Date: Fri, 10 Jan 2025 11:51:35 -0600 Subject: [PATCH 115/208] Small formatting fix in API for contributors (#25336) Fixed some curly brackets that should have been square brackets --- docs/Contributing/API-for-contributors.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Contributing/API-for-contributors.md b/docs/Contributing/API-for-contributors.md index 32a08b9190aa..8bfa61d60752 100644 --- a/docs/Contributing/API-for-contributors.md +++ b/docs/Contributing/API-for-contributors.md @@ -4179,7 +4179,7 @@ _Available in Fleet Premium._ ```json { "team_name": "Foobar", - "app_store_apps": { + "app_store_apps": [ { "app_store_id": "597799333", "self_service": false @@ -4188,7 +4188,7 @@ _Available in Fleet Premium._ "app_store_id": "497799835", "self_service": true, } - } + ] } ``` From 8c338a1d775b51c1a1afe8dc8bf6fb5cca44ef06 Mon Sep 17 00:00:00 2001 From: Dante Catalfamo <43040593+dantecatalfamo@users.noreply.github.com> Date: Fri, 10 Jan 2025 12:52:13 -0500 Subject: [PATCH 116/208] Try splitting up integration tests (#25312) Follow up to #25271 and #21774 Integration test failures will happen much faster of they occur, but now the bottleneck is the `fleetctl` test suite. It's trivial to continue splitting tests up now. We should look into creating an action that checks that mock generation is up-to-date, run it before all the tests, and then remove the mock generation step from each test step. That would save about a minute and a half of runtime from each test and help offset the cost of splitting the tests up. ![ci runtime breakdown](https://github.com/user-attachments/assets/057b8ee1-782c-4e1f-9486-42c7d1169c81) ![ci runtime max](https://github.com/user-attachments/assets/3a26995f-d9cb-490b-84d9-1a7fbb3cd6b3) ![image](https://github.com/user-attachments/assets/b4c888c8-867f-4bdd-9b69-0dc20d0d202a) --- .github/workflows/test-go.yaml | 14 ++++++++++---- Makefile | 2 ++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-go.yaml b/.github/workflows/test-go.yaml index e347f7278217..1a65e08d65ad 100644 --- a/.github/workflows/test-go.yaml +++ b/.github/workflows/test-go.yaml @@ -42,7 +42,7 @@ jobs: test-go: strategy: matrix: - suite: ["integration", "core", "mysql", "fleetctl", "vuln"] + suite: ["integration-core", "integration-enterprise", "integration-mdm", "core", "mysql", "fleetctl", "vuln"] os: [ubuntu-latest] mysql: ["mysql:8.0.36", "mysql:8.4.3", "mysql:9.1.0"] # make sure to update supported versions docs when this changes isCron: @@ -120,9 +120,15 @@ jobs: if [[ "${{ matrix.suite }}" == "core" ]]; then CI_TEST_PKG=main RUN_TESTS_ARG='-skip=^TestIntegrations' - elif [[ "${{ matrix.suite }}" == "integration" ]]; then - CI_TEST_PKG=main - RUN_TESTS_ARG='-run=^TestIntegrations' + elif [[ "${{ matrix.suite }}" == "integration-core" ]]; then + CI_TEST_PKG=integration + RUN_TESTS_ARG='-run=^TestIntegrations -skip "^(TestIntegrationsMDM|TestIntegrationsEnterprise)"' + elif [[ "${{ matrix.suite }}" == "integration-mdm" ]]; then + CI_TEST_PKG=integration + RUN_TESTS_ARG='-run=^TestIntegrationsMDM' + elif [[ "${{ matrix.suite }}" == "integration-enterprise" ]]; then + CI_TEST_PKG=integration + RUN_TESTS_ARG='-run=^TestIntegrationsEnterprise' else CI_TEST_PKG="${{ matrix.suite }}" RUN_TESTS_ARG='' diff --git a/Makefile b/Makefile index 6ffdca38de79..23c7fc5c178c 100644 --- a/Makefile +++ b/Makefile @@ -158,6 +158,8 @@ dlv_test_pkg_to_test := $(addprefix github.com/fleetdm/fleet/v4/,$(PKG_TO_TEST)) DEFAULT_PKG_TO_TEST := ./cmd/... ./ee/... ./orbit/pkg/... ./orbit/cmd/orbit ./pkg/... ./server/... ./tools/... ifeq ($(CI_TEST_PKG), main) CI_PKG_TO_TEST=$(shell go list ${DEFAULT_PKG_TO_TEST} | grep -v "server/datastore/mysql" | grep -v "cmd/fleetctl" | grep -v "server/vulnerabilities" | sed -e 's|github.com/fleetdm/fleet/v4/||g') +else ifeq ($(CI_TEST_PKG), integration) + CI_PKG_TO_TEST="server/service" else ifeq ($(CI_TEST_PKG), mysql) CI_PKG_TO_TEST="server/datastore/mysql/..." else ifeq ($(CI_TEST_PKG), fleetctl) From b4a2115b2cd8c39918ade8d8889c322c578c6f11 Mon Sep 17 00:00:00 2001 From: Dante Catalfamo <43040593+dantecatalfamo@users.noreply.github.com> Date: Fri, 10 Jan 2025 13:13:28 -0500 Subject: [PATCH 117/208] Display correct key path to user for agent options (#25199) #24038 --- changes/24038-agent-options-key-error | 1 + ee/server/service/teams.go | 17 ++++ server/fleet/agent_options.go | 85 +++++++++++++++++++ server/fleet/errors.go | 9 ++ server/service/integration_enterprise_test.go | 26 ++++++ 5 files changed, 138 insertions(+) create mode 100644 changes/24038-agent-options-key-error diff --git a/changes/24038-agent-options-key-error b/changes/24038-agent-options-key-error new file mode 100644 index 000000000000..dd8235c59a9e --- /dev/null +++ b/changes/24038-agent-options-key-error @@ -0,0 +1 @@ +- Display the correct path for agent options when a key is placed in the wrong object diff --git a/ee/server/service/teams.go b/ee/server/service/teams.go index eded9b4788ac..121351b2559a 100644 --- a/ee/server/service/teams.go +++ b/ee/server/service/teams.go @@ -414,6 +414,23 @@ func (svc *Service) ModifyTeamAgentOptions(ctx context.Context, teamID uint, tea if teamOptions != nil { if err := fleet.ValidateJSONAgentOptions(ctx, svc.ds, teamOptions, true); err != nil { + if field := fleet.GetJSONUnknownField(err); field != nil { + correctKeyPath, keyErr := fleet.FindAgentOptionsKeyPath(*field) + if keyErr != nil { + level.Error(svc.logger).Log("err", err, "msg", "error parsing generated agent options structs") + } + var keyPathJoined string + switch pathLen := len(correctKeyPath); { + case pathLen > 1: + keyPathJoined = fmt.Sprintf("%q", strings.Join(correctKeyPath[:len(correctKeyPath)-1], ".")) + case pathLen == 1: + keyPathJoined = "top level" + } + if keyPathJoined != "" { + err = fmt.Errorf("%q should be part of the %s object", *field, keyPathJoined) + } + } + err = fleet.NewUserMessageError(err, http.StatusBadRequest) if applyOptions.Force && !applyOptions.DryRun { level.Info(svc.logger).Log("err", err, "msg", "force-apply team agent options with validation errors") diff --git a/server/fleet/agent_options.go b/server/fleet/agent_options.go index 0e185a3fee2e..51f9d94dcfdc 100644 --- a/server/fleet/agent_options.go +++ b/server/fleet/agent_options.go @@ -325,3 +325,88 @@ func validateJSONAgentOptionsSet(rawJSON json.RawMessage) error { } return nil } + +func FindAgentOptionsKeyPath(key string) ([]string, error) { + if key == "script_execution_timeout" { + return []string{"script_execution_timeout"}, nil + } + + configPath, err := locateStructJSONKeyPath(key, "config", osqueryAgentOptions{}) + if err != nil { + return nil, fmt.Errorf("locating key path in agent options: %w", err) + } + if configPath != nil { + return configPath, nil + } + + if key == "overrides" { + return []string{"overrides"}, nil + } + if key == "platforms" { + return []string{"overrides", "platforms"}, nil + } + + commandLinePath, err := locateStructJSONKeyPath(key, "command_line_flags", osqueryCommandLineFlags{}) + if err != nil { + return nil, fmt.Errorf("locating key path in agent command line options: %w", err) + } + if commandLinePath != nil { + return commandLinePath, nil + } + + extensionsPath, err := locateStructJSONKeyPath(key, "extensions", ExtensionInfo{}) + if err != nil { + return nil, fmt.Errorf("locating key path in agent extensions options: %w", err) + } + if extensionsPath != nil { + return extensionsPath, nil + } + + channelsPath, err := locateStructJSONKeyPath(key, "update_channels", OrbitUpdateChannels{}) + if err != nil { + return nil, fmt.Errorf("locating key path in agent update channels: %w", err) + } + if channelsPath != nil { + return channelsPath, nil + } + + return nil, nil +} + +// Only searches two layers deep +func locateStructJSONKeyPath(key, startKey string, target any) ([]string, error) { + if key == startKey { + return []string{startKey}, nil + } + + optionsBytes, err := json.Marshal(target) + if err != nil { + return nil, fmt.Errorf("unable to marshall target: %w", err) + } + + var opts map[string]any + + if err := json.Unmarshal(optionsBytes, &opts); err != nil { + return nil, fmt.Errorf("unable to unmarshall target: %w", err) + } + + var path [3]string + path[0] = startKey + for k, v := range opts { + path[1] = k + if k == key { + return path[:2], nil + } + + if inner, ok := v.(map[string]any); ok { + for k2 := range inner { + path[2] = k2 + if key == k2 { + return path[:3], nil + } + } + } + } + + return nil, nil +} diff --git a/server/fleet/errors.go b/server/fleet/errors.go index fb5e302c11ca..77098617bc73 100644 --- a/server/fleet/errors.go +++ b/server/fleet/errors.go @@ -477,6 +477,15 @@ func IsJSONUnknownFieldError(err error) bool { return rxJSONUnknownField.MatchString(err.Error()) } +func GetJSONUnknownField(err error) *string { + errCause := Cause(err) + if IsJSONUnknownFieldError(errCause) { + substr := rxJSONUnknownField.FindStringSubmatch(errCause.Error()) + return &substr[1] + } + return nil +} + // UserMessage implements the user-friendly translation of the error if its // root cause is one of the supported types, otherwise it returns the error // message. diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go index 8d102a974687..b210cb1e8fbe 100644 --- a/server/service/integration_enterprise_test.go +++ b/server/service/integration_enterprise_test.go @@ -1476,6 +1476,32 @@ func (s *integrationEnterpriseTestSuite) TestTeamEndpoints() { "x": "y" }`), http.StatusBadRequest, &tmResp) + // modify team agent options with invalid key + badRes := s.Do("POST", fmt.Sprintf("/api/latest/fleet/teams/%d/agent_options", tm1ID), json.RawMessage(`{ + "bad_key": 1 + }`), http.StatusBadRequest) + errText := extractServerErrorText(badRes.Body) + require.Contains(t, errText, "unsupported key provided") + + // modify team agent options with correct options under the wrong key + badRes = s.Do("POST", fmt.Sprintf("/api/latest/fleet/teams/%d/agent_options", tm1ID), json.RawMessage(`{ + "distributed_tls_max_attempts": 3 + }`), http.StatusBadRequest) + errText = extractServerErrorText(badRes.Body) + require.Contains(t, errText, "\"distributed_tls_max_attempts\" should be part of the \"config.options\" object") + + badRes = s.Do("POST", fmt.Sprintf("/api/latest/fleet/teams/%d/agent_options", tm1ID), json.RawMessage(`{ + "config": { "options": { "logger_plugin": 3 } } + }`), http.StatusBadRequest) + errText = extractServerErrorText(badRes.Body) + require.Contains(t, errText, "\"logger_plugin\" should be part of the \"command_line_flags\" object") + + badRes = s.Do("POST", fmt.Sprintf("/api/latest/fleet/teams/%d/agent_options", tm1ID), json.RawMessage(`{ + "update_channels": { "config": 1 } + }`), http.StatusBadRequest) + errText = extractServerErrorText(badRes.Body) + require.Contains(t, errText, "\"config\" should be part of the top level object") + // modify team agent options with invalid platform options tmResp.Team = nil s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/teams/%d/agent_options", tm1ID), json.RawMessage( From 98b839c616fffb27909901afd8afd309ff8eb1ac Mon Sep 17 00:00:00 2001 From: jacobshandling <61553566+jacobshandling@users.noreply.github.com> Date: Fri, 10 Jan 2025 10:42:44 -0800 Subject: [PATCH 118/208] Replace email logo with one that looks good in both light and dark mode (#25192) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## For #24618 **Change email:** Screenshot 2025-01-06 at 3 50 27 PM **Invite user:** Screenshot 2025-01-06 at 4 15 05 PM **Enable MFA:** Screenshot 2025-01-06 at 4 21 46 PM **Reset password:** Screenshot 2025-01-06 at 4 25 54 PM **Setup smtp:** Screenshot 2025-01-06 at 4 28 29 PM - [x] Changes file added for user-visible changes in `changes/` - [x] Manual QA for all new/changed functionality --------- Co-authored-by: Jacob Shandling --- ...24618-make-email-logo-dark-mode-compatible | 1 + .../templates/change_email_confirmation.html | 14 ++--- server/mail/templates/invite_token.html | 9 ++-- server/mail/templates/mfa.html | 13 +++-- server/mail/templates/password_reset.html | 25 +++++---- server/mail/templates/smtp_setup.html | 48 ++++++++++-------- ...eet-logo-email-dark-friendly-162x92@2x.png | Bin 0 -> 5759 bytes 7 files changed, 64 insertions(+), 46 deletions(-) create mode 100644 changes/24618-make-email-logo-dark-mode-compatible create mode 100644 website/assets/images/permanent/fleet-logo-email-dark-friendly-162x92@2x.png diff --git a/changes/24618-make-email-logo-dark-mode-compatible b/changes/24618-make-email-logo-dark-mode-compatible new file mode 100644 index 000000000000..421e91d9b787 --- /dev/null +++ b/changes/24618-make-email-logo-dark-mode-compatible @@ -0,0 +1 @@ +* Use an email logo compatible with dark modes diff --git a/server/mail/templates/change_email_confirmation.html b/server/mail/templates/change_email_confirmation.html index 917e45a8a2a3..b804150ec444 100644 --- a/server/mail/templates/change_email_confirmation.html +++ b/server/mail/templates/change_email_confirmation.html @@ -2,7 +2,10 @@ - +