From 13e5e14a2f98059c903bf0770d6d4921d2d62729 Mon Sep 17 00:00:00 2001
From: Sarah Gillespie <73313222+gillespi314@users.noreply.github.com>
Date: Wed, 31 Jul 2024 10:03:10 -0500
Subject: [PATCH 01/88] Update UI to allow for showing software install details
from activities related to deleted hosts (#20866)
---
.../AppInstallDetails/AppInstallDetails.tsx | 11 ++++--
.../SoftwareInstallDetails.tsx | 37 +++++++++++++------
.../cards/ActivityFeed/ActivityFeed.tsx | 16 ++++----
.../HostDetailsPage/HostDetailsPage.tsx | 18 ++++++---
.../SoftwareDetailsModal.tsx | 3 +-
5 files changed, 57 insertions(+), 28 deletions(-)
diff --git a/frontend/components/ActivityDetails/InstallDetails/AppInstallDetails/AppInstallDetails.tsx b/frontend/components/ActivityDetails/InstallDetails/AppInstallDetails/AppInstallDetails.tsx
index c4991f0fd7..dd1de5a727 100644
--- a/frontend/components/ActivityDetails/InstallDetails/AppInstallDetails/AppInstallDetails.tsx
+++ b/frontend/components/ActivityDetails/InstallDetails/AppInstallDetails/AppInstallDetails.tsx
@@ -90,6 +90,12 @@ export const AppInstallDetails = ({
subordinate = status === "pending" ? " when it comes online" : "";
}
+ const formattedHost = host_display_name ? (
+ {host_display_name}
+ ) : (
+ "the host"
+ );
+
const showCommandResponse = isStatusNotNow || status !== "pending";
return (
@@ -98,8 +104,7 @@ export const AppInstallDetails = ({
- The response from
{host_display_name} :
+ The response from {formattedHost}:
diff --git a/frontend/components/ActivityDetails/InstallDetails/SoftwareInstallDetails/SoftwareInstallDetails.tsx b/frontend/components/ActivityDetails/InstallDetails/SoftwareInstallDetails/SoftwareInstallDetails.tsx
index 27480cd8e5..61c2d5f04f 100644
--- a/frontend/components/ActivityDetails/InstallDetails/SoftwareInstallDetails/SoftwareInstallDetails.tsx
+++ b/frontend/components/ActivityDetails/InstallDetails/SoftwareInstallDetails/SoftwareInstallDetails.tsx
@@ -1,6 +1,7 @@
import React from "react";
import { useQuery } from "react-query";
+import { IActivityDetails } from "interfaces/activity";
import {
ISoftwareInstallResult,
ISoftwareInstallResults,
@@ -21,17 +22,28 @@ import {
const baseClass = "software-install-details";
+// TODO: Expand to include more details as needed
+export type IPackageInstallDetails = Pick<
+ IActivityDetails,
+ "install_uuid" | "host_display_name"
+>;
+
const StatusMessage = ({
result: { host_display_name, software_package, software_title, status },
}: {
result: ISoftwareInstallResult;
}) => {
+ const formattedHost = host_display_name ? (
+
{host_display_name}
+ ) : (
+ "the host"
+ );
return (
Fleet {getInstallDetailsStatusPredicate(status)} {software_title} {" "}
- ({software_package}) on {host_display_name}
+ ({software_package}) on {formattedHost}
{status === "pending" ? " when it comes online" : ""}.
@@ -56,18 +68,17 @@ const Output = ({
};
export const SoftwareInstallDetails = ({
- installUuid,
-}: {
- installUuid: string;
-}) => {
+ host_display_name = "",
+ install_uuid = "",
+}: IPackageInstallDetails) => {
const { data: result, isLoading, isError } = useQuery<
ISoftwareInstallResults,
Error,
ISoftwareInstallResult
>(
- ["softwareInstallResults", installUuid],
+ ["softwareInstallResults", install_uuid],
() => {
- return softwareAPI.getSoftwareInstallResult(installUuid);
+ return softwareAPI.getSoftwareInstallResult(install_uuid);
},
{
refetchOnWindowFocus: false,
@@ -88,7 +99,11 @@ export const SoftwareInstallDetails = ({
return (
<>
-
+
{result.status !== "pending" && (
<>
{result.pre_install_query_output && (
@@ -106,10 +121,10 @@ export const SoftwareInstallDetails = ({
};
export const SoftwareInstallDetailsModal = ({
- installUuid,
+ details,
onCancel,
}: {
- installUuid: string;
+ details: IPackageInstallDetails;
onCancel: () => void;
}) => {
return (
@@ -121,7 +136,7 @@ export const SoftwareInstallDetailsModal = ({
>
<>
-
+
diff --git a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityFeed.tsx b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityFeed.tsx
index 3a5d520d27..c9e6b08af9 100644
--- a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityFeed.tsx
+++ b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityFeed.tsx
@@ -37,7 +37,10 @@ const ActivityFeed = ({
const [pageIndex, setPageIndex] = useState(0);
const [showShowQueryModal, setShowShowQueryModal] = useState(false);
const [showScriptDetailsModal, setShowScriptDetailsModal] = useState(false);
- const [installedSoftwareUuid, setInstalledSoftwareUuid] = useState("");
+ const [
+ packageInstallDetails,
+ setPackageInstallDetails,
+ ] = useState(null);
const [
appInstallDetails,
setAppInstallDetails,
@@ -88,7 +91,6 @@ const ActivityFeed = ({
activityType: ActivityType,
details: IActivityDetails
) => {
- console.log("activityType", activityType);
switch (activityType) {
case ActivityType.LiveQuery:
queryShown.current = details.query_sql ?? "";
@@ -102,10 +104,10 @@ const ActivityFeed = ({
setShowScriptDetailsModal(true);
break;
case ActivityType.InstalledSoftware:
- setInstalledSoftwareUuid(details.install_uuid ?? "");
+ setPackageInstallDetails({ ...details });
break;
case ActivityType.InstalledAppStoreApp:
- setAppInstallDetails(details);
+ setAppInstallDetails({ ...details });
break;
default:
break;
@@ -197,10 +199,10 @@ const ActivityFeed = ({
onCancel={() => setShowScriptDetailsModal(false)}
/>
)}
- {installedSoftwareUuid && (
+ {packageInstallDetails && (
setInstalledSoftwareUuid("")}
+ details={packageInstallDetails}
+ onCancel={() => setPackageInstallDetails(null)}
/>
)}
{appInstallDetails && (
diff --git a/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx b/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx
index 2f2c88a5bd..a55744bc33 100644
--- a/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx
+++ b/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx
@@ -62,7 +62,10 @@ import {
AppInstallDetailsModal,
IAppInstallDetails,
} from "components/ActivityDetails/InstallDetails/AppInstallDetails/AppInstallDetails";
-import { SoftwareInstallDetailsModal } from "components/ActivityDetails/InstallDetails/SoftwareInstallDetails";
+import {
+ SoftwareInstallDetailsModal,
+ IPackageInstallDetails,
+} from "components/ActivityDetails/InstallDetails/SoftwareInstallDetails/SoftwareInstallDetails";
import HostSummaryCard from "../cards/HostSummary";
import AboutCard from "../cards/About";
@@ -172,7 +175,10 @@ const HostDetailsPage = ({
const [selectedPolicy, setSelectedPolicy] = useState(
null
);
- const [softwareInstallUuid, setSoftwareInstallUuid] = useState("");
+ const [
+ packageInstallDetails,
+ setPackageInstallDetails,
+ ] = useState(null);
const [
appInstallDetails,
setAppInstallDetails,
@@ -577,7 +583,7 @@ const HostDetailsPage = ({
setScriptDetailsId(details?.script_execution_id || "");
break;
case "installed_software":
- setSoftwareInstallUuid(details?.install_uuid || "");
+ setPackageInstallDetails({ ...details });
break;
case "installed_app_store_app":
setAppInstallDetails({ ...details });
@@ -618,7 +624,7 @@ const HostDetailsPage = ({
}, [refetchPastActivities, refetchUpcomingActivities]);
const onCancelSoftwareInstallDetailsModal = useCallback(() => {
- setSoftwareInstallUuid("");
+ setPackageInstallDetails(null);
}, []);
const onCancelAppInstallDetailsModal = useCallback(() => {
@@ -1026,9 +1032,9 @@ const HostDetailsPage = ({
onCancel={onCancelScriptDetailsModal}
/>
)}
- {!!softwareInstallUuid && (
+ {!!packageInstallDetails && (
)}
diff --git a/frontend/pages/hosts/details/cards/Software/SoftwareDetailsModal/SoftwareDetailsModal.tsx b/frontend/pages/hosts/details/cards/Software/SoftwareDetailsModal/SoftwareDetailsModal.tsx
index f8373d8c14..2fff0a4fb4 100644
--- a/frontend/pages/hosts/details/cards/Software/SoftwareDetailsModal/SoftwareDetailsModal.tsx
+++ b/frontend/pages/hosts/details/cards/Software/SoftwareDetailsModal/SoftwareDetailsModal.tsx
@@ -149,7 +149,8 @@ const InstallDetailsContent = ({
} else if (hasHostSoftwarePackageLastInstall(software)) {
return (
);
}
From ff623f9875d3118c3f43efb8ad63c57314c18eee Mon Sep 17 00:00:00 2001
From: allenhouchins <32207388+allenhouchins@users.noreply.github.com>
Date: Wed, 31 Jul 2024 11:18:36 -0500
Subject: [PATCH 02/88] Shortened up the Event lead follow-up section (#20883)
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [ ] 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.
- [ ] 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 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`).
- [ ] Manual QA for all new/changed functionality
- For Orbit and Fleet Desktop changes:
- [ ] Orbit runs on macOS, Linux and Windows. Check if the orbit
feature/bugfix should only apply to one platform (`runtime.GOOS`).
- [ ] Manual QA must be performed in the three main OSs, macOS, Windows
and Linux.
- [ ] Auto-update manual QA, from released version of component to new
version (see [tools/tuf/test](../tools/tuf/test/README.md)).
---
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 8d8a7c1f60..0c3eed5af7 100644
--- a/handbook/company/communications.md
+++ b/handbook/company/communications.md
@@ -133,7 +133,7 @@ It's important for Fleet to engage at [events](https://docs.google.com/spreadshe
#### Event lead follow-up
-Eventgoers are expecting a timely follow-up from Fleet based on the conversations that they had at the event. It is up to Digital Marketing Manager to make sure this process is followed.
+Eventgoers expect a timely follow-up from Fleet based on the conversations that they had at the event.
1. Once a list of badge scans is available, Fleeties that attended the event are to add any follow up notes that note buying situation, amount of endpoints, level of interest, and general talking points.
2. Within 3 business days of returning from the event, attendees will set up a debrief meeting with the demand team to discuss follow-up.
From a33bd6527249eb40e2033b68b6086a58d7eeaf34 Mon Sep 17 00:00:00 2001
From: Sarah Gillespie <73313222+gillespi314@users.noreply.github.com>
Date: Wed, 31 Jul 2024 11:34:01 -0500
Subject: [PATCH 03/88] Revert "Add minimum os version requirements to DEP
enrollment flow" (#20878)
---
changes/19674-dep-min-os-version | 1 -
server/datastore/mysql/apple_mdm.go | 58 ------
server/datastore/mysql/apple_mdm_test.go | 172 ------------------
server/fleet/apple_mdm.go | 50 ------
server/fleet/datastore.go | 4 -
server/fleet/service.go | 3 -
server/mdm/apple/AppleIncRootCertificate.cer | Bin 1215 -> 0 bytes
server/mdm/apple/deviceinfo.go | 179 -------------------
server/mock/datastore_mock.go | 12 --
server/service/apple_mdm.go | 110 ------------
server/service/apple_mdm_test.go | 78 --------
11 files changed, 667 deletions(-)
delete mode 100644 changes/19674-dep-min-os-version
delete mode 100644 server/mdm/apple/AppleIncRootCertificate.cer
delete mode 100644 server/mdm/apple/deviceinfo.go
diff --git a/changes/19674-dep-min-os-version b/changes/19674-dep-min-os-version
deleted file mode 100644
index b9adefe9ec..0000000000
--- a/changes/19674-dep-min-os-version
+++ /dev/null
@@ -1 +0,0 @@
-- Updated MDM features to enforce minimum OS version settings during Apple Automated Device Enrollment (ADE).
diff --git a/server/datastore/mysql/apple_mdm.go b/server/datastore/mysql/apple_mdm.go
index 0bb49993b6..a0e95f7d75 100644
--- a/server/datastore/mysql/apple_mdm.go
+++ b/server/datastore/mysql/apple_mdm.go
@@ -4603,61 +4603,3 @@ LIMIT 500
return deviceUUIDs, nil
}
-
-func (ds *Datastore) GetMDMAppleOSUpdatesSettingsByHostSerial(ctx context.Context, serial string) (*fleet.AppleOSUpdateSettings, error) {
- stmt := `
-SELECT
- team_id, platform
-FROM
- hosts h
-JOIN
- host_dep_assignments hdep ON h.id = host_id
-WHERE
- hardware_serial = ? AND deleted_at IS NULL
-LIMIT 1`
-
- var dest struct {
- TeamID *uint `db:"team_id"`
- Platform string `db:"platform"`
- }
- if err := sqlx.GetContext(ctx, ds.reader(ctx), &dest, stmt, serial); err != nil {
- return nil, ctxerr.Wrap(ctx, err, "getting team id for host")
- }
-
- var settings fleet.AppleOSUpdateSettings
- if dest.TeamID == nil {
- // use the global settings
- ac, err := ds.AppConfig(ctx)
- if err != nil {
- return nil, ctxerr.Wrap(ctx, err, "getting app config for os update settings")
- }
- switch dest.Platform {
- case "ios":
- settings = ac.MDM.IOSUpdates
- case "ipados":
- settings = ac.MDM.IPadOSUpdates
- case "darwin":
- settings = ac.MDM.MacOSUpdates
- default:
- return nil, ctxerr.New(ctx, fmt.Sprintf("unsupported platform %s", dest.Platform))
- }
- } else {
- // use the team settings
- tm, err := ds.TeamWithoutExtras(ctx, *dest.TeamID)
- if err != nil {
- return nil, ctxerr.Wrap(ctx, err, "getting team os update settings")
- }
- switch dest.Platform {
- case "ios":
- settings = tm.Config.MDM.IOSUpdates
- case "ipados":
- settings = tm.Config.MDM.IPadOSUpdates
- case "darwin":
- settings = tm.Config.MDM.MacOSUpdates
- default:
- return nil, ctxerr.New(ctx, fmt.Sprintf("unsupported platform %s", dest.Platform))
- }
- }
-
- return &settings, nil
-}
diff --git a/server/datastore/mysql/apple_mdm_test.go b/server/datastore/mysql/apple_mdm_test.go
index 53c8423809..35a8a87497 100644
--- a/server/datastore/mysql/apple_mdm_test.go
+++ b/server/datastore/mysql/apple_mdm_test.go
@@ -6001,7 +6001,6 @@ func testGetHostUUIDsWithPendingMDMAppleCommands(t *testing.T, ds *Datastore) {
require.NoError(t, err)
require.ElementsMatch(t, []string{hosts[1].UUID, hosts[2].UUID}, uuids)
}
-
func testHostDetailsMDMProfilesIOSIPadOS(t *testing.T, ds *Datastore) {
ctx := context.Background()
@@ -6144,174 +6143,3 @@ func testHostDetailsMDMProfilesIOSIPadOS(t *testing.T, ds *Datastore) {
require.Equal(t, fleet.MDMDeliveryVerified, *gotProfs[0].Status)
}
}
-
-func TestGetMDMAppleOSUpdatesSettingsByHostSerial(t *testing.T) {
- ds := CreateMySQLDS(t)
- defer ds.Close()
-
- keys := []string{"ios", "ipados", "macos"}
- devicesByKey := map[string]godep.Device{
- "ios": {SerialNumber: "dep-serial-ios-updates", DeviceFamily: "iPhone"},
- "ipados": {SerialNumber: "dep-serial-ipados-updates", DeviceFamily: "iPad"},
- "macos": {SerialNumber: "dep-serial-macos-updates", DeviceFamily: "Mac"},
- }
-
- getConfigSettings := func(teamID uint, key string) *fleet.AppleOSUpdateSettings {
- var settings fleet.AppleOSUpdateSettings
- ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
- stmt := fmt.Sprintf(`SELECT json_value->'$.mdm.%s_updates' FROM app_config_json`, key)
- if teamID > 0 {
- stmt = fmt.Sprintf(`SELECT config->'$.mdm.%s_updates' FROM teams WHERE id = %d`, key, teamID)
- }
- var raw json.RawMessage
- if err := sqlx.GetContext(context.Background(), q, &raw, stmt); err != nil {
- return err
- }
- if err := json.Unmarshal(raw, &settings); err != nil {
- return err
- }
- return nil
- })
- return &settings
- }
-
- setConfigSettings := func(teamID uint, key string, minVersion string) {
- var mv *string
- if minVersion != "" {
- mv = &minVersion
- }
- ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
- stmt := fmt.Sprintf(`UPDATE app_config_json SET json_value = JSON_SET(json_value, '$.mdm.%s_updates.minimum_version', ?)`, key)
- if teamID > 0 {
- stmt = fmt.Sprintf(`UPDATE teams SET config = JSON_SET(config, '$.mdm.%s_updates.minimum_version', ?) WHERE id = %d`, key, teamID)
- }
- if _, err := q.ExecContext(context.Background(), stmt, mv); err != nil {
- return err
- }
- return nil
- })
- }
-
- checkExpectedVersion := func(t *testing.T, gotSettings *fleet.AppleOSUpdateSettings, expectedVersion string) {
- if expectedVersion == "" {
- require.True(t, gotSettings.MinimumVersion.Set)
- require.False(t, gotSettings.MinimumVersion.Valid)
- require.Empty(t, gotSettings.MinimumVersion.Value)
- } else {
- require.True(t, gotSettings.MinimumVersion.Set)
- require.True(t, gotSettings.MinimumVersion.Valid)
- require.Equal(t, expectedVersion, gotSettings.MinimumVersion.Value)
- }
- }
-
- checkDevice := func(t *testing.T, teamID uint, key string, wantVersion string) {
- checkExpectedVersion(t, getConfigSettings(teamID, key), wantVersion)
- gotSettings, err := ds.GetMDMAppleOSUpdatesSettingsByHostSerial(context.Background(), devicesByKey[key].SerialNumber)
- require.NoError(t, err)
- checkExpectedVersion(t, gotSettings, wantVersion)
- }
-
- // empty global settings to start
- for _, key := range keys {
- checkExpectedVersion(t, getConfigSettings(0, key), "")
- }
-
- // ingest some test devices
- n, _, err := ds.IngestMDMAppleDevicesFromDEPSync(context.Background(), []godep.Device{devicesByKey["ios"], devicesByKey["ipados"], devicesByKey["macos"]})
- require.NoError(t, err)
- require.Equal(t, int64(3), n)
- hostIDsByKey := map[string]uint{}
- for key, device := range devicesByKey {
- ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
- var hid uint
- err = sqlx.GetContext(context.Background(), q, &hid, "SELECT id FROM hosts WHERE hardware_serial = ?", device.SerialNumber)
- require.NoError(t, err)
- hostIDsByKey[key] = hid
- return nil
- })
- }
-
- // not set in global config, so devics should return empty
- checkDevice(t, 0, "ios", "")
- checkDevice(t, 0, "ipados", "")
- checkDevice(t, 0, "macos", "")
-
- // set the minimum version for ios
- setConfigSettings(0, "ios", "17.1")
- checkDevice(t, 0, "ios", "17.1")
- checkDevice(t, 0, "ipados", "") // no change
- checkDevice(t, 0, "macos", "") // no change
-
- // set the minimum version for ipados
- setConfigSettings(0, "ipados", "17.2")
- checkDevice(t, 0, "ios", "17.1") // no change
- checkDevice(t, 0, "ipados", "17.2")
- checkDevice(t, 0, "macos", "") // no change
-
- // set the minimum version for macos
- setConfigSettings(0, "macos", "14.5")
- checkDevice(t, 0, "ios", "17.1") // no change
- checkDevice(t, 0, "ipados", "17.2") // no change
- checkDevice(t, 0, "macos", "14.5")
-
- // create a team
- team, err := ds.NewTeam(context.Background(), &fleet.Team{Name: "team1"})
- require.NoError(t, err)
-
- // empty team settings to start
- for _, key := range keys {
- checkExpectedVersion(t, getConfigSettings(team.ID, key), "")
- }
-
- // transfer ios and ipados to the team
- err = ds.AddHostsToTeam(context.Background(), &team.ID, []uint{hostIDsByKey["ios"], hostIDsByKey["ipados"]})
- require.NoError(t, err)
-
- checkDevice(t, team.ID, "ios", "") // team settings are empty to start
- checkDevice(t, team.ID, "ipados", "") // team settings are empty to start
- checkDevice(t, 0, "macos", "14.5") // no change, still global
-
- setConfigSettings(team.ID, "ios", "17.3")
- checkDevice(t, team.ID, "ios", "17.3") // team settings are set for ios
- checkDevice(t, team.ID, "ipados", "") // team settings are empty for ipados
- checkDevice(t, 0, "macos", "14.5") // no change, still global
-
- setConfigSettings(team.ID, "ipados", "17.4")
- checkDevice(t, team.ID, "ios", "17.3") // no change in team settings for ios
- checkDevice(t, team.ID, "ipados", "17.4") // team settings are set for ipados
- checkDevice(t, 0, "macos", "14.5") // no change, still global
-
- // transfer macos to the team
- err = ds.AddHostsToTeam(context.Background(), &team.ID, []uint{hostIDsByKey["macos"]})
- require.NoError(t, err)
- checkDevice(t, team.ID, "macos", "") // team settings are empty for macos
-
- setConfigSettings(team.ID, "macos", "14.6")
- checkDevice(t, team.ID, "macos", "14.6") // team settings are set for macos
-
- // create a non-DEP host
- _, err = ds.NewHost(context.Background(), &fleet.Host{
- DetailUpdatedAt: time.Now(),
- LabelUpdatedAt: time.Now(),
- PolicyUpdatedAt: time.Now(),
- SeenTime: time.Now(),
- OsqueryHostID: ptr.String("non-dep-osquery-id"),
- NodeKey: ptr.String("non-dep-node-key"),
- UUID: "non-dep-uuid",
- Hostname: "non-dep-hostname",
- Platform: "macos",
- HardwareSerial: "non-dep-serial",
- })
-
- // non-DEP host should return not found
- _, err = ds.GetMDMAppleOSUpdatesSettingsByHostSerial(context.Background(), "non-dep-serial")
- require.ErrorIs(t, err, sql.ErrNoRows)
-
- // deleted DEP host should return not found
- ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
- _, err := q.ExecContext(context.Background(), "UPDATE host_dep_assignments SET deleted_at = NOW() WHERE host_id = ?", hostIDsByKey["macos"])
- return err
- })
- _, err = ds.GetMDMAppleOSUpdatesSettingsByHostSerial(context.Background(), devicesByKey["macos"].SerialNumber)
- require.ErrorIs(t, err, sql.ErrNoRows)
-}
diff --git a/server/fleet/apple_mdm.go b/server/fleet/apple_mdm.go
index b86434ac8e..e1cabf2533 100644
--- a/server/fleet/apple_mdm.go
+++ b/server/fleet/apple_mdm.go
@@ -841,53 +841,3 @@ type MDMAppleDDMActivation struct {
ServerToken string `json:"ServerToken"`
Type string `json:"Type"` // "com.apple.activation.simple"
}
-
-// MDMAppleMachineInfo is a [device's information][1] sent as part of an MDM enrollment profile request
-//
-// [1]: https://developer.apple.com/documentation/devicemanagement/machineinfo
-type MDMAppleMachineInfo struct {
- IMEI string `plist:"IMEI,omitempty"`
- Language string `plist:"LANGUAGE,omitempty"`
- MDMCanRequestSoftwareUpdate bool `plist:"MDM_CAN_REQUEST_SOFTWARE_UPDATE"`
- MEID string `plist:"MEID,omitempty"`
- OSVersion string `plist:"OS_VERSION"`
- PairingToken string `plist:"PAIRING_TOKEN,omitempty"`
- Product string `plist:"PRODUCT"`
- Serial string `plist:"SERIAL"`
- SoftwareUpdateDeviceID string `plist:"SOFTWARE_UPDATE_DEVICE_ID,omitempty"`
- SupplementalBuildVersion string `plist:"SUPPLEMENTAL_BUILD_VERSION,omitempty"`
- SupplementalOSVersionExtra string `plist:"SUPPLEMENTAL_OS_VERSION_EXTRA,omitempty"`
- UDID string `plist:"UDID"`
- Version string `plist:"VERSION"`
-}
-
-// MDMAppleSoftwareUpdateRequiredCode is the [code][1] specified by Apple to indicate that the device
-// needs to perform a software update before enrollment and setup can proceed.
-//
-// [1]: https://developer.apple.com/documentation/devicemanagement/errorcodesoftwareupdaterequired
-const MDMAppleSoftwareUpdateRequiredCode = "com.apple.softwareupdate.required"
-
-// MDMAppleSoftwareUpdateRequiredDetails is the [details][1] specified by Apple for the
-// required software update.
-//
-// [1]: https://developer.apple.com/documentation/devicemanagement/errorcodesoftwareupdaterequired/details
-type MDMAppleSoftwareUpdateRequiredDetails struct {
- OSVersion string `json:"OSVersion"`
- BuildVersion string `json:"BuildVersion"`
-}
-
-// MDMAppleSoftwareUpdateRequired is the [error response][1] specified by Apple to indicate that the device
-// needs to perform a software update before enrollment and setup can proceed.
-//
-// [1]: https://developer.apple.com/documentation/devicemanagement/errorcodesoftwareupdaterequired
-type MDMAppleSoftwareUpdateRequired struct {
- Code string `json:"code"` // "com.apple.softwareupdate.required"
- Details MDMAppleSoftwareUpdateRequiredDetails `json:"details"`
-}
-
-func NewMDMAppleSoftwareUpdateRequired(settings AppleOSUpdateSettings) *MDMAppleSoftwareUpdateRequired {
- return &MDMAppleSoftwareUpdateRequired{
- Code: MDMAppleSoftwareUpdateRequiredCode,
- Details: MDMAppleSoftwareUpdateRequiredDetails{OSVersion: settings.MinimumVersion.Value},
- }
-}
diff --git a/server/fleet/datastore.go b/server/fleet/datastore.go
index cb85d7782e..a043eba04e 100644
--- a/server/fleet/datastore.go
+++ b/server/fleet/datastore.go
@@ -1288,10 +1288,6 @@ type Datastore interface {
// the provided value.
MDMAppleSetPendingDeclarationsAs(ctx context.Context, hostUUID string, status *MDMDeliveryStatus, detail string) error
- // GetMDMAppleOSUpdatesSettingsByHostSerial returns applicable Apple OS update settings (if any)
- // for the host with the given serial number. The host must be DEP assigned to Fleet.
- GetMDMAppleOSUpdatesSettingsByHostSerial(ctx context.Context, hostSerial string) (*AppleOSUpdateSettings, error)
-
// InsertMDMConfigAssets inserts MDM related config assets, such as SCEP and APNS certs and keys.
InsertMDMConfigAssets(ctx context.Context, assets []MDMConfigAsset) error
diff --git a/server/fleet/service.go b/server/fleet/service.go
index be8cf36adc..5375b3d4bd 100644
--- a/server/fleet/service.go
+++ b/server/fleet/service.go
@@ -918,9 +918,6 @@ type Service interface {
GetMDMManualEnrollmentProfile(ctx context.Context) ([]byte, error)
- // CheckMDMAppleEnrollmentWithMinimumOSVersion checks if the minimum OS version is met for a MDM enrollment
- CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx context.Context, m *MDMAppleMachineInfo) (*MDMAppleSoftwareUpdateRequired, error)
-
///////////////////////////////////////////////////////////////////////////////
// CronSchedulesService
diff --git a/server/mdm/apple/AppleIncRootCertificate.cer b/server/mdm/apple/AppleIncRootCertificate.cer
deleted file mode 100644
index 8a9ff247419dd22a07c837ba7394aeabdd0f14e2..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 1215
zcmXqLV%crb#JqR`GZP~d5E<~YacZ@Bw0-AgWMpM!Fi0}wHsEAq4rO5zW(o~96gCh9
zakzxJ9199^QWZS&lJyML3{*gZ+`_UDLFd$>lFYQsBg2!4D>>yS-j;I@c+L7YuChh5U7`KHvAiEsOqE5wMI&W5Px=0B&b;#hyADPKr1x`dQTTp(jgCTo
z!8UtFgP!fq=lSQ_e%AKXkUH`2+}53ZH{)ckownU-we|}?AHyW>jf!G=C0A{DZzqYZ
zUR*fIJvj8>dVR;uKYl+hIQwj|k87R0Pjj_t->jT;Rj-bAq&^<-@B
zm%W!-{69S|b&uzbviZg$sSC@eoYZAvW@KPo+{9P~43RPeK43h`@-s62XJG-R8#V)e
z5MLO?XEk63QUIB4HrbfL%co
zBPgB8DzG#$asX{)0b&Md!c0zKWi)8~WT3^yq0I(NqwGwKVsaTJB?ZM+`ugSN<$8&r
zl&P1TpQ{gMB`4||G#-X4W-@5pCe^q(C^aWDF)uk)0hmHdGBS%5lHrLqRUxTTAu+E~
zp&+rS1js5bF3n9XR!B@vPAw>b=t%?WNd@6N1&|%Uq@D!K48=g%l*FPGg_6{wT%d-$
z6ouscyp&8(HYirePg5u@PSruNs30Gx7i1YwCER{crYR^&OfJa;IuB@ONosCtUP-YY
za{2^jO+DWQZ}F>7!e*{cX?eJDxY@8r;
zW8atJ+3zl;@Sm>qH@UIM?q|jS>=W#7YAu_)gB31Y9ND;kmOoeaf9*e!%UL;V#2vx}
zUUwk!R<>!CBEM2a=7;zgw~E
zguTASugG_6SFxo3)|+Pa2irq$E}yy6$m#cutA+FG76xsX-aFYzMMzw9>OIdRD+
Xyc@&=R&`yy_2kb5PImJRrKO4hMS!&;
diff --git a/server/mdm/apple/deviceinfo.go b/server/mdm/apple/deviceinfo.go
deleted file mode 100644
index ebed273690..0000000000
--- a/server/mdm/apple/deviceinfo.go
+++ /dev/null
@@ -1,179 +0,0 @@
-// The contents of this file have been copied and modified pursuant to the following
-// license from the original source:
-// https://github.com/korylprince/dep-webview-oidc/blob/2dd846a54fed04c16dd227b8c6c31665b4d0ebd8/header/header.go
-//
-// MIT License
-//
-// Copyright (c) 2023 Kory Prince
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
-
-package apple_mdm
-
-import (
- "bytes"
- "crypto"
- "crypto/rsa"
- "crypto/sha1" // nolint:gosec // See comments regarding Apple's Root CA below
- "crypto/x509"
- _ "embed"
- "encoding/base64"
- "errors"
- "fmt"
-
- "github.com/groob/plist"
- "go.mozilla.org/pkcs7"
-)
-
-const DeviceInfoHeader = "x-apple-aspen-deviceinfo"
-
-// appleRootCert is https://www.apple.com/appleca/AppleIncRootCertificate.cer
-//
-//go:embed AppleIncRootCertificate.cer
-var appleRootCert []byte
-
-func newAppleRootCert() *x509.Certificate {
- cert, err := x509.ParseCertificate(appleRootCert)
- if err != nil {
- panic(fmt.Errorf("could not parse cert: %w", err))
- }
- return cert
-}
-
-// appleRootCA is Apple's Root CA parsed to an *x509.Certificate
-var appleRootCA = newAppleRootCert()
-
-// MachineInfo is a [device's information] sent as part of an MDM enrollment profile request
-//
-// [device's information]: https://developer.apple.com/documentation/devicemanagement/machineinfo
-type MachineInfo struct {
- IMEI string `plist:"IMEI,omitempty"`
- Language string `plist:"LANGUAGE,omitempty"`
- MDMCanRequestSoftwareUpdate bool `plist:"MDM_CAN_REQUEST_SOFTWARE_UPDATE"`
- MEID string `plist:"MEID,omitempty"`
- OSVersion string `plist:"OS_VERSION"`
- PairingToken string `plist:"PAIRING_TOKEN,omitempty"`
- Product string `plist:"PRODUCT"`
- Serial string `plist:"SERIAL"`
- SoftwareUpdateDeviceID string `plist:"SOFTWARE_UPDATE_DEVICE_ID,omitempty"`
- SupplementalBuildVersion string `plist:"SUPPLEMENTAL_BUILD_VERSION,omitempty"`
- SupplementalOSVersionExtra string `plist:"SUPPLEMENTAL_OS_VERSION_EXTRA,omitempty"`
- UDID string `plist:"UDID"`
- Version string `plist:"VERSION"`
-}
-
-// verifyPKCS7SHA1RSA performs a manual SHA1withRSA verification, since it's deprecated in Go 1.18.
-// If verifyChain is true, the signer certificate and its chain of certificates is verified against Apple's Root CA.
-// Also note that the certificate validity time window of the signing cert is not checked, since the cert is expired.
-// This follows guidance from Apple on the expired certificate.
-func verifyPKCS7SHA1RSA(p7 *pkcs7.PKCS7, verifyChain bool) error {
- if len(p7.Signers) == 0 {
- return errors.New("not signed")
- }
-
- // get signing cert
- issuer := p7.Signers[0].IssuerAndSerialNumber
- var signer *x509.Certificate
- for _, cert := range p7.Certificates {
- if bytes.Equal(cert.RawIssuer, issuer.IssuerName.FullBytes) && cert.SerialNumber.Cmp(issuer.SerialNumber) == 0 {
- signer = cert
- }
- }
-
- // get sha1 hash of content
- hashed := sha1.Sum(p7.Content) // nolint:gosec
-
- // verify content signature
- signature := p7.Signers[0].EncryptedDigest
- if err := rsa.VerifyPKCS1v15(signer.PublicKey.(*rsa.PublicKey), crypto.SHA1, hashed[:], signature); err != nil {
- return fmt.Errorf("signature could not be verified: %w", err)
- }
-
- if !verifyChain {
- return nil
- }
-
- // verify chain from signer to root
- cert := signer
-outer:
- for {
- // check if cert is signed by root
- if bytes.Equal(cert.RawIssuer, appleRootCA.RawSubject) {
- hashed := sha1.Sum(cert.RawTBSCertificate) // nolint:gosec
- // check signature
- if err := rsa.VerifyPKCS1v15(appleRootCA.PublicKey.(*rsa.PublicKey), crypto.SHA1, hashed[:], cert.Signature); err != nil {
- return fmt.Errorf("could not verify root CA signature: %w", err)
- }
- return nil
- }
- for _, c := range p7.Certificates {
- if cert == c {
- continue
- }
- // check if cert is signed by intermediate cert in chain
- if bytes.Equal(cert.RawIssuer, c.RawSubject) {
- // check signature
- hashed := sha1.Sum(cert.RawTBSCertificate) // nolint:gosec
- if err := rsa.VerifyPKCS1v15(c.PublicKey.(*rsa.PublicKey), crypto.SHA1, hashed[:], cert.Signature); err != nil {
- return fmt.Errorf("could not verify chained certificate signature: %w", err)
- }
- cert = c
- continue outer
- }
- }
- return errors.New("certificate root not found")
- }
-}
-
-// ParseDeviceinfo attempts to parse the provided string, assuming it to be the base64-encoded value
-// of an x-apple-aspen-deviceinfo header. If successful, it returns the parsed *MachineInfo. If the
-// verify parameter is specified as true, the signature is also verified against Apple's Root CA and
-// an error will be returned if the signature is invalid.
-//
-// Warning: The information in this header, despite being signed by Apple PKI, shouldn't be trusted
-// for device attestation or other security purposes. See the related [documentation] and referenced
-// [article] for more information.
-//
-// [documentation]: https://github.com/korylprince/dep-webview-oidc/blob/2dd846a54fed04c16dd227b8c6c31665b4d0ebd8/docs/Architecture.md#x-apple-aspen-deviceinfo-header
-// [article]: https://duo.com/labs/research/mdm-me-maybe
-func ParseDeviceinfo(b64 string, verify bool) (*MachineInfo, error) {
- buf, err := base64.StdEncoding.DecodeString(b64)
- if err != nil {
- return nil, fmt.Errorf("could not decode base64: %w", err)
- }
-
- p7, err := pkcs7.Parse(buf)
- if err != nil {
- return nil, fmt.Errorf("could not decode pkcs7: %w", err)
- }
-
- // verify signature and certificate chain
- if verify {
- if err = verifyPKCS7SHA1RSA(p7, verify); err != nil {
- return nil, fmt.Errorf("could not verify signature: %w", err)
- }
- }
-
- info := new(MachineInfo)
- if err = plist.Unmarshal(p7.Content, info); err != nil {
- return nil, fmt.Errorf("could not decode plist: %w", err)
- }
-
- return info, nil
-}
diff --git a/server/mock/datastore_mock.go b/server/mock/datastore_mock.go
index fdbd7a6238..3fdb56d30b 100644
--- a/server/mock/datastore_mock.go
+++ b/server/mock/datastore_mock.go
@@ -848,8 +848,6 @@ type MDMAppleStoreDDMStatusReportFunc func(ctx context.Context, hostUUID string,
type MDMAppleSetPendingDeclarationsAsFunc func(ctx context.Context, hostUUID string, status *fleet.MDMDeliveryStatus, detail string) error
-type GetMDMAppleOSUpdatesSettingsByHostSerialFunc func(ctx context.Context, hostSerial string) (*fleet.AppleOSUpdateSettings, error)
-
type InsertMDMConfigAssetsFunc func(ctx context.Context, assets []fleet.MDMConfigAsset) error
type GetAllMDMConfigAssetsByNameFunc func(ctx context.Context, assetNames []fleet.MDMAssetName) (map[fleet.MDMAssetName]fleet.MDMConfigAsset, error)
@@ -2257,9 +2255,6 @@ type DataStore struct {
MDMAppleSetPendingDeclarationsAsFunc MDMAppleSetPendingDeclarationsAsFunc
MDMAppleSetPendingDeclarationsAsFuncInvoked bool
- GetMDMAppleOSUpdatesSettingsByHostSerialFunc GetMDMAppleOSUpdatesSettingsByHostSerialFunc
- GetMDMAppleOSUpdatesSettingsByHostSerialFuncInvoked bool
-
InsertMDMConfigAssetsFunc InsertMDMConfigAssetsFunc
InsertMDMConfigAssetsFuncInvoked bool
@@ -5407,13 +5402,6 @@ func (s *DataStore) MDMAppleSetPendingDeclarationsAs(ctx context.Context, hostUU
return s.MDMAppleSetPendingDeclarationsAsFunc(ctx, hostUUID, status, detail)
}
-func (s *DataStore) GetMDMAppleOSUpdatesSettingsByHostSerial(ctx context.Context, hostSerial string) (*fleet.AppleOSUpdateSettings, error) {
- s.mu.Lock()
- s.GetMDMAppleOSUpdatesSettingsByHostSerialFuncInvoked = true
- s.mu.Unlock()
- return s.GetMDMAppleOSUpdatesSettingsByHostSerialFunc(ctx, hostSerial)
-}
-
func (s *DataStore) InsertMDMConfigAssets(ctx context.Context, assets []fleet.MDMConfigAsset) error {
s.mu.Lock()
s.InsertMDMConfigAssetsFuncInvoked = true
diff --git a/server/service/apple_mdm.go b/server/service/apple_mdm.go
index 2118b13670..3f1ef747a3 100644
--- a/server/service/apple_mdm.go
+++ b/server/service/apple_mdm.go
@@ -18,7 +18,6 @@ import (
"sync"
"time"
- "github.com/Masterminds/semver"
"github.com/docker/go-units"
"github.com/fleetdm/fleet/v4/pkg/file"
"github.com/fleetdm/fleet/v4/pkg/optjson"
@@ -1288,38 +1287,6 @@ func (svc *Service) EnqueueMDMAppleCommand(
type mdmAppleEnrollRequest struct {
Token string `query:"token"`
EnrollmentReference string `query:"enrollment_reference,optional"`
- MachineInfo *fleet.MDMAppleMachineInfo
-}
-
-func (mdmAppleEnrollRequest) DecodeRequest(ctx context.Context, r *http.Request) (interface{}, error) {
- decoded := mdmAppleEnrollRequest{}
-
- tok := r.URL.Query().Get("token")
- if tok == "" {
- return nil, &fleet.BadRequestError{
- Message: "token is required",
- }
- }
- decoded.Token = tok
-
- er := r.URL.Query().Get("enrollment_reference")
- decoded.EnrollmentReference = er
-
- // Parse the machine info from the request body
- di := r.Header.Get("x-apple-aspen-deviceinfo")
- if di != "" {
- // extract x-apple-aspen-deviceinfo custom header from request
- parsed, err := apple_mdm.ParseDeviceinfo(di, true)
- if err != nil {
- return nil, &fleet.BadRequestError{
- Message: "unable to parse deviceinfo header",
- }
- }
- p := fleet.MDMAppleMachineInfo(*parsed)
- decoded.MachineInfo = &p
- }
-
- return &decoded, nil
}
func (r mdmAppleEnrollResponse) error() error { return r.Err }
@@ -1329,20 +1296,9 @@ type mdmAppleEnrollResponse struct {
// Profile field is used in hijackRender for the response.
Profile []byte
-
- SoftwareUpdateRequired *fleet.MDMAppleSoftwareUpdateRequired
}
func (r mdmAppleEnrollResponse) hijackRender(ctx context.Context, w http.ResponseWriter) {
- if r.SoftwareUpdateRequired != nil {
- w.Header().Set("Content-Type", "application/json")
- w.WriteHeader(http.StatusForbidden)
- if err := json.NewEncoder(w).Encode(r.SoftwareUpdateRequired); err != nil {
- encodeError(ctx, ctxerr.New(ctx, "failed to encode software update required"), w)
- }
- return
- }
-
w.Header().Set("Content-Length", strconv.FormatInt(int64(len(r.Profile)), 10))
w.Header().Set("Content-Type", "application/x-apple-aspen-config")
w.Header().Set("X-Content-Type-Options", "nosniff")
@@ -1360,16 +1316,6 @@ func (r mdmAppleEnrollResponse) hijackRender(ctx context.Context, w http.Respons
func mdmAppleEnrollEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
req := request.(*mdmAppleEnrollRequest)
- sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, req.MachineInfo)
- if err != nil {
- return mdmAppleEnrollResponse{Err: err}, nil
- }
- if sur != nil {
- return mdmAppleEnrollResponse{
- SoftwareUpdateRequired: sur,
- }, nil
- }
-
profile, err := svc.GetMDMAppleEnrollmentProfileByToken(ctx, req.Token, req.EnrollmentReference)
if err != nil {
return mdmAppleEnrollResponse{Err: err}, nil
@@ -1430,62 +1376,6 @@ func (svc *Service) GetMDMAppleEnrollmentProfileByToken(ctx context.Context, tok
return signed, nil
}
-func (svc *Service) CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx context.Context, m *fleet.MDMAppleMachineInfo) (*fleet.MDMAppleSoftwareUpdateRequired, error) {
- // skipauth: The enroll profile endpoint is unauthenticated.
- svc.authz.SkipAuthorization(ctx)
-
- if m == nil {
- level.Info(svc.logger).Log("msg", "no machine info, skipping os version check")
- return nil, nil
- }
-
- if !m.MDMCanRequestSoftwareUpdate {
- level.Info(svc.logger).Log("msg", "mdm cannot request software update, skipping os version check", "machine_info", *m)
- return nil, nil
- }
-
- // NOTE: Under the hood, the datastore is joining host_dep_assignments to the hosts table to
- // look up DEP hosts by serial number. It grabs the team id and platform from the
- // hosts table. Then it uses the team id to get either the global config or team config.
- // Finally, it uses the platform to get os updates settings from the config for
- // one of ios, ipados, or darwin, as applicable. There's a lot of assumptions going on here, not
- // least of which is that the platform is correct in the hosts table. If the platform is wrong,
- // we'll end up with a meaningless comparison of unrelated versions. We could potentially add
- // some cross-check against the machine info to ensure that the platform of the host aligns with
- // what we expect from the machine info. But that would involve work to derive the platform from
- // the machine info (presumably from the product name, but that's not a 1:1 mapping).
- settings, err := svc.ds.GetMDMAppleOSUpdatesSettingsByHostSerial(ctx, m.Serial)
- if err != nil {
- if fleet.IsNotFound(err) {
- level.Info(svc.logger).Log("msg", "settings not found, skipping os version check", "machine_info", *m)
- return nil, nil
- }
- return nil, ctxerr.Wrap(ctx, err, "get os updates settings")
- }
-
- // TODO: confirm what this check should do
- if !settings.MinimumVersion.Set || !settings.MinimumVersion.Valid || settings.MinimumVersion.Value == "" {
- level.Info(svc.logger).Log("msg", "settings not set, skipping os version check", "machine_info", *m, "settings", settings)
- return nil, nil
- }
-
- want, err := semver.NewVersion(settings.MinimumVersion.Value)
- if err != nil {
- return nil, ctxerr.Wrap(ctx, err, "parsing minimum version")
- }
-
- got, err := semver.NewVersion(m.OSVersion)
- if err != nil {
- return nil, ctxerr.Wrap(ctx, err, "parsing device os version")
- }
-
- if got.LessThan(want) {
- return fleet.NewMDMAppleSoftwareUpdateRequired(*settings), nil
- }
-
- return nil, nil
-}
-
func (svc *Service) mdmPushCertTopic(ctx context.Context) (string, error) {
assets, err := svc.ds.GetAllMDMConfigAssetsByName(ctx, []fleet.MDMAssetName{
fleet.MDMAssetAPNSCert,
diff --git a/server/service/apple_mdm_test.go b/server/service/apple_mdm_test.go
index 4c959a7649..545de31a0a 100644
--- a/server/service/apple_mdm_test.go
+++ b/server/service/apple_mdm_test.go
@@ -24,7 +24,6 @@ import (
"testing"
"time"
- "github.com/fleetdm/fleet/v4/pkg/optjson"
"github.com/fleetdm/fleet/v4/server/authz"
"github.com/fleetdm/fleet/v4/server/config"
"github.com/fleetdm/fleet/v4/server/contexts/license"
@@ -3372,80 +3371,3 @@ func TestUnmarshalAppList(t *testing.T) {
require.NoError(t, err)
assert.ElementsMatch(t, expectedSoftware, software)
}
-
-func TestCheckMDMAppleEnrollmentWithMinimumOSVersion(t *testing.T) {
- svc, ctx, ds := setupAppleMDMService(t, &fleet.LicenseInfo{Tier: fleet.TierPremium})
-
- ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serialNumber string) (*fleet.AppleOSUpdateSettings, error) {
- return &fleet.AppleOSUpdateSettings{
- MinimumVersion: optjson.SetString("14.2"),
- }, nil
- }
-
- testCases := []struct {
- name string
- deviceOSVersion string
- mdmCanRequestSoftwareUpdate bool
- wantUpdateRequired string
- }{
- {
- name: "OS version is greater than minimum",
- deviceOSVersion: "15.0",
- mdmCanRequestSoftwareUpdate: true,
- wantUpdateRequired: "",
- },
- {
- name: "OS version is equal to minimum",
- deviceOSVersion: "14.2",
- mdmCanRequestSoftwareUpdate: true,
- wantUpdateRequired: "",
- },
- {
- name: "OS version is less than minimum",
- deviceOSVersion: "14.0.2",
- mdmCanRequestSoftwareUpdate: true,
- wantUpdateRequired: "14.2",
- },
- {
- name: "OS version is less than minimum but MDM cannot request software update",
- deviceOSVersion: "14.0.2",
- mdmCanRequestSoftwareUpdate: false,
- wantUpdateRequired: "",
- },
- }
-
- for _, tt := range testCases {
- t.Run(tt.name, func(t *testing.T) {
- sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, &fleet.MDMAppleMachineInfo{OSVersion: tt.deviceOSVersion, MDMCanRequestSoftwareUpdate: tt.mdmCanRequestSoftwareUpdate})
- require.NoError(t, err)
- if tt.wantUpdateRequired == "" {
- require.Nil(t, sur)
- } else {
- require.Equal(t, &fleet.MDMAppleSoftwareUpdateRequired{
- Code: fleet.MDMAppleSoftwareUpdateRequiredCode,
- Details: fleet.MDMAppleSoftwareUpdateRequiredDetails{
- OSVersion: tt.wantUpdateRequired,
- },
- }, sur)
- }
- })
- }
-
- t.Run("error getting OS update settings", func(t *testing.T) {
- ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serialNumber string) (*fleet.AppleOSUpdateSettings, error) {
- return nil, newNotFoundError()
- }
-
- sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, &fleet.MDMAppleMachineInfo{OSVersion: "14.0.2", MDMCanRequestSoftwareUpdate: true})
- require.NoError(t, err)
- require.Nil(t, sur)
-
- ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serialNumber string) (*fleet.AppleOSUpdateSettings, error) {
- return nil, errors.New("error")
- }
-
- sur, err = svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, &fleet.MDMAppleMachineInfo{OSVersion: "14.0.2", MDMCanRequestSoftwareUpdate: true})
- require.Error(t, err)
- require.Nil(t, sur)
- })
-}
From d19e5c4a9a6ef3c77210ac86a48cc3dc4d362b66 Mon Sep 17 00:00:00 2001
From: jacobshandling <61553566+jacobshandling@users.noreply.github.com>
Date: Wed, 31 Jul 2024 10:02:16 -0700
Subject: [PATCH 04/88] =?UTF-8?q?=20UI=20=E2=80=93=20Fix=20hosts=20table?=
=?UTF-8?q?=20pagination=20(#20853)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## #20604
- [x] Changes file added for user-visible changes in `changes/`
- [x] Manual QA for all new/changed functionality
---------
Co-authored-by: Jacob Shandling
---
changes/20604-hosts-page-pagination | 1 +
frontend/pages/hosts/ManageHostsPage/HostsPageConfig.tsx | 2 +-
frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx | 6 +++---
3 files changed, 5 insertions(+), 4 deletions(-)
create mode 100644 changes/20604-hosts-page-pagination
diff --git a/changes/20604-hosts-page-pagination b/changes/20604-hosts-page-pagination
new file mode 100644
index 0000000000..c1f68d5f94
--- /dev/null
+++ b/changes/20604-hosts-page-pagination
@@ -0,0 +1 @@
+* Fix a bug where hosts page would sometimes allow excess pagination
\ No newline at end of file
diff --git a/frontend/pages/hosts/ManageHostsPage/HostsPageConfig.tsx b/frontend/pages/hosts/ManageHostsPage/HostsPageConfig.tsx
index 114b537d3b..910f4813f3 100644
--- a/frontend/pages/hosts/ManageHostsPage/HostsPageConfig.tsx
+++ b/frontend/pages/hosts/ManageHostsPage/HostsPageConfig.tsx
@@ -55,7 +55,7 @@ export const LABEL_SLUG_PREFIX = "labels/";
export const DEFAULT_SORT_HEADER = "display_name";
export const DEFAULT_SORT_DIRECTION = "asc";
-export const DEFAULT_PAGE_SIZE = 20;
+export const DEFAULT_PAGE_SIZE = 50;
export const DEFAULT_PAGE_INDEX = 0;
export const getHostSelectStatuses = (isSandboxMode = false) => {
diff --git a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx
index e5bd75fd0e..71f2386c58 100644
--- a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx
+++ b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx
@@ -405,8 +405,8 @@ const ManageHostsPage = ({
osName,
osVersion,
vulnerability,
- page: tableQueryData ? tableQueryData.pageIndex : 0,
- perPage: tableQueryData ? tableQueryData.pageSize : 50,
+ page: tableQueryData ? tableQueryData.pageIndex : DEFAULT_PAGE_INDEX,
+ perPage: tableQueryData ? tableQueryData.pageSize : DEFAULT_PAGE_SIZE,
device_mapping: true,
osSettings: osSettingsStatus,
diskEncryptionStatus,
@@ -1578,7 +1578,7 @@ const ManageHostsPage = ({
}
defaultPageIndex={page || DEFAULT_PAGE_INDEX}
defaultSearchQuery={searchQuery}
- pageSize={50}
+ pageSize={DEFAULT_PAGE_SIZE}
additionalQueries={JSON.stringify(selectedFilters)}
inputPlaceHolder={HOSTS_SEARCH_BOX_PLACEHOLDER}
actionButton={{
From 134534a9d88afb202e09d323ea10ae34b7cda719 Mon Sep 17 00:00:00 2001
From: Martin Angers
Date: Wed, 31 Jul 2024 13:56:25 -0400
Subject: [PATCH 05/88] Special-case the name of the .exe installer for Notion
as it uses non-standard naming (#20881)
---
changes/20440-Notion-exe-installer-name | 1 +
pkg/file/file_test.go | 13 +++++-----
pkg/file/pe.go | 32 +++++++++++++++++++++++--
3 files changed, 38 insertions(+), 8 deletions(-)
create mode 100644 changes/20440-Notion-exe-installer-name
diff --git a/changes/20440-Notion-exe-installer-name b/changes/20440-Notion-exe-installer-name
new file mode 100644
index 0000000000..bc3996cc5d
--- /dev/null
+++ b/changes/20440-Notion-exe-installer-name
@@ -0,0 +1 @@
+* Added a special-case to properly name the Notion .exe Windows installer the same as how it will be reported by osquery post-install.
diff --git a/pkg/file/file_test.go b/pkg/file/file_test.go
index ceccdc9f6c..73d1ad5573 100644
--- a/pkg/file/file_test.go
+++ b/pkg/file/file_test.go
@@ -104,10 +104,11 @@ func TestExists(t *testing.T) {
//
// That is, it breaks the file name at the dollar sign and the first part is
// the expected name, the second is the expected version, the third is the
-// hex-encoded hash. Note that by default, files in testdata/installers are NOT
-// included in git, so the test files must be added manually (for size and
-// licenses considerations). Why the dollar sign? Because dots, dashes and
-// underlines are more likely to be part of the name or version.
+// hex-encoded hash and the fourth is the bundle identifier. Note that by
+// default, files in testdata/installers are NOT included in git, so the test
+// files must be added manually (for size and licenses considerations). Why the
+// dollar sign? Because dots, dashes and underlines are more likely to be part
+// of the name or version.
func TestExtractInstallerMetadata(t *testing.T) {
dents, err := os.ReadDir(filepath.Join("testdata", "installers"))
if err != nil {
@@ -120,8 +121,8 @@ func TestExtractInstallerMetadata(t *testing.T) {
}
t.Run(dent.Name(), func(t *testing.T) {
parts := strings.Split(strings.TrimSuffix(dent.Name(), filepath.Ext(dent.Name())), "$")
- if len(parts) < 3 {
- t.Fatalf("invalid filename, expected at least 3 sections, got %d: %s", len(parts), dent.Name())
+ if len(parts) < 4 {
+ t.Fatalf("invalid filename, expected at least 4 sections, got %d: %s", len(parts), dent.Name())
}
wantName, wantVersion, wantHash, wantBundleIdentifier := parts[0], parts[1], parts[2], parts[3]
wantExtension := strings.TrimPrefix(filepath.Ext(dent.Name()), ".")
diff --git a/pkg/file/pe.go b/pkg/file/pe.go
index 1a436665e9..1b0e053c09 100644
--- a/pkg/file/pe.go
+++ b/pkg/file/pe.go
@@ -50,9 +50,37 @@ func ExtractPEMetadata(r io.Reader) (*InstallerMetadata, error) {
if err != nil {
return nil, fmt.Errorf("error parsing PE version resources: %w", err)
}
- return &InstallerMetadata{
+ return applySpecialCases(&InstallerMetadata{
Name: strings.TrimSpace(v["ProductName"]),
Version: strings.TrimSpace(v["ProductVersion"]),
SHASum: h.Sum(nil),
- }, nil
+ }, v), nil
+}
+
+var exeSpecialCases = map[string]func(*InstallerMetadata, map[string]string) *InstallerMetadata{
+ "Notion": func(meta *InstallerMetadata, resources map[string]string) *InstallerMetadata {
+ if meta.Version != "" {
+ meta.Name = meta.Name + " " + meta.Version
+ }
+ return meta
+ },
+}
+
+// Unlike .exe files that are the software itself (and just need to be copied
+// over to the host), and unlike standard installer formats like .msi where the
+// metadata defines the name under which the software will be installed, .exe
+// installers may do pretty much anything they want when installing the
+// software, regardless of what the .exe metadata contains.
+//
+// For example, the Notion .exe installer installs the app under a name like
+// "Notion 3.11.1", and not just "Notion". There's no way to detect that by
+// parsing the installer's metadata, so we need to apply some special cases at
+// least for the most popular apps that use unusual naming.
+//
+// See https://github.com/fleetdm/fleet/issues/20440#issuecomment-2260500661
+func applySpecialCases(meta *InstallerMetadata, resources map[string]string) *InstallerMetadata {
+ if fn := exeSpecialCases[meta.Name]; fn != nil {
+ return fn(meta, resources)
+ }
+ return meta
}
From 672da9049d24c766068eef3a3c0d0f9341c2ba86 Mon Sep 17 00:00:00 2001
From: jacobshandling <61553566+jacobshandling@users.noreply.github.com>
Date: Wed, 31 Jul 2024 11:06:02 -0700
Subject: [PATCH 06/88] UI - improve comment syntax for better VSCode hints
(#20864)
## https://github.com/fleetdm/fleet/pull/20850#discussion_r1697497788
---------
Co-authored-by: Jacob Shandling
---
frontend/components/DataError/DataError.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/components/DataError/DataError.tsx b/frontend/components/DataError/DataError.tsx
index 1b0fbf1d10..7b2cfd6d55 100644
--- a/frontend/components/DataError/DataError.tsx
+++ b/frontend/components/DataError/DataError.tsx
@@ -15,7 +15,7 @@ interface IDataErrorProps {
children?: React.ReactNode;
card?: boolean;
className?: string;
- // flag to use the updated DataError design
+ /** Flag to use the updated DataError design */
useNew?: boolean;
}
From d16f0e085e83a933e266ba84c83a6a044480629c Mon Sep 17 00:00:00 2001
From: Luke Heath
Date: Wed, 31 Jul 2024 11:26:41 -0700
Subject: [PATCH 07/88] Add isExperimental property to features table (#20775)
---
handbook/company/pricing-features-table.yml | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/handbook/company/pricing-features-table.yml b/handbook/company/pricing-features-table.yml
index ccf0f918fe..f29ddac991 100644
--- a/handbook/company/pricing-features-table.yml
+++ b/handbook/company/pricing-features-table.yml
@@ -226,6 +226,7 @@
tier: Premium
jamfProHasFeature: appleOnly
jamfProtectHasFeature: no
+ isExperimental: yes
usualDepartment: IT
productCategories: [Device management]
pricingTableCategories: [Device management]
@@ -247,6 +248,7 @@
tier: Premium
jamfProHasFeature: yes
jamfProtectHasFeature: no
+ isExperimental: yes
usualDepartment: IT
productCategories: [Device management]
pricingTableCategories: [Device management]
@@ -689,6 +691,7 @@
tier: Premium
jamfProHasFeature: no
jamfProtectHasFeature: no
+ isExperimental: yes
productCategories: [Endpoint operations]
pricingTableCategories: [Endpoint operations]
#
@@ -1477,3 +1480,18 @@
usualDepartment: IT
productCategories: [Device management]
pricingTableCategories: [Integrations]
+#
+# ╔╦╗╦═╗╦╔═╗╔═╗╔═╗╦═╗ ╔═╗ ╦ ╦╔═╗╦═╗╦╔═╔═╗╦ ╔═╗╦ ╦ ╔╗ ╔═╗╔═╗╔═╗╔╦╗ ╔═╗╔╗╔ ╔═╗
+# ║ ╠╦╝║║ ╦║ ╦║╣ ╠╦╝ ╠═╣ ║║║║ ║╠╦╝╠╩╗╠╣ ║ ║ ║║║║ ╠╩╗╠═╣╚═╗║╣ ║║ ║ ║║║║ ╠═╣
+# ╩ ╩╚═╩╚═╝╚═╝╚═╝╩╚═ ╩ ╩ ╚╩╝╚═╝╩╚═╩ ╩╚ ╩═╝╚═╝╚╩╝ ╚═╝╩ ╩╚═╝╚═╝═╩╝ ╚═╝╝╚╝ ╩ ╩
+# ╔═╗╔═╗╦╦ ╦╔╗╔╔═╗ ╔═╗╔═╗╦ ╦╔═╗╦ ╦
+# ╠╣ ╠═╣║║ ║║║║║ ╦ ╠═╝║ ║║ ║║ ╚╦╝
+# ╚ ╩ ╩╩╩═╝╩╝╚╝╚═╝ ╩ ╚═╝╩═╝╩╚═╝ ╩
+- industryName: Trigger a workflow based on a failing policy
+ documentationUrl: https://fleetdm.com/docs/using-fleet/automations#policy-automations
+ productCategories: [Endpoint operations,Device management]
+ pricingTableCategories: [Integrations]
+ usualDepartment: IT
+ tier: Free
+ jamfProHasFeature: yes
+ jamfProtectHasFeature: no
From fa529e47f9d35b35b26b6e2e3be274752641e6b0 Mon Sep 17 00:00:00 2001
From: Eric
Date: Wed, 31 Jul 2024 13:26:54 -0500
Subject: [PATCH 08/88] Website: add Segment analytics (#20867)
Related to: https://github.com/fleetdm/confidential/issues/7339
Changes:
- Added a script tag for segment
- Added segment tracking for contact form submissions, signups, and swag
requests.
---
website/assets/.eslintrc | 1 +
website/assets/js/pages/contact.page.js | 6 +++++
.../js/pages/customers/new-license.page.js | 26 ++++++++++++++++++-
.../js/pages/docs/basic-documentation.page.js | 3 +++
.../assets/js/pages/entrance/login.page.js | 4 +--
.../assets/js/pages/entrance/signup.page.js | 4 +--
website/assets/js/pages/start.page.js | 25 ++++++++++++++++++
website/views/layouts/layout.ejs | 13 ++++++----
8 files changed, 72 insertions(+), 10 deletions(-)
diff --git a/website/assets/.eslintrc b/website/assets/.eslintrc
index 96d18f3427..6cb581fa72 100644
--- a/website/assets/.eslintrc
+++ b/website/assets/.eslintrc
@@ -49,6 +49,7 @@
"docsearch": true,
"Chart": true,
"gtag": true,
+ "analytics": true,
// ...etc.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/website/assets/js/pages/contact.page.js b/website/assets/js/pages/contact.page.js
index 4982f49cf8..36571b672a 100644
--- a/website/assets/js/pages/contact.page.js
+++ b/website/assets/js/pages/contact.page.js
@@ -86,6 +86,9 @@ parasails.registerPage('contact', {
if(typeof window.lintrk !== 'undefined') {
window.lintrk('track', { conversion_id: 18587089 });// eslint-disable-line camelcase
}
+ if(typeof analytics !== 'undefined'){
+ analytics.track('fleet_website__contact_forms');
+ }
// Show the success message.
this.cloudSuccess = true;
@@ -98,6 +101,9 @@ parasails.registerPage('contact', {
if(typeof window.lintrk !== 'undefined') {
window.lintrk('track', { conversion_id: 18587089 });// eslint-disable-line camelcase
}
+ if(typeof analytics !== 'undefined'){
+ analytics.track('fleet_website__contact_forms');
+ }
if(this.formData.numberOfHosts > 700){
this.goto(`https://calendly.com/fleetdm/talk-to-us?email=${encodeURIComponent(this.formData.emailAddress)}&name=${encodeURIComponent(this.formData.firstName+' '+this.formData.lastName)}`);
} else {
diff --git a/website/assets/js/pages/customers/new-license.page.js b/website/assets/js/pages/customers/new-license.page.js
index 6917592f51..3f1b582300 100644
--- a/website/assets/js/pages/customers/new-license.page.js
+++ b/website/assets/js/pages/customers/new-license.page.js
@@ -39,7 +39,31 @@ parasails.registerPage('new-license', {
// ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣
// ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
beforeMount: function() {
- //…
+ if(window.location.hash) {
+ if(typeof analytics !== 'undefined') {
+ if(window.location.hash === '#signup') {
+ analytics.identify(this.me.id, {
+ email: this.me.emailAddress,
+ firstName: this.me.firstName,
+ lastName: this.me.lastName,
+ company: this.me.organization,
+ primaryBuyingSituation: this.me.primaryBuyingSituation,
+ psychologicalStage: this.me.psychologicalStage,
+ });
+ analytics.track('fleet_website__sign_up');
+ } else if(window.location.hash === '#login') {
+ analytics.identify(this.me.id, {
+ email: this.me.emailAddress,
+ firstName: this.me.firstName,
+ lastName: this.me.lastName,
+ company: this.me.organization,
+ primaryBuyingSituation: this.me.primaryBuyingSituation,
+ psychologicalStage: this.me.psychologicalStage,
+ });
+ }
+ }
+ window.location.hash = '';
+ }
},
mounted: async function() {
diff --git a/website/assets/js/pages/docs/basic-documentation.page.js b/website/assets/js/pages/docs/basic-documentation.page.js
index 2c40e629da..d09d42f605 100644
--- a/website/assets/js/pages/docs/basic-documentation.page.js
+++ b/website/assets/js/pages/docs/basic-documentation.page.js
@@ -229,6 +229,9 @@ parasails.registerPage('basic-documentation', {
if(typeof window.lintrk !== 'undefined') {
window.lintrk('track', { conversion_id: 18587105 });// eslint-disable-line camelcase
}
+ if(typeof analytics !== 'undefined'){
+ analytics.track('fleet_website__swag_request');
+ }
this.goto('https://kqphpqst851.typeform.com/to/ZfA3sOu0');
},
diff --git a/website/assets/js/pages/entrance/login.page.js b/website/assets/js/pages/entrance/login.page.js
index a506effa58..a18738b75d 100644
--- a/website/assets/js/pages/entrance/login.page.js
+++ b/website/assets/js/pages/entrance/login.page.js
@@ -27,7 +27,7 @@ parasails.registerPage('login', {
showCustomerLogin: true,
// For redirecting users coming from the "Get your license" link to the license dispenser.
registerSlug: '/register',
- pageToRedirectToAfterLogin: '/start',
+ pageToRedirectToAfterLogin: '/start#login',
},
// ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
@@ -41,7 +41,7 @@ parasails.registerPage('login', {
// If we're redirecting this user to the license dispenser after they log in, modify the link to the /register page and the pageToRedirectToAfterLogin.
if(window.location.hash && window.location.hash === '#purchaseLicense'){
this.registerSlug = '/register#purchaseLicense';
- this.pageToRedirectToAfterLogin = '/new-license';
+ this.pageToRedirectToAfterLogin = '/new-license#login';
window.location.hash = '';
}
},
diff --git a/website/assets/js/pages/entrance/signup.page.js b/website/assets/js/pages/entrance/signup.page.js
index 6cad63f897..d749af7b30 100644
--- a/website/assets/js/pages/entrance/signup.page.js
+++ b/website/assets/js/pages/entrance/signup.page.js
@@ -26,7 +26,7 @@ parasails.registerPage('signup', {
showFullForm: false,
// For redirecting users coming from the "Get your license" link to the license dispenser.
loginSlug: '/login',
- pageToRedirectToAfterRegistration: '/start',
+ pageToRedirectToAfterRegistration: '/start#signup',
primaryBuyingSituation: undefined
},
@@ -37,7 +37,7 @@ parasails.registerPage('signup', {
// If we're redirecting this user to the license dispenser after they sign up, modify the link to the login page and the pageToRedirectToAfterRegistration
if(window.location.hash && window.location.hash === '#purchaseLicense'){
this.loginSlug = '/login#purchaseLicense';
- this.pageToRedirectToAfterRegistration = '/new-license';
+ this.pageToRedirectToAfterRegistration = '/new-license#signup';
window.location.hash = '';
}
},
diff --git a/website/assets/js/pages/start.page.js b/website/assets/js/pages/start.page.js
index ac31cb69d4..191a445f99 100644
--- a/website/assets/js/pages/start.page.js
+++ b/website/assets/js/pages/start.page.js
@@ -87,6 +87,31 @@ parasails.registerPage('start', {
if(this.primaryBuyingSituation && _.isEmpty(this.formData['what-are-you-using-fleet-for'])){
this.formData['what-are-you-using-fleet-for'] = {primaryBuyingSituation: this.primaryBuyingSituation};
}
+ if(window.location.hash) {
+ if(typeof analytics !== 'undefined') {
+ if(window.location.hash === '#signup') {
+ analytics.identify(this.me.id, {
+ email: this.me.emailAddress,
+ firstName: this.me.firstName,
+ lastName: this.me.lastName,
+ company: this.me.organization,
+ primaryBuyingSituation: this.me.primaryBuyingSituation,
+ psychologicalStage: this.me.psychologicalStage,
+ });
+ analytics.track('fleet_website__sign_up');
+ } else if(window.location.hash === '#login') {
+ analytics.identify(this.me.id, {
+ email: this.me.emailAddress,
+ firstName: this.me.firstName,
+ lastName: this.me.lastName,
+ company: this.me.organization,
+ primaryBuyingSituation: this.me.primaryBuyingSituation,
+ psychologicalStage: this.me.psychologicalStage,
+ });
+ }
+ }
+ window.location.hash = '';
+ }
},
mounted: async function() {
//…
diff --git a/website/views/layouts/layout.ejs b/website/views/layouts/layout.ejs
index 9b090d48db..ed4ebaf20b 100644
--- a/website/views/layouts/layout.ejs
+++ b/website/views/layouts/layout.ejs
@@ -61,6 +61,13 @@
+ <%/* Segment */%>
+
<% /* Meta pixel code */ %>
- <% /* Delete the global `self` to help avoid client-side bugs.
- (see https://developer.mozilla.org/en-US/docs/Web/API/Window/self) */ %>
-
-
-
<%/* bowser.js (for browser detection) -- included inline to avoid issues with minification that could affect the unsupported browser overlay */%>
@@ -635,6 +637,7 @@
window.history.replaceState({}, '', queryParameterLessUrl);// https://caniuse.com/mdn-api_history_replacestate
}
});
+
// Adding hover events to header dropdown menus.
$(function(){
$('[purpose=dropdown-button]').hover(
From 42ff57d05948f5d15b481d9e572f25b478b9465a Mon Sep 17 00:00:00 2001
From: Roberto Dip
Date: Wed, 31 Jul 2024 16:49:24 -0300
Subject: [PATCH 09/88] UI/Docs changes to rotate keys using Escrow Buddy
(#20843)
UI portion for #13157
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [x] Added/updated tests
- [x] Manual QA for all new/changed functionality
---
docs/Contributing/API-for-contributors.md | 21 ------
.../details/DeviceUserPage/DeviceUserPage.tsx | 13 ----
.../ResetKeyModal/ResetKeyModal.tsx | 70 -------------------
.../DeviceUserPage/ResetKeyModal/_styles.scss | 10 ---
.../DeviceUserPage/ResetKeyModal/index.ts | 1 -
.../DeviceUserBanners.tests.tsx | 46 +-----------
.../DeviceUserBanners/DeviceUserBanners.tsx | 30 ++------
.../HostDetailsBanners/HostDetailsBanners.tsx | 3 +-
frontend/services/entities/mdm.ts | 4 --
frontend/utilities/endpoints.ts | 3 -
10 files changed, 9 insertions(+), 192 deletions(-)
delete mode 100644 frontend/pages/hosts/details/DeviceUserPage/ResetKeyModal/ResetKeyModal.tsx
delete mode 100644 frontend/pages/hosts/details/DeviceUserPage/ResetKeyModal/_styles.scss
delete mode 100644 frontend/pages/hosts/details/DeviceUserPage/ResetKeyModal/index.ts
diff --git a/docs/Contributing/API-for-contributors.md b/docs/Contributing/API-for-contributors.md
index 8e42d4480b..170ff1c8ea 100644
--- a/docs/Contributing/API-for-contributors.md
+++ b/docs/Contributing/API-for-contributors.md
@@ -2755,27 +2755,6 @@ Signals the Fleet server to send a webbook request with the device UUID and seri
---
-#### Trigger FileVault key escrow
-
-Sends a signal to Fleet Desktop to initiate a FileVault key escrow. This is useful for setting the escrow key initially as well as in scenarios where a token rotation is required. **Requires Fleet Premium license**
-
-`POST /api/v1/fleet/device/{token}/rotate_encryption_key`
-
-##### Parameters
-
-| Name | Type | In | Description |
-| ----- | ------ | ---- | ---------------------------------- |
-| token | string | path | The device's authentication token. |
-
-##### Example
-
-`POST /api/v1/fleet/device/abcdef012456789/rotate_encryption_key`
-
-##### Default response
-
-`Status: 204`
-
-
### Report an agent error
Notifies the server about an agent error, resulting in two outcomes:
diff --git a/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx b/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx
index 91e9292dd9..189c8ae488 100644
--- a/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx
+++ b/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx
@@ -42,7 +42,6 @@ import PolicyDetailsModal from "../cards/Policies/HostPoliciesTable/PolicyDetail
import AutoEnrollMdmModal from "./AutoEnrollMdmModal";
import ManualEnrollMdmModal from "./ManualEnrollMdmModal";
import OSSettingsModal from "../OSSettingsModal";
-import ResetKeyModal from "./ResetKeyModal";
import BootstrapPackageModal from "../HostDetailsPage/modals/BootstrapPackageModal";
import { parseHostSoftwareQueryParams } from "../cards/Software/HostSoftware";
import SelfService from "../cards/Software/SelfService";
@@ -90,7 +89,6 @@ const DeviceUserPage = ({
const [isPremiumTier, setIsPremiumTier] = useState(false);
const [showInfoModal, setShowInfoModal] = useState(false);
const [showEnrollMdmModal, setShowEnrollMdmModal] = useState(false);
- const [showResetKeyModal, setShowResetKeyModal] = useState(false);
const [refetchStartTime, setRefetchStartTime] = useState(null);
const [showRefetchSpinner, setShowRefetchSpinner] = useState(false);
const [orgLogoURL, setOrgLogoURL] = useState("");
@@ -248,10 +246,6 @@ const DeviceUserPage = ({
setShowEnrollMdmModal(!showEnrollMdmModal);
}, [showEnrollMdmModal, setShowEnrollMdmModal]);
- const toggleResetKeyModal = useCallback(() => {
- setShowResetKeyModal(!showResetKeyModal);
- }, [showResetKeyModal, setShowResetKeyModal]);
-
const togglePolicyDetailsModal = useCallback(
(policy: IHostPolicy) => {
setShowPolicyDetailsModal(!showPolicyDetailsModal);
@@ -361,7 +355,6 @@ const DeviceUserPage = ({
host.mdm.macos_settings?.action_required ?? null
}
onTurnOnMdm={toggleEnrollMdmModal}
- onResetKey={toggleResetKeyModal}
/>
{showInfoModal && }
{showEnrollMdmModal && renderEnrollMdmModal()}
- {showResetKeyModal && (
-
- )}
)}
{!!host && showPolicyDetailsModal && (
diff --git a/frontend/pages/hosts/details/DeviceUserPage/ResetKeyModal/ResetKeyModal.tsx b/frontend/pages/hosts/details/DeviceUserPage/ResetKeyModal/ResetKeyModal.tsx
deleted file mode 100644
index e9fe2b3f05..0000000000
--- a/frontend/pages/hosts/details/DeviceUserPage/ResetKeyModal/ResetKeyModal.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import React from "react";
-
-import Button from "components/buttons/Button";
-import Modal from "components/Modal";
-import mdmAPI from "services/entities/mdm";
-import { useQuery } from "react-query";
-import Spinner from "components/Spinner";
-import DataError from "components/DataError";
-
-interface IResetKeyModalProps {
- onClose: () => void;
- deviceAuthToken: string;
-}
-
-const baseClass = "reset-key-modal";
-
-const ResetKeyModal = ({
- onClose,
- deviceAuthToken,
-}: IResetKeyModalProps): JSX.Element => {
- const { isLoading: isLoadingResetDEKey, error: errorResetDEKey } = useQuery(
- ["resetDEkey", deviceAuthToken],
- () => mdmAPI.resetEncryptionKey(deviceAuthToken),
- { refetchOnWindowFocus: false }
- );
-
- const renderModalBody = () => {
- if (isLoadingResetDEKey) {
- return
;
- }
- if (errorResetDEKey) {
- return
;
- }
-
- return (
-
-
-
- Wait 30 seconds for the Reset disk encryption key pop up to
- open.
-
-
- In the popup, enter the password you use to login to your Mac.
-
-
- Close this window and select Refetch on your My device page.
- This tells your organization that you reset your key.
-
-
-
-
- Done
-
-
-
- );
- };
- return (
-
- {renderModalBody()}
-
- );
-};
-
-export default ResetKeyModal;
diff --git a/frontend/pages/hosts/details/DeviceUserPage/ResetKeyModal/_styles.scss b/frontend/pages/hosts/details/DeviceUserPage/ResetKeyModal/_styles.scss
deleted file mode 100644
index f40c1f1c1d..0000000000
--- a/frontend/pages/hosts/details/DeviceUserPage/ResetKeyModal/_styles.scss
+++ /dev/null
@@ -1,10 +0,0 @@
-.reset-key-modal {
- ol {
- padding-left: 0;
- }
-
- li {
- margin-bottom: $pad-large;
- list-style: number inside;
- }
-}
diff --git a/frontend/pages/hosts/details/DeviceUserPage/ResetKeyModal/index.ts b/frontend/pages/hosts/details/DeviceUserPage/ResetKeyModal/index.ts
deleted file mode 100644
index 3cd713ee62..0000000000
--- a/frontend/pages/hosts/details/DeviceUserPage/ResetKeyModal/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from "./ResetKeyModal";
diff --git a/frontend/pages/hosts/details/DeviceUserPage/components/DeviceUserBanners/DeviceUserBanners.tests.tsx b/frontend/pages/hosts/details/DeviceUserPage/components/DeviceUserBanners/DeviceUserBanners.tests.tsx
index dc7a052653..cd3f28c01b 100644
--- a/frontend/pages/hosts/details/DeviceUserPage/components/DeviceUserBanners/DeviceUserBanners.tests.tsx
+++ b/frontend/pages/hosts/details/DeviceUserPage/components/DeviceUserBanners/DeviceUserBanners.tests.tsx
@@ -6,8 +6,7 @@ import DeviceUserBanners from "./DeviceUserBanners";
describe("Device User Banners", () => {
const turnOnMdmExpcetedText = /Mobile device management \(MDM\) is off\./;
- const logoutDiskEncryptExpectedText = /Disk encryption: Log out of your device or restart to turn on disk encryption\./;
- const resetKeyDiskEncryptExpcetedText = /Disk encryption: Reset your disk encryption key\./;
+ const resetKeyDiskEncryptExpcetedText = /Disk encryption: Log out of your device or restart it to safeguard your data in case your device is lost or stolen\./;
it("renders the turn on mdm banner correctly", () => {
render(
@@ -19,28 +18,11 @@ describe("Device User Banners", () => {
diskEncryptionStatus={null}
diskEncryptionActionRequired={null}
onTurnOnMdm={noop}
- onResetKey={noop}
/>
);
expect(screen.getByText(turnOnMdmExpcetedText)).toBeInTheDocument();
});
- it("renders the logout for disk encrpytion banner correctly", () => {
- render(
-
- );
- expect(screen.getByText(logoutDiskEncryptExpectedText)).toBeInTheDocument();
- });
-
it("renders the reset key for disk encryption banner correctly", () => {
render(
{
diskEncryptionStatus="action_required"
diskEncryptionActionRequired="rotate_key"
onTurnOnMdm={noop}
- onResetKey={noop}
/>
);
expect(
@@ -59,28 +40,6 @@ describe("Device User Banners", () => {
).toBeInTheDocument();
});
- it("renders only one banner in a priority order", () => {
- // set up to render logout disk encryption banner, which is 2nd in priority
- render(
-
- );
-
- expect(screen.queryByText(turnOnMdmExpcetedText)).not.toBeInTheDocument();
- expect(screen.getByText(logoutDiskEncryptExpectedText)).toBeInTheDocument();
- expect(
- screen.queryByText(resetKeyDiskEncryptExpcetedText)
- ).not.toBeInTheDocument();
- });
-
it("renders no banner correctly", () => {
// setup so mdm is not enabled and configured.
render(
@@ -92,13 +51,12 @@ describe("Device User Banners", () => {
diskEncryptionStatus={null}
diskEncryptionActionRequired={null}
onTurnOnMdm={noop}
- onResetKey={noop}
/>
);
expect(screen.queryByText(turnOnMdmExpcetedText)).not.toBeInTheDocument();
expect(
- screen.queryByText(logoutDiskEncryptExpectedText)
+ screen.queryByText(resetKeyDiskEncryptExpcetedText)
).not.toBeInTheDocument();
expect(
screen.queryByText(resetKeyDiskEncryptExpcetedText)
diff --git a/frontend/pages/hosts/details/DeviceUserPage/components/DeviceUserBanners/DeviceUserBanners.tsx b/frontend/pages/hosts/details/DeviceUserPage/components/DeviceUserBanners/DeviceUserBanners.tsx
index 9cc5e3256a..028165648f 100644
--- a/frontend/pages/hosts/details/DeviceUserPage/components/DeviceUserBanners/DeviceUserBanners.tsx
+++ b/frontend/pages/hosts/details/DeviceUserPage/components/DeviceUserBanners/DeviceUserBanners.tsx
@@ -15,7 +15,6 @@ interface IDeviceUserBannersProps {
diskEncryptionStatus: DiskEncryptionStatus | null;
diskEncryptionActionRequired: MacDiskEncryptionActionRequired | null;
onTurnOnMdm: () => void;
- onResetKey: () => void;
}
const DeviceUserBanners = ({
@@ -26,7 +25,6 @@ const DeviceUserBanners = ({
diskEncryptionStatus,
diskEncryptionActionRequired,
onTurnOnMdm,
- onResetKey,
}: IDeviceUserBannersProps) => {
const isMdmUnenrolled =
mdmEnrollmentStatus === "Off" || mdmEnrollmentStatus === null;
@@ -37,11 +35,6 @@ const DeviceUserBanners = ({
const showTurnOnMdmBanner =
hostPlatform === "darwin" && isMdmUnenrolled && mdmEnabledAndConfigured;
- const showDiskEncryptionLogoutRestart =
- diskEncryptionBannersEnabled &&
- diskEncryptionStatus === "action_required" &&
- diskEncryptionActionRequired === "log_out";
-
const showDiskEncryptionKeyResetRequired =
diskEncryptionBannersEnabled &&
diskEncryptionStatus === "action_required" &&
@@ -53,12 +46,6 @@ const DeviceUserBanners = ({
);
- const resetKeyButton = (
-
- Reset key
-
- );
-
const renderBanner = () => {
if (showTurnOnMdmBanner) {
return (
@@ -68,19 +55,14 @@ const DeviceUserBanners = ({
your device up to date so you don't have to.
);
- } else if (showDiskEncryptionLogoutRestart) {
+ }
+
+ if (showDiskEncryptionKeyResetRequired) {
return (
- Disk encryption: Log out of your device or restart to turn on disk
- encryption. Then, select Refetch . This prevents
- unauthorized access to the information on your device.
-
- );
- } else if (showDiskEncryptionKeyResetRequired) {
- return (
-
- Disk encryption: Reset your disk encryption key. This lets your
- organization help you unlock your device if you forget your password.
+ Disk encryption: Log out of your device or restart it to safeguard
+ your data in case your device is lost or stolen. After, select{" "}
+ Refetch to clear this banner.
);
}
diff --git a/frontend/pages/hosts/details/HostDetailsPage/components/HostDetailsBanners/HostDetailsBanners.tsx b/frontend/pages/hosts/details/HostDetailsPage/components/HostDetailsBanners/HostDetailsBanners.tsx
index ea3110af9b..89632164b0 100644
--- a/frontend/pages/hosts/details/HostDetailsPage/components/HostDetailsBanners/HostDetailsBanners.tsx
+++ b/frontend/pages/hosts/details/HostDetailsPage/components/HostDetailsBanners/HostDetailsBanners.tsx
@@ -79,8 +79,7 @@ const HostDetailsBanners = ({
{showDiskEncryptionUserActionRequired && (
Disk encryption: Requires action from the end user. Ask the end user
- to follow Disk encryption instructions on their{" "}
- My device page.
+ to log out of their device or restart it.
)}
diff --git a/frontend/services/entities/mdm.ts b/frontend/services/entities/mdm.ts
index a5b7409922..f8cae4461f 100644
--- a/frontend/services/entities/mdm.ts
+++ b/frontend/services/entities/mdm.ts
@@ -83,10 +83,6 @@ export interface IGetMdmCommandResultsResponse {
}
const mdmService = {
- resetEncryptionKey: (token: string) => {
- const { DEVICE_USER_RESET_ENCRYPTION_KEY } = endpoints;
- return sendRequest("POST", DEVICE_USER_RESET_ENCRYPTION_KEY(token));
- },
unenrollHostFromMdm: (hostId: number, timeout?: number) => {
const { HOST_MDM_UNENROLL } = endpoints;
return sendRequest(
diff --git a/frontend/utilities/endpoints.ts b/frontend/utilities/endpoints.ts
index 235029bf70..92553ada42 100644
--- a/frontend/utilities/endpoints.ts
+++ b/frontend/utilities/endpoints.ts
@@ -31,9 +31,6 @@ export default {
`/${API_VERSION}/fleet/device/${token}/software`,
DEVICE_SOFTWARE_INSTALL: (token: string, softwareTitleId: number) =>
`/${API_VERSION}/fleet/device/${token}/software/install/${softwareTitleId}`,
- DEVICE_USER_RESET_ENCRYPTION_KEY: (token: string): string => {
- return `/${API_VERSION}/fleet/device/${token}/rotate_encryption_key`;
- },
DEVICE_USER_MDM_ENROLLMENT_PROFILE: (token: string): string => {
return `/${API_VERSION}/fleet/device/${token}/mdm/apple/manual_enrollment_profile`;
},
From fbf1f553992d0b8b895d0af73cf3cc0be94ac3d9 Mon Sep 17 00:00:00 2001
From: Eric
Date: Wed, 31 Jul 2024 14:50:42 -0500
Subject: [PATCH 10/88] Website: Remove duplicate "Fleet" from page titles
(#20893)
Changes:
- Removed the duplicate "Fleet" from page titles.
---
.../api/controllers/articles/view-articles.js | 18 ++++-----
.../articles/view-basic-article.js | 2 +-
.../docs/view-basic-documentation.js | 2 +-
.../handbook/view-basic-handbook.js | 2 +-
website/api/controllers/view-query-detail.js | 2 +-
website/config/routes.js | 38 +++++++++----------
6 files changed, 32 insertions(+), 32 deletions(-)
diff --git a/website/api/controllers/articles/view-articles.js b/website/api/controllers/articles/view-articles.js
index f8b685a8eb..8f28c6c5c4 100644
--- a/website/api/controllers/articles/view-articles.js
+++ b/website/api/controllers/articles/view-articles.js
@@ -50,7 +50,7 @@ module.exports = {
});
}
- let pageTitleForMeta = 'Fleet blog | Fleet';
+ let pageTitleForMeta = 'Fleet blog';
let pageDescriptionForMeta = 'Read the latest articles written by Fleet.';
// Create a currentSection variable, this will be used to highlight the header dropdown that this article category lives under.
// There are three possible values for this (documentation, community, and platform), so we'll default to the one with the most article categories (community) and set the value to another section if needed.
@@ -60,39 +60,39 @@ module.exports = {
// Set a pageTitleForMeta, pageDescriptionForMeta, and currentSection variable based on the article category.
switch(category) {
case 'success-stories':
- pageTitleForMeta = 'Success stories | Fleet';
+ pageTitleForMeta = 'Success stories';
pageDescriptionForMeta = 'Read about how others are using Fleet and osquery.';
currentSection = 'platform';
break;
case 'deploy':
- pageTitleForMeta = 'Deployment guides | Fleet';
+ pageTitleForMeta = 'Deployment guides';
pageDescriptionForMeta = 'Learn how to deploy Fleet on a variety of production environments.';
currentSection = 'documentation';
break;
case 'releases':
- pageTitleForMeta = 'Releases | Fleet';
+ pageTitleForMeta = 'Releases';
pageDescriptionForMeta = 'Fleet releases new and updated features every three weeks. Read about the latest product improvements here.';
currentSection = 'documentation';
break;
case 'guides':
- pageTitleForMeta = 'Guides | Fleet';
+ pageTitleForMeta = 'Guides';
pageDescriptionForMeta = 'A collection of how-to guides for Fleet and osquery.';
currentSection = 'documentation';
break;
case 'securing':
- pageTitleForMeta = 'Security articles | Fleet';
+ pageTitleForMeta = 'Security articles';
pageDescriptionForMeta = 'Learn more about how we secure Fleet.';
break;
case 'engineering':
- pageTitleForMeta = 'Engineering articles | Fleet';
+ pageTitleForMeta = 'Engineering articles';
pageDescriptionForMeta = 'Read about engineering at Fleet and beyond.';
break;
case 'announcements':
- pageTitleForMeta = 'Announcements | Fleet';
+ pageTitleForMeta = 'Announcements';
pageDescriptionForMeta = 'Read the latest news from Fleet.';
break;
case 'podcasts':
- pageTitleForMeta = 'Podcasts | Fleet';
+ pageTitleForMeta = 'Podcasts';
pageDescriptionForMeta = 'Listen to the Future of Device Management podcast.';
break;
}
diff --git a/website/api/controllers/articles/view-basic-article.js b/website/api/controllers/articles/view-basic-article.js
index e7fc38ff83..17b997f5f7 100644
--- a/website/api/controllers/articles/view-basic-article.js
+++ b/website/api/controllers/articles/view-basic-article.js
@@ -52,7 +52,7 @@ module.exports = {
// Note: Leaving title and description as `undefined` in our view means we'll default to the generic title and description set in layout.ejs.
let pageTitleForMeta;
if(thisPage.meta.articleTitle) {
- pageTitleForMeta = thisPage.meta.articleTitle + ' | Fleet';
+ pageTitleForMeta = thisPage.meta.articleTitle;
}//fi
let pageDescriptionForMeta;
if(thisPage.meta.description){
diff --git a/website/api/controllers/docs/view-basic-documentation.js b/website/api/controllers/docs/view-basic-documentation.js
index 3961775bad..b5d1746e8b 100644
--- a/website/api/controllers/docs/view-basic-documentation.js
+++ b/website/api/controllers/docs/view-basic-documentation.js
@@ -69,7 +69,7 @@ module.exports = {
compiledPagePartialsAppPath: sails.config.builtStaticContent.compiledPagePartialsAppPath,
pageTitleForMeta: (
thisPage.title !== 'Readme.md' ? thisPage.title + ' | Fleet documentation'// « custom meta title for this page, if provided in markdown
- : 'Documentation | Fleet' // « otherwise we're on the landing page for this section of the site, so we'll follow the title format of other top-level pages
+ : 'Documentation' // « otherwise we're on the landing page for this section of the site, so we'll follow the title format of other top-level pages
),
pageDescriptionForMeta: (
thisPage.meta.description ? thisPage.meta.description // « custom meta description for this page, if provided in markdown
diff --git a/website/api/controllers/handbook/view-basic-handbook.js b/website/api/controllers/handbook/view-basic-handbook.js
index bd30a469d6..6c9f1b4b01 100644
--- a/website/api/controllers/handbook/view-basic-handbook.js
+++ b/website/api/controllers/handbook/view-basic-handbook.js
@@ -64,7 +64,7 @@ module.exports = {
compiledPagePartialsAppPath: sails.config.builtStaticContent.compiledPagePartialsAppPath,
pageTitleForMeta: (
thisPage.title !== 'Readme.md' ? thisPage.title + ' | Fleet handbook'// « custom meta title for this page, if provided in markdown
- : 'Handbook | Fleet' // « otherwise we're on the landing page for this section of the site, so we'll follow the title format of other top-level pages
+ : 'Handbook' // « otherwise we're on the landing page for this section of the site, so we'll follow the title format of other top-level pages
),
pageDescriptionForMeta: (
thisPage.meta.description ? thisPage.meta.description // « custom meta description for this page, if provided in markdown
diff --git a/website/api/controllers/view-query-detail.js b/website/api/controllers/view-query-detail.js
index 3d974034ad..c4f8f51430 100644
--- a/website/api/controllers/view-query-detail.js
+++ b/website/api/controllers/view-query-detail.js
@@ -35,7 +35,7 @@ module.exports = {
}
// Setting the meta title and description of this page using the query object, and falling back to a generic title or description if query.name or query.description are missing.
- let pageTitleForMeta = query.name ? query.name + ' | Query details' : 'Query details | Fleet';
+ let pageTitleForMeta = query.name ? query.name + ' | Query details' : 'Query details';
let pageDescriptionForMeta = query.description ? query.description : 'View more information about a query in Fleet\'s standard query library';
// Respond with view.
return {
diff --git a/website/config/routes.js b/website/config/routes.js
index 8339089831..59b068e650 100644
--- a/website/config/routes.js
+++ b/website/config/routes.js
@@ -23,7 +23,7 @@ module.exports.routes = {
'GET /contact': {
action: 'view-contact',
locals: {
- pageTitleForMeta: 'Contact us | Fleet',
+ pageTitleForMeta: 'Contact us',
pageDescriptionForMeta: 'Get in touch with our team.',
hideFooterLinks: true,
}
@@ -34,7 +34,7 @@ module.exports.routes = {
locals: {
hideHeaderLinks: true,
hideFooterLinks: true,
- pageTitleForMeta: 'fleetctl preview | Fleet',
+ pageTitleForMeta: 'fleetctl preview',
pageDescriptionForMeta: 'Learn about getting started with Fleet using fleetctl.'
}
},
@@ -43,7 +43,7 @@ module.exports.routes = {
action: 'view-pricing',
locals: {
currentSection: 'pricing',
- pageTitleForMeta: 'Pricing | Fleet',
+ pageTitleForMeta: 'Pricing',
pageDescriptionForMeta: 'Use Fleet for free or get started with Fleet Premium (self-hosted or managed cloud). Have a large deployment? We\'ve got you covered.'
}
},
@@ -51,7 +51,7 @@ module.exports.routes = {
'GET /logos': {
action: 'view-press-kit',
locals: {
- pageTitleForMeta: 'Logos | Fleet',
+ pageTitleForMeta: 'Logos',
pageDescriptionForMeta: 'Download Fleet logos, wallpapers, and screenshots.'
}
},
@@ -60,7 +60,7 @@ module.exports.routes = {
action: 'view-query-library',
locals: {
currentSection: 'documentation',
- pageTitleForMeta: 'Queries | Fleet',
+ pageTitleForMeta: 'Queries',
pageDescriptionForMeta: 'A growing collection of useful queries for organizations deploying Fleet and osquery.'
}
},
@@ -106,7 +106,7 @@ module.exports.routes = {
hideHeaderLinks: true,
hideFooterLinks: true,
hideStartCTA: true,
- pageTitleForMeta: 'Get Fleet Premium | Fleet',
+ pageTitleForMeta: 'Get Fleet Premium',
pageDescriptionForMeta: 'Generate your quote and start using Fleet Premium today.',
}
},
@@ -114,7 +114,7 @@ module.exports.routes = {
action: 'entrance/view-signup',
locals: {
hideFooterLinks: true,
- pageTitleForMeta: 'Sign up | Fleet',
+ pageTitleForMeta: 'Sign up',
pageDescriptionForMeta: 'Sign up for a Fleet account.',
}
},
@@ -122,7 +122,7 @@ module.exports.routes = {
action: 'entrance/view-login',
locals: {
hideFooterLinks: true,
- pageTitleForMeta: 'Log in | Fleet',
+ pageTitleForMeta: 'Log in',
pageDescriptionForMeta: 'Log in to Fleet.',
}
},
@@ -132,7 +132,7 @@ module.exports.routes = {
hideHeaderLinks: true,
hideFooterLinks: true,
hideStartCTA: true,
- pageTitleForMeta: 'Customer dashboard | Fleet',
+ pageTitleForMeta: 'Customer dashboard',
pageDescriptionForMeta: 'View and edit information about your Fleet Premium license.',
}
},
@@ -142,7 +142,7 @@ module.exports.routes = {
hideHeaderLinks: true,
hideFooterLinks: true,
hideStartCTA: true,
- pageTitleForMeta: 'Forgot password | Fleet',
+ pageTitleForMeta: 'Forgot password',
pageDescriptionForMeta: 'Recover the password for your Fleet customer account.',
}
},
@@ -152,7 +152,7 @@ module.exports.routes = {
hideHeaderLinks: true,
hideFooterLinks: true,
hideStartCTA: true,
- pageTitleForMeta: 'New password | Fleet',
+ pageTitleForMeta: 'New password',
pageDescriptionForMeta: 'Change the password for your Fleet customer account.',
}
},
@@ -160,7 +160,7 @@ module.exports.routes = {
'GET /reports/state-of-device-management': {
action: 'reports/view-state-of-device-management',
locals: {
- pageTitleForMeta: 'State of device management | Fleet',
+ pageTitleForMeta: 'State of device management',
pageDescriptionForMeta: 'We surveyed 200+ security practitioners to discover the state of device management in 2022. Click here to learn about their struggles and best practices.',
}
},
@@ -221,7 +221,7 @@ module.exports.routes = {
'GET /device-management': {
action: 'view-device-management',
locals: {
- pageTitleForMeta: 'Device management (MDM) | Fleet',
+ pageTitleForMeta: 'Device management (MDM)',
pageDescriptionForMeta: 'Manage your devices in any browser or use git to make changes as code.',
currentSection: 'platform',
}
@@ -230,7 +230,7 @@ module.exports.routes = {
'GET /endpoint-ops': {
action: 'view-endpoint-ops',
locals: {
- pageTitleForMeta: 'Endpoint ops | Fleet',
+ pageTitleForMeta: 'Endpoint ops',
pageDescriptionForMeta: 'Pulse check anything, build reports, and ship data to any platform with Fleet.',
currentSection: 'platform',
}
@@ -239,7 +239,7 @@ module.exports.routes = {
'GET /vulnerability-management': {
action: 'view-vulnerability-management',
locals: {
- pageTitleForMeta: 'Vulnerability management | Fleet',
+ pageTitleForMeta: 'Vulnerability management',
pageDescriptionForMeta: 'Report CVEs, software inventory, security posture, and other risks down to the chipset of any endpoint with Fleet.',
currentSection: 'platform',
}
@@ -248,7 +248,7 @@ module.exports.routes = {
'GET /support': {
action: 'view-support',
locals: {
- pageTitleForMeta: 'Support | Fleet',
+ pageTitleForMeta: 'Support',
pageDescriptionForMeta: 'Ask a question, chat with engineers, or get in touch with the Fleet team.',
currentSection: 'documentation',
}
@@ -257,7 +257,7 @@ module.exports.routes = {
'GET /integrations': {
action: 'view-integrations',
locals: {
- pageTitleForMeta: 'Integrations | Fleet',
+ pageTitleForMeta: 'Integrations',
pageDescriptionForMeta: 'Integrate IT ticketing systems, SIEM and SOAR platforms, custom IT workflows, and more.',
currentSection: 'platform'
}
@@ -269,7 +269,7 @@ module.exports.routes = {
hideFooterLinks: true,
hideGetStartedButton: true,
hideStartCTA: true,
- pageTitleForMeta: 'Start | Fleet',
+ pageTitleForMeta: 'Start',
pageDescriptionForMeta: 'Get Started with Fleet. Spin up a local demo or get your Premium license key.',
}
},
@@ -278,7 +278,7 @@ module.exports.routes = {
action: 'view-transparency',
locals: {
pageDescriptionForMeta: 'Discover how Fleet simplifies IT and security, prioritizing privacy, transparency, and trust for end users.',
- pageTitleForMeta: 'Better with Fleet | Fleet'
+ pageTitleForMeta: 'Better with Fleet'
}
},
From 7a080a9b36aa1c30773478868c1d5f05a58472d6 Mon Sep 17 00:00:00 2001
From: Roberto Dip
Date: Wed, 31 Jul 2024 16:59:30 -0300
Subject: [PATCH 11/88] use Escrow Buddy to rotate FileVault keys on macOS
(#20842)
back-end and agent part of #13157
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files)
for more information.
- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
- [x] Added/updated tests
- [x] 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)).
---
Makefile | 8 +
changes/13157-fv-escrow | 1 +
ee/fleetctl/updates.go | 2 +
ee/server/service/devices.go | 4 -
orbit/changes/13157-fv-escrow | 1 +
orbit/cmd/orbit/orbit.go | 6 +-
orbit/pkg/update/escrow_buddy.go | 120 +++++++++++++++
orbit/pkg/update/escrow_buddy_test.go | 86 +++++++++++
orbit/pkg/update/options.go | 6 +
orbit/pkg/update/testing_utils.go | 1 +
orbit/pkg/update/update.go | 14 ++
server/datastore/mysql/hosts.go | 15 --
server/datastore/mysql/hosts_test.go | 9 +-
server/fleet/capabilities.go | 3 +
server/fleet/datastore.go | 2 -
server/fleet/hosts.go | 26 +---
server/fleet/hosts_test.go | 44 ------
server/fleet/service.go | 2 -
server/mock/datastore_mock.go | 12 --
server/service/devices.go | 35 -----
server/service/handler.go | 4 -
server/service/hosts_test.go | 195 ++++++++++++++++++++++++-
server/service/integration_mdm_test.go | 32 +---
server/service/orbit.go | 78 ++++++++--
tools/tuf/README.md | 13 ++
tools/tuf/test/create_repository.sh | 17 ++-
26 files changed, 537 insertions(+), 199 deletions(-)
create mode 100644 changes/13157-fv-escrow
create mode 100644 orbit/changes/13157-fv-escrow
create mode 100644 orbit/pkg/update/escrow_buddy.go
create mode 100644 orbit/pkg/update/escrow_buddy_test.go
diff --git a/Makefile b/Makefile
index 8f7cf455d5..2e7c317baa 100644
--- a/Makefile
+++ b/Makefile
@@ -424,6 +424,14 @@ endif
tar czf $(out-path)/swiftDialog.app.tar.gz -C $(TMP_DIR)/swiftDialog_pkg_payload_expanded/Library/Application\ Support/Dialog/ Dialog.app
rm -rf $(TMP_DIR)
+# Generate escrowBuddy.pkg bundle from the Escrow Buddy repo.
+#
+# Usage:
+# make escrow-buddy-pkg version=1.0.0 out-path=.
+escrow-buddy-pkg:
+ curl -L https://github.com/macadmins/escrow-buddy/releases/download/v$(version)/Escrow.Buddy-$(version).pkg --output $(out-path)/escrowBuddy.pkg
+
+
# Build and generate desktop.app.tar.gz bundle.
#
# Usage:
diff --git a/changes/13157-fv-escrow b/changes/13157-fv-escrow
new file mode 100644
index 0000000000..e6804a05ec
--- /dev/null
+++ b/changes/13157-fv-escrow
@@ -0,0 +1 @@
+* `fleetd` now uses Escrow Buddy to rotate FileVault keys. Internal API endpoints documented in the API for contributors have been modified and/or removed.
diff --git a/ee/fleetctl/updates.go b/ee/fleetctl/updates.go
index 8ef04ef1f6..2e55081fa4 100644
--- a/ee/fleetctl/updates.go
+++ b/ee/fleetctl/updates.go
@@ -270,6 +270,8 @@ func updatesAddFunc(c *cli.Context) error {
dstPath += ".exe"
case strings.HasSuffix(target, ".app.tar.gz"):
dstPath += ".app.tar.gz"
+ case strings.HasSuffix(target, ".pkg"):
+ dstPath += ".pkg"
// osquery extensions require the .ext suffix
case strings.HasSuffix(target, ".ext"):
dstPath += ".ext"
diff --git a/ee/server/service/devices.go b/ee/server/service/devices.go
index 4208ee15c5..590067e916 100644
--- a/ee/server/service/devices.go
+++ b/ee/server/service/devices.go
@@ -17,10 +17,6 @@ func (svc *Service) ListDevicePolicies(ctx context.Context, host *fleet.Host) ([
return svc.ds.ListPoliciesForHost(ctx, host)
}
-func (svc *Service) RequestEncryptionKeyRotation(ctx context.Context, hostID uint) error {
- return svc.ds.SetDiskEncryptionResetStatus(ctx, hostID, true)
-}
-
const refetchMDMUnenrollCriticalQueryDuration = 3 * time.Minute
// TriggerMigrateMDMDevice triggers the webhook associated with the MDM
diff --git a/orbit/changes/13157-fv-escrow b/orbit/changes/13157-fv-escrow
new file mode 100644
index 0000000000..b4ff408b05
--- /dev/null
+++ b/orbit/changes/13157-fv-escrow
@@ -0,0 +1 @@
+* Use Escrow Buddy to rotate FileVault keys on macOS
diff --git a/orbit/cmd/orbit/orbit.go b/orbit/cmd/orbit/orbit.go
index cf24f3c0ef..d124a47d57 100644
--- a/orbit/cmd/orbit/orbit.go
+++ b/orbit/cmd/orbit/orbit.go
@@ -870,7 +870,11 @@ func main() {
orbitClient.RegisterConfigReceiver(update.ApplyNudgeConfigReceiverMiddleware(update.NudgeConfigFetcherOptions{
UpdateRunner: updateRunner, RootDir: c.String("root-dir"), Interval: nudgeLaunchInterval,
}))
- orbitClient.RegisterConfigReceiver(update.ApplyDiskEncryptionRunnerMiddleware())
+ if orbitClient.GetServerCapabilities().Has(fleet.CapabilityEscrowBuddy) {
+ orbitClient.RegisterConfigReceiver(update.NewEscrowBuddyRunner(updateRunner, 5*time.Minute))
+ } else {
+ orbitClient.RegisterConfigReceiver(update.ApplyDiskEncryptionRunnerMiddleware())
+ }
orbitClient.RegisterConfigReceiver(update.ApplySwiftDialogDownloaderMiddleware(updateRunner))
case "windows":
orbitClient.RegisterConfigReceiver(update.ApplyWindowsMDMEnrollmentFetcherMiddleware(windowsMDMEnrollmentCommandFrequency, orbitHostInfo.HardwareUUID, orbitClient))
diff --git a/orbit/pkg/update/escrow_buddy.go b/orbit/pkg/update/escrow_buddy.go
new file mode 100644
index 0000000000..72ef1fab5a
--- /dev/null
+++ b/orbit/pkg/update/escrow_buddy.go
@@ -0,0 +1,120 @@
+package update
+
+import (
+ "fmt"
+ "sync"
+ "time"
+
+ "github.com/fleetdm/fleet/v4/server/fleet"
+ "github.com/rs/zerolog/log"
+)
+
+// EscrowBuddyRunner sets up [Escrow Buddy][1] to rotate FileVault keys on
+// macOS without user interaction. This runner:
+//
+// - Ensures Escrow Buddy is added as a target for the update runner, so the
+// authorization plugin is downloaded and installed.
+// - Shells out to call `defaults` to configure Escrow Buddy according to
+// server instructions provided via notifications.
+//
+// [1]: https://github.com/macadmins/escrow-buddy
+type EscrowBuddyRunner struct {
+ // updateRunner is the wrapped Runner where Escrow Buddy will be set as
+ // a target.
+ updateRunner *Runner
+ // runCmdFunc can be set in tests to mock the command executed to
+ // configure Escrow Buddy
+ runCmdFunc func(cmd string, args ...string) error
+ // runMu guards runs to prevent multiple Run calls happening at the
+ // same time.
+ runMu sync.Mutex
+ // lastRun is used to guarantee that the run interval is enforced
+ lastRun time.Time
+ // interval defines how often Run is allowed to perform work
+ interval time.Duration
+}
+
+// NewEscrowBuddyRunner returns a new instance configured with the provided values
+func NewEscrowBuddyRunner(runner *Runner, interval time.Duration) fleet.OrbitConfigReceiver {
+ return &EscrowBuddyRunner{updateRunner: runner, interval: interval}
+}
+
+func (e *EscrowBuddyRunner) Run(cfg *fleet.OrbitConfig) error {
+ log.Debug().Msgf("EscrowBuddyRunner: notification: %t", cfg.Notifications.RotateDiskEncryptionKey)
+
+ if e.updateRunner == nil {
+ log.Debug().Msg("EscrowBuddyRunner: received nil UpdateRunner, this probably indicates that updates are turned off. Skipping any actions related to Disk encryption")
+ return nil
+ }
+
+ if !e.runMu.TryLock() {
+ log.Debug().Msg("EscrowBuddyRunner: a previous instance is currently running, returning early")
+ return nil
+ }
+
+ defer e.runMu.Unlock()
+ if time.Since(e.lastRun) < e.interval {
+ log.Debug().Msgf("EscrowBuddyRunner: last run (%v) is less than the configured interval (%v), returning early", e.lastRun, e.interval)
+ return nil
+ }
+
+ updaterHasTarget := e.updateRunner.HasRunnerOptTarget("escrowBuddy")
+ // if the notification is false, it could mean that we shouldn't do
+ // anything at all (eg: MDM is not configured) or that this host
+ // doesn't need to rotate the key.
+ //
+ // if Escrow Buddy is a TUF target, it means that we tried to rotate
+ // the key before, and we must disable it to keep the local state as
+ // instructed by the server.
+ if !cfg.Notifications.RotateDiskEncryptionKey {
+ if updaterHasTarget {
+ log.Debug().Msg("EscrowBuddyRunner: disabling disk encryption rotation")
+ e.lastRun = time.Now()
+ return e.setGenerateNewKeyTo(false)
+ }
+
+ log.Debug().Msg("EscrowBuddyRunner: skipping any actions related to disk encryption")
+ return nil
+ }
+
+ runnerHasLocalHash := e.updateRunner.HasLocalHash("escrowBuddy")
+ if !updaterHasTarget || !runnerHasLocalHash {
+ log.Info().Msg("refreshing the update runner config with Escrow Buddy targets and hashes")
+ log.Debug().Msgf("updater has target: %t, runner has local hash: %t", updaterHasTarget, runnerHasLocalHash)
+ if err := e.setTargetsAndHashes(); err != nil {
+ return fmt.Errorf("setting Escrow Buddy targets and hashes: %w", err)
+ }
+ }
+
+ log.Debug().Msg("EscrowBuddyRunner: enabling disk encryption rotation")
+ if err := e.setGenerateNewKeyTo(true); err != nil {
+ return fmt.Errorf("enabling disk encryption rotation: %w", err)
+ }
+
+ e.lastRun = time.Now()
+ return nil
+}
+
+func (e *EscrowBuddyRunner) setTargetsAndHashes() error {
+ e.updateRunner.AddRunnerOptTarget("escrowBuddy")
+ e.updateRunner.updater.SetTargetInfo("escrowBuddy", EscrowBuddyMacOSTarget)
+ // we don't want to keep escrowBuddy as a target if we failed to update the
+ // cached hashes in the runner.
+ if err := e.updateRunner.StoreLocalHash("escrowBuddy"); err != nil {
+ log.Debug().Msgf("removing escrowBuddy from target options, error updating local hashes: %s", err)
+ e.updateRunner.RemoveRunnerOptTarget("escrowBuddy")
+ e.updateRunner.updater.RemoveTargetInfo("escrowBuddy")
+ return err
+ }
+ return nil
+}
+
+func (e *EscrowBuddyRunner) setGenerateNewKeyTo(enabled bool) error {
+ log.Debug().Msgf("running defaults write to configure Escrow Buddy with value %t", enabled)
+ cmd := fmt.Sprintf("defaults write /Library/Preferences/com.netflix.Escrow-Buddy.plist GenerateNewKey -bool %t", enabled)
+ fn := e.runCmdFunc
+ if fn == nil {
+ fn = runCmdCollectErr
+ }
+ return fn("sh", "-c", cmd)
+}
diff --git a/orbit/pkg/update/escrow_buddy_test.go b/orbit/pkg/update/escrow_buddy_test.go
new file mode 100644
index 0000000000..0ed61883b0
--- /dev/null
+++ b/orbit/pkg/update/escrow_buddy_test.go
@@ -0,0 +1,86 @@
+package update
+
+import (
+ "testing"
+ "time"
+
+ "github.com/fleetdm/fleet/v4/server/fleet"
+ "github.com/stretchr/testify/require"
+ "github.com/stretchr/testify/suite"
+)
+
+func TestEscrowBuddy(t *testing.T) {
+ testingSuite := new(escrowBuddyTestSuite)
+ testingSuite.s = &testingSuite.Suite
+ suite.Run(t, testingSuite)
+}
+
+type escrowBuddyTestSuite struct {
+ suite.Suite
+ withTUF
+}
+
+func (s *escrowBuddyTestSuite) TestUpdatesDisabled() {
+ t := s.T()
+ cfg := &fleet.OrbitConfig{}
+ cfg.Notifications.RotateDiskEncryptionKey = true
+ r := NewEscrowBuddyRunner(nil, time.Second)
+ err := r.Run(cfg)
+ require.NoError(t, err)
+}
+
+func (s *escrowBuddyTestSuite) TestEscrowBuddyRotatesKey() {
+ t := s.T()
+ updater := &Updater{
+ client: s.client,
+ opt: Options{Targets: make(map[string]TargetInfo), RootDirectory: t.TempDir()},
+ }
+ runner := &Runner{updater: updater, localHashes: make(map[string][]byte)}
+ escrowBuddyPath := "escrowBuddy/macos/stable/escrowBuddy.pkg"
+
+ cfg := &fleet.OrbitConfig{}
+ r := &EscrowBuddyRunner{updateRunner: runner, interval: time.Millisecond}
+ // mock the command to run the defaults cli
+ cmdCalls := []map[string]any{}
+ r.runCmdFunc = func(cmd string, args ...string) error {
+ cmdCalls = append(cmdCalls, map[string]any{"cmd": cmd, "args": args})
+ return nil
+ }
+
+ // no new target added if the notification is not set
+ err := r.Run(cfg)
+ require.NoError(t, err)
+ targets := runner.updater.opt.Targets
+ require.Len(t, targets, 0)
+ require.Empty(t, cmdCalls)
+
+ // there's an error when the remote repo doesn't have the target yet
+ cfg.Notifications.RotateDiskEncryptionKey = true
+ err = r.Run(cfg)
+ require.ErrorContains(t, err, "tuf: file not found")
+ require.Empty(t, cmdCalls)
+
+ // add escrow buddy to the remote
+ s.addRemoteTarget(escrowBuddyPath)
+
+ err = r.Run(cfg)
+ require.NoError(t, err)
+ require.Len(t, cmdCalls, 1)
+ require.Equal(t, cmdCalls[0]["cmd"], "sh")
+ require.Equal(t, cmdCalls[0]["args"], []string{"-c", "defaults write /Library/Preferences/com.netflix.Escrow-Buddy.plist GenerateNewKey -bool true"})
+
+ targets = runner.updater.opt.Targets
+ require.Len(t, targets, 1)
+ ti, ok := targets["escrowBuddy"]
+ require.True(t, ok)
+ require.EqualValues(t, EscrowBuddyMacOSTarget, ti)
+
+ time.Sleep(3 * time.Millisecond)
+ cfg.Notifications.RotateDiskEncryptionKey = false
+ err = r.Run(cfg)
+ require.NoError(t, err)
+ require.Len(t, cmdCalls, 2)
+ require.Equal(t, cmdCalls[1]["cmd"], "sh")
+ require.Equal(t, cmdCalls[1]["args"], []string{"-c", "defaults write /Library/Preferences/com.netflix.Escrow-Buddy.plist GenerateNewKey -bool false"})
+
+}
diff --git a/orbit/pkg/update/options.go b/orbit/pkg/update/options.go
index 90cdcaf62a..ed04f5a10b 100644
--- a/orbit/pkg/update/options.go
+++ b/orbit/pkg/update/options.go
@@ -122,4 +122,10 @@ var (
TargetFile: "swiftDialog.app.tar.gz",
ExtractedExecSubPath: []string{"Dialog.app", "Contents", "MacOS", "Dialog"},
}
+
+ EscrowBuddyMacOSTarget = TargetInfo{
+ Platform: "macos",
+ Channel: "stable",
+ TargetFile: "escrowBuddy.pkg",
+ }
)
diff --git a/orbit/pkg/update/testing_utils.go b/orbit/pkg/update/testing_utils.go
index 53c943f2c8..83698e240e 100644
--- a/orbit/pkg/update/testing_utils.go
+++ b/orbit/pkg/update/testing_utils.go
@@ -85,6 +85,7 @@ func (ts *withTUF) SetupSuite() {
ts.mockFiles = map[string][]byte{
"nudge/macos/stable/nudge.app.tar.gz": ts.memTarGz("/Nudge.app/Contents/MacOS/Nudge", "nudge"),
"osqueryd/macos/stable/osqueryd.app.tar.gz": ts.memTarGz("osqueryd", "osqueryd"),
+ "escrowBuddy/macos/stable/escrowBuddy.pkg": {},
}
ts.store = tuf.MemoryStore(nil, ts.mockFiles)
diff --git a/orbit/pkg/update/update.go b/orbit/pkg/update/update.go
index c6b00df89e..f9bee2413f 100644
--- a/orbit/pkg/update/update.go
+++ b/orbit/pkg/update/update.go
@@ -384,6 +384,12 @@ func (u *Updater) get(target string) (*LocalTarget, error) {
return nil, fmt.Errorf("failed to remove old extracted dir: %q: %w", localTarget.DirPath, err)
}
}
+ if strings.HasSuffix(localTarget.Path, ".pkg") {
+ cmd := exec.Command("installer", "-pkg", localTarget.Path, "-target", "/")
+ if out, err := cmd.CombinedOutput(); err != nil {
+ return nil, fmt.Errorf("running pkgutil to install %s: %s: %w", localTarget.Path, string(out), err)
+ }
+ }
} else {
log.Debug().Str("path", localTarget.Path).Str("target", target).Msg("found expected target locally")
}
@@ -558,6 +564,14 @@ func (u *Updater) checkExec(target, tmpPath string, customCheckExec func(execPat
tmpPath = filepath.Join(append([]string{filepath.Dir(tmpPath)}, localTarget.Info.ExtractedExecSubPath...)...)
}
+ if strings.HasSuffix(tmpPath, ".pkg") && runtime.GOOS == "darwin" {
+ cmd := exec.Command("pkgutil", "--payload-files", tmpPath)
+ if out, err := cmd.CombinedOutput(); err != nil {
+ return fmt.Errorf("running pkgutil to verify %s: %s: %w", tmpPath, string(out), err)
+ }
+ return nil
+ }
+
if customCheckExec != nil {
if err := customCheckExec(tmpPath); err != nil {
return fmt.Errorf("custom exec new version failed: %w", err)
diff --git a/server/datastore/mysql/hosts.go b/server/datastore/mysql/hosts.go
index 1afd8e93de..f5ed94d2f4 100644
--- a/server/datastore/mysql/hosts.go
+++ b/server/datastore/mysql/hosts.go
@@ -2271,7 +2271,6 @@ func (ds *Datastore) LoadHostByOrbitNodeKey(ctx context.Context, nodeKey string)
h.policy_updated_at,
h.public_ip,
h.orbit_node_key,
- COALESCE(hdek.reset_requested, false) AS disk_encryption_reset_requested,
IF(hdep.host_id AND ISNULL(hdep.deleted_at), true, false) AS dep_assigned_to_fleet,
hd.encrypted as disk_encryption_enabled,
COALESCE(hdek.decryptable, false) as encryption_key_available,
@@ -4955,20 +4954,6 @@ func (ds *Datastore) ListUpcomingHostMaintenanceWindows(ctx context.Context, hid
return mws, nil
}
-func (ds *Datastore) SetDiskEncryptionResetStatus(ctx context.Context, hostID uint, status bool) error {
- const stmt = `
- INSERT INTO host_disk_encryption_keys (host_id, reset_requested, base64_encrypted)
- VALUES (?, ?, '')
- ON DUPLICATE KEY UPDATE
- reset_requested = VALUES(reset_requested)`
-
- _, err := ds.writer(ctx).ExecContext(ctx, stmt, hostID, status)
- if err != nil {
- return ctxerr.Wrap(ctx, err, "upsert disk encryption reset status")
- }
- return nil
-}
-
// countHostNotResponding counts the hosts that haven't been submitting results for sent queries.
//
// Notes:
diff --git a/server/datastore/mysql/hosts_test.go b/server/datastore/mysql/hosts_test.go
index fb37661ccf..ed703b0f54 100644
--- a/server/datastore/mysql/hosts_test.go
+++ b/server/datastore/mysql/hosts_test.go
@@ -7616,7 +7616,6 @@ func testHostsLoadHostByOrbitNodeKey(t *testing.T, ds *Datastore) {
// the returned host by LoadHostByOrbitNodeKey will have the orbit key stored
h.OrbitNodeKey = &orbitKey
- h.DiskEncryptionResetRequested = ptr.Bool(false)
returned, err := ds.LoadHostByOrbitNodeKey(ctx, orbitKey)
require.NoError(t, err)
@@ -7696,8 +7695,8 @@ func testHostsLoadHostByOrbitNodeKey(t *testing.T, ds *Datastore) {
require.NoError(t, err)
loadFleet, err = ds.LoadHostByOrbitNodeKey(ctx, *hFleet.OrbitNodeKey)
require.NoError(t, err)
- require.True(t, loadFleet.MDM.EncryptionKeyAvailable)
require.NoError(t, err)
+ require.True(t, loadFleet.MDM.EncryptionKeyAvailable)
require.NotNil(t, loadFleet.DiskEncryptionEnabled)
require.True(t, *loadFleet.DiskEncryptionEnabled)
@@ -8361,8 +8360,8 @@ func testHostsEncryptionKeyRawDecryption(t *testing.T, ds *Datastore) {
// no disk encryption key information
got, err := ds.Host(ctx, host.ID)
require.NoError(t, err)
- require.False(t, got.MDM.EncryptionKeyAvailable)
require.NotNil(t, got.MDM.TestGetRawDecryptable())
+ require.False(t, got.MDM.EncryptionKeyAvailable)
require.Equal(t, -1, *got.MDM.TestGetRawDecryptable())
// create the encryption key row, but unknown decryptable
@@ -8380,8 +8379,8 @@ func testHostsEncryptionKeyRawDecryption(t *testing.T, ds *Datastore) {
got, err = ds.Host(ctx, host.ID)
require.NoError(t, err)
- require.False(t, got.MDM.EncryptionKeyAvailable)
require.NotNil(t, got.MDM.TestGetRawDecryptable())
+ require.False(t, got.MDM.EncryptionKeyAvailable)
require.Equal(t, 0, *got.MDM.TestGetRawDecryptable())
// mark the key as decryptable
@@ -8390,8 +8389,8 @@ func testHostsEncryptionKeyRawDecryption(t *testing.T, ds *Datastore) {
got, err = ds.Host(ctx, host.ID)
require.NoError(t, err)
- require.True(t, got.MDM.EncryptionKeyAvailable)
require.NotNil(t, got.MDM.TestGetRawDecryptable())
+ require.True(t, got.MDM.EncryptionKeyAvailable)
require.Equal(t, 1, *got.MDM.TestGetRawDecryptable())
}
diff --git a/server/fleet/capabilities.go b/server/fleet/capabilities.go
index b1bfff1139..be397bcc32 100644
--- a/server/fleet/capabilities.go
+++ b/server/fleet/capabilities.go
@@ -78,6 +78,8 @@ const (
// CapabilityEndUserEmail denotes the ability of the server to support
// receiving the end-user email from orbit.
CapabilityEndUserEmail Capability = "end_user_email"
+ // CapabilityEscrowBuddy allows to use Escrow Buddy to rotate FileVault keys
+ CapabilityEscrowBuddy Capability = "escrow_buddy"
)
func GetServerOrbitCapabilities() CapabilityMap {
@@ -85,6 +87,7 @@ func GetServerOrbitCapabilities() CapabilityMap {
CapabilityOrbitEndpoints: {},
CapabilityTokenRotation: {},
CapabilityEndUserEmail: {},
+ CapabilityEscrowBuddy: {},
}
}
diff --git a/server/fleet/datastore.go b/server/fleet/datastore.go
index a043eba04e..7c61870d68 100644
--- a/server/fleet/datastore.go
+++ b/server/fleet/datastore.go
@@ -883,8 +883,6 @@ type Datastore interface {
// GetHostDiskEncryptionKey returns the encryption key information for a given host
GetHostDiskEncryptionKey(ctx context.Context, hostID uint) (*HostDiskEncryptionKey, error)
- SetDiskEncryptionResetStatus(ctx context.Context, hostID uint, status bool) error
-
// GetHostCertAssociationsToExpire retrieves host certificate
// associations that are close to expire and don't have a renewal in
// progress based on the provided arguments.
diff --git a/server/fleet/hosts.go b/server/fleet/hosts.go
index 8dc1eca42e..ac0e86723d 100644
--- a/server/fleet/hosts.go
+++ b/server/fleet/hosts.go
@@ -321,10 +321,6 @@ type Host struct {
// omitted if we don't have encryption information yet.
DiskEncryptionEnabled *bool `json:"disk_encryption_enabled,omitempty" db:"disk_encryption_enabled" csv:"-"`
- // DiskEncryptionResetRequested is only fetched when loading a host by
- // orbit_node_key, and so it's not used in the UI.
- DiskEncryptionResetRequested *bool `json:"disk_encryption_reset_requested,omitempty" db:"disk_encryption_reset_requested" csv:"-"`
-
HostIssues `json:"issues,omitempty" csv:"-"`
// DeviceMapping is in fact included in the CSV export, but it is not directly
@@ -574,11 +570,7 @@ func (d *MDMHostData) PopulateOSSettingsAndMacOSSettings(profiles []HostMDMApple
// but either we didn't get an encryption key or we're not able to
// decrypt the key we've got
settings.DiskEncryption = DiskEncryptionActionRequired.addrOf()
- if *d.rawDecryptable == 0 {
- settings.ActionRequired = ActionRequiredRotateKey.addrOf()
- } else {
- settings.ActionRequired = ActionRequiredLogOut.addrOf()
- }
+ settings.ActionRequired = ActionRequiredRotateKey.addrOf()
} else {
// if [a FileVault profile is pending to be installed or] the
// matching row in host_disk_encryption_keys has a field decryptable
@@ -1230,19 +1222,3 @@ func IsEligibleForDEPMigration(host *Host, mdmInfo *HostMDM, isConnectedToFleetM
// the checkout message from the host.
(!isConnectedToFleetMDM || mdmInfo.Name != WellKnownMDMFleet)
}
-
-// IsEligibleForBitLockerEncryption checks if the host needs to enforce disk
-// encryption using Fleet MDM features.
-func IsEligibleForBitLockerEncryption(h *Host, mdmInfo *HostMDM, isConnectedToFleetMDM bool) bool {
- isServer := mdmInfo != nil && mdmInfo.IsServer
- isWindows := h.FleetPlatform() == "windows"
- needsEncryption := h.DiskEncryptionEnabled != nil && !*h.DiskEncryptionEnabled
- encryptedWithoutKey := h.DiskEncryptionEnabled != nil && *h.DiskEncryptionEnabled && !h.MDM.EncryptionKeyAvailable
-
- return isWindows &&
- h.IsOsqueryEnrolled() &&
- isConnectedToFleetMDM &&
- !isServer &&
- mdmInfo != nil &&
- (needsEncryption || encryptedWithoutKey)
-}
diff --git a/server/fleet/hosts_test.go b/server/fleet/hosts_test.go
index e350d701cd..94d0cd40a0 100644
--- a/server/fleet/hosts_test.go
+++ b/server/fleet/hosts_test.go
@@ -214,50 +214,6 @@ func TestMDMEnrollmentStatus(t *testing.T) {
}
}
-func TestIsEligibleForBitLockerEncryption(t *testing.T) {
- require.False(t, IsEligibleForBitLockerEncryption(&Host{}, &HostMDM{}, false))
-
- hostThatNeedsEnforcement := &Host{
- Platform: "windows",
- OsqueryHostID: ptr.String("test"),
- MDM: MDMHostData{
- EncryptionKeyAvailable: false,
- },
- DiskEncryptionEnabled: ptr.Bool(false),
- }
- hostThatNeedsEnforcementMdmInfo := &HostMDM{
- Name: WellKnownMDMFleet,
- Enrolled: true,
- IsServer: false,
- InstalledFromDep: true,
- }
- require.True(t, IsEligibleForBitLockerEncryption(hostThatNeedsEnforcement, hostThatNeedsEnforcementMdmInfo, true))
-
- // macOS hosts are not elegible
- hostThatNeedsEnforcement.Platform = "darwin"
- require.False(t, IsEligibleForBitLockerEncryption(hostThatNeedsEnforcement, hostThatNeedsEnforcementMdmInfo, true))
- hostThatNeedsEnforcement.Platform = "windows"
- require.True(t, IsEligibleForBitLockerEncryption(hostThatNeedsEnforcement, hostThatNeedsEnforcementMdmInfo, true))
-
- // hosts with disk encryption already enabled are elegible only if we
- // can't decrypt the key
- hostThatNeedsEnforcement.DiskEncryptionEnabled = ptr.Bool(true)
- require.True(t, IsEligibleForBitLockerEncryption(hostThatNeedsEnforcement, hostThatNeedsEnforcementMdmInfo, true))
- hostThatNeedsEnforcement.MDM.EncryptionKeyAvailable = true
- require.False(t, IsEligibleForBitLockerEncryption(hostThatNeedsEnforcement, hostThatNeedsEnforcementMdmInfo, true))
-
- hostThatNeedsEnforcement.DiskEncryptionEnabled = ptr.Bool(false)
- hostThatNeedsEnforcement.MDM.EncryptionKeyAvailable = false
- require.True(t, IsEligibleForBitLockerEncryption(hostThatNeedsEnforcement, hostThatNeedsEnforcementMdmInfo, true))
-
- // hosts without MDMinfo are not elegible
- require.False(t, IsEligibleForBitLockerEncryption(hostThatNeedsEnforcement, nil, true))
- require.True(t, IsEligibleForBitLockerEncryption(hostThatNeedsEnforcement, hostThatNeedsEnforcementMdmInfo, true))
-
- // hosts that are not enrolled in MDM are not elegible
- require.False(t, IsEligibleForBitLockerEncryption(hostThatNeedsEnforcement, hostThatNeedsEnforcementMdmInfo, false))
-}
-
func TestIsEligibleForDEPMigration(t *testing.T) {
testCases := []struct {
name string
diff --git a/server/fleet/service.go b/server/fleet/service.go
index 5375b3d4bd..0148eb4162 100644
--- a/server/fleet/service.go
+++ b/server/fleet/service.go
@@ -928,8 +928,6 @@ type Service interface {
// for all hosts that are already marked as failing.
ResetAutomation(ctx context.Context, teamIDs, policyIDs []uint) error
- RequestEncryptionKeyRotation(ctx context.Context, hostID uint) error
-
///////////////////////////////////////////////////////////////////////////////
// Windows MDM
diff --git a/server/mock/datastore_mock.go b/server/mock/datastore_mock.go
index 3fdb56d30b..7c9523da38 100644
--- a/server/mock/datastore_mock.go
+++ b/server/mock/datastore_mock.go
@@ -618,8 +618,6 @@ type SetHostsDiskEncryptionKeyStatusFunc func(ctx context.Context, hostIDs []uin
type GetHostDiskEncryptionKeyFunc func(ctx context.Context, hostID uint) (*fleet.HostDiskEncryptionKey, error)
-type SetDiskEncryptionResetStatusFunc func(ctx context.Context, hostID uint, status bool) error
-
type GetHostCertAssociationsToExpireFunc func(ctx context.Context, expiryDays int, limit int) ([]fleet.SCEPIdentityAssociation, error)
type SetCommandForPendingSCEPRenewalFunc func(ctx context.Context, assocs []fleet.SCEPIdentityAssociation, cmdUUID string) error
@@ -1910,9 +1908,6 @@ type DataStore struct {
GetHostDiskEncryptionKeyFunc GetHostDiskEncryptionKeyFunc
GetHostDiskEncryptionKeyFuncInvoked bool
- SetDiskEncryptionResetStatusFunc SetDiskEncryptionResetStatusFunc
- SetDiskEncryptionResetStatusFuncInvoked bool
-
GetHostCertAssociationsToExpireFunc GetHostCertAssociationsToExpireFunc
GetHostCertAssociationsToExpireFuncInvoked bool
@@ -4597,13 +4592,6 @@ func (s *DataStore) GetHostDiskEncryptionKey(ctx context.Context, hostID uint) (
return s.GetHostDiskEncryptionKeyFunc(ctx, hostID)
}
-func (s *DataStore) SetDiskEncryptionResetStatus(ctx context.Context, hostID uint, status bool) error {
- s.mu.Lock()
- s.SetDiskEncryptionResetStatusFuncInvoked = true
- s.mu.Unlock()
- return s.SetDiskEncryptionResetStatusFunc(ctx, hostID, status)
-}
-
func (s *DataStore) GetHostCertAssociationsToExpire(ctx context.Context, expiryDays int, limit int) ([]fleet.SCEPIdentityAssociation, error) {
s.mu.Lock()
s.GetHostCertAssociationsToExpireFuncInvoked = true
diff --git a/server/service/devices.go b/server/service/devices.go
index 31e9889cb5..288fbb304e 100644
--- a/server/service/devices.go
+++ b/server/service/devices.go
@@ -564,41 +564,6 @@ func (svc *Service) GetDeviceMDMAppleEnrollmentProfile(ctx context.Context) ([]b
return signed, nil
}
-////////////////////////////////////////////////////////////////////////////////
-// Request a disk encryption reset
-////////////////////////////////////////////////////////////////////////////////
-
-type rotateEncryptionKeyRequest struct {
- Token string `url:"token"`
-}
-
-func (r *rotateEncryptionKeyRequest) deviceAuthToken() string {
- return r.Token
-}
-
-type rotateEncryptionKeyResponse struct {
- Err error `json:"error,omitempty"`
-}
-
-func (r rotateEncryptionKeyResponse) error() error { return r.Err }
-
-func rotateEncryptionKeyEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
- host, ok := hostctx.FromContext(ctx)
- if !ok {
- err := ctxerr.Wrap(ctx, fleet.NewAuthRequiredError("internal error: missing host from request context"))
- return rotateEncryptionKeyResponse{Err: err}, nil
- }
-
- if err := svc.RequestEncryptionKeyRotation(ctx, host.ID); err != nil {
- return rotateEncryptionKeyResponse{Err: err}, nil
- }
- return rotateEncryptionKeyResponse{}, nil
-}
-
-func (svc *Service) RequestEncryptionKeyRotation(ctx context.Context, hostID uint) error {
- return fleet.ErrMissingLicense
-}
-
////////////////////////////////////////////////////////////////////////////////
// Signal start of mdm migration on a device
////////////////////////////////////////////////////////////////////////////////
diff --git a/server/service/handler.go b/server/service/handler.go
index caaf16ddb4..3d606bd159 100644
--- a/server/service/handler.go
+++ b/server/service/handler.go
@@ -799,10 +799,6 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
errorLimiter.Limit("get_device_mdm", desktopQuota),
).GET("/api/_version_/fleet/device/{token}/mdm/apple/manual_enrollment_profile", getDeviceMDMManualEnrollProfileEndpoint, getDeviceMDMManualEnrollProfileRequest{})
- demdm.WithCustomMiddleware(
- errorLimiter.Limit("post_device_rotate_encryption_key", desktopQuota),
- ).POST("/api/_version_/fleet/device/{token}/rotate_encryption_key", rotateEncryptionKeyEndpoint, rotateEncryptionKeyRequest{})
-
demdm.WithCustomMiddleware(
errorLimiter.Limit("post_device_migrate_mdm", desktopQuota),
).POST("/api/_version_/fleet/device/{token}/migrate_mdm", migrateMDMDeviceEndpoint, deviceMigrateMDMRequest{})
diff --git a/server/service/hosts_test.go b/server/service/hosts_test.go
index 67492d1c4d..b0f6dc7529 100644
--- a/server/service/hosts_test.go
+++ b/server/service/hosts_test.go
@@ -137,7 +137,7 @@ func TestHostDetailsMDMAppleDiskEncryption(t *testing.T) {
OperationType: fleet.MDMOperationTypeInstall,
},
fleet.DiskEncryptionActionRequired,
- fleet.ActionRequiredLogOut,
+ fleet.ActionRequiredRotateKey,
&fleet.MDMDeliveryPending,
},
{
@@ -1857,3 +1857,196 @@ func TestBulkOperationFilterValidation(t *testing.T) {
})
}
}
+
+func TestSetDiskEncryptionNotifications(t *testing.T) {
+ ds := new(mock.Store)
+ ctx := context.Background()
+ svc := &Service{ds: ds}
+
+ tests := []struct {
+ name string
+ host *fleet.Host
+ appConfig *fleet.AppConfig
+ diskEncryptionConfigured bool
+ isConnectedToFleetMDM bool
+ mdmInfo *fleet.HostMDM
+ getHostDiskEncryptionKey func(context.Context, uint) (*fleet.HostDiskEncryptionKey, error)
+ expectedNotifications *fleet.OrbitConfigNotifications
+ expectedError bool
+ }{
+ {
+ name: "no MDM configured",
+ host: &fleet.Host{ID: 1, Platform: "darwin"},
+ appConfig: &fleet.AppConfig{
+ MDM: fleet.MDM{EnabledAndConfigured: false},
+ },
+ diskEncryptionConfigured: true,
+ isConnectedToFleetMDM: true,
+ mdmInfo: nil,
+ getHostDiskEncryptionKey: nil,
+ expectedNotifications: &fleet.OrbitConfigNotifications{},
+ expectedError: false,
+ },
+ {
+ name: "not connected to Fleet MDM",
+ host: &fleet.Host{ID: 1, Platform: "darwin"},
+ appConfig: &fleet.AppConfig{
+ MDM: fleet.MDM{EnabledAndConfigured: true},
+ },
+ diskEncryptionConfigured: true,
+ isConnectedToFleetMDM: false,
+ mdmInfo: nil,
+ getHostDiskEncryptionKey: nil,
+ expectedNotifications: &fleet.OrbitConfigNotifications{},
+ expectedError: false,
+ },
+ {
+ name: "host not enrolled in osquery",
+ host: &fleet.Host{ID: 1, Platform: "darwin", OsqueryHostID: nil},
+ appConfig: &fleet.AppConfig{
+ MDM: fleet.MDM{EnabledAndConfigured: true},
+ },
+ diskEncryptionConfigured: true,
+ isConnectedToFleetMDM: true,
+ mdmInfo: nil,
+ getHostDiskEncryptionKey: nil,
+ expectedNotifications: &fleet.OrbitConfigNotifications{},
+ expectedError: false,
+ },
+ {
+ name: "disk encryption not configured",
+ host: &fleet.Host{ID: 1, Platform: "darwin"},
+ appConfig: &fleet.AppConfig{
+ MDM: fleet.MDM{EnabledAndConfigured: true},
+ },
+ diskEncryptionConfigured: false,
+ isConnectedToFleetMDM: true,
+ mdmInfo: nil,
+ getHostDiskEncryptionKey: nil,
+ expectedNotifications: &fleet.OrbitConfigNotifications{},
+ expectedError: false,
+ },
+ {
+ name: "darwin with decryptable key",
+ host: &fleet.Host{ID: 1, Platform: "darwin"},
+ appConfig: &fleet.AppConfig{
+ MDM: fleet.MDM{EnabledAndConfigured: true},
+ },
+ diskEncryptionConfigured: true,
+ isConnectedToFleetMDM: true,
+ mdmInfo: nil,
+ getHostDiskEncryptionKey: func(ctx context.Context, id uint) (*fleet.HostDiskEncryptionKey, error) {
+ return &fleet.HostDiskEncryptionKey{Decryptable: ptr.Bool(true)}, nil
+ },
+ expectedNotifications: &fleet.OrbitConfigNotifications{
+ RotateDiskEncryptionKey: false,
+ },
+ expectedError: false,
+ },
+ {
+ name: "windows server with no encryption needed",
+ host: &fleet.Host{ID: 1, Platform: "windows", DiskEncryptionEnabled: ptr.Bool(true)},
+ appConfig: &fleet.AppConfig{
+ MDM: fleet.MDM{EnabledAndConfigured: true},
+ },
+ diskEncryptionConfigured: true,
+ isConnectedToFleetMDM: true,
+ mdmInfo: &fleet.HostMDM{IsServer: true},
+ getHostDiskEncryptionKey: func(ctx context.Context, id uint) (*fleet.HostDiskEncryptionKey, error) {
+ return nil, newNotFoundError()
+ },
+ expectedNotifications: &fleet.OrbitConfigNotifications{
+ EnforceBitLockerEncryption: false,
+ },
+ expectedError: false,
+ },
+ {
+ name: "windows with encryption enabled but key missing",
+ host: &fleet.Host{ID: 1, Platform: "windows", DiskEncryptionEnabled: ptr.Bool(true)},
+ appConfig: &fleet.AppConfig{
+ MDM: fleet.MDM{EnabledAndConfigured: true},
+ },
+ diskEncryptionConfigured: true,
+ isConnectedToFleetMDM: true,
+ mdmInfo: &fleet.HostMDM{IsServer: false},
+ getHostDiskEncryptionKey: func(ctx context.Context, id uint) (*fleet.HostDiskEncryptionKey, error) {
+ return nil, newNotFoundError()
+ },
+ expectedNotifications: &fleet.OrbitConfigNotifications{
+ EnforceBitLockerEncryption: true,
+ },
+ expectedError: false,
+ },
+ {
+ name: "darwin with missing encryption key",
+ host: &fleet.Host{ID: 1, Platform: "darwin"},
+ appConfig: &fleet.AppConfig{
+ MDM: fleet.MDM{EnabledAndConfigured: true},
+ },
+ diskEncryptionConfigured: true,
+ isConnectedToFleetMDM: true,
+ mdmInfo: nil,
+ getHostDiskEncryptionKey: func(ctx context.Context, id uint) (*fleet.HostDiskEncryptionKey, error) {
+ return nil, newNotFoundError()
+ },
+ expectedNotifications: &fleet.OrbitConfigNotifications{
+ RotateDiskEncryptionKey: false,
+ },
+ expectedError: false,
+ },
+ {
+ name: "windows with encryption key and not decryptable",
+ host: &fleet.Host{ID: 1, Platform: "windows", DiskEncryptionEnabled: ptr.Bool(true)},
+ appConfig: &fleet.AppConfig{
+ MDM: fleet.MDM{EnabledAndConfigured: true},
+ },
+ diskEncryptionConfigured: true,
+ isConnectedToFleetMDM: true,
+ mdmInfo: &fleet.HostMDM{IsServer: false},
+ getHostDiskEncryptionKey: func(ctx context.Context, id uint) (*fleet.HostDiskEncryptionKey, error) {
+ return &fleet.HostDiskEncryptionKey{Decryptable: ptr.Bool(false)}, nil
+ },
+ expectedNotifications: &fleet.OrbitConfigNotifications{
+ EnforceBitLockerEncryption: true,
+ },
+ expectedError: false,
+ },
+ {
+ name: "windows with enforce BitLocker",
+ host: &fleet.Host{ID: 1, Platform: "windows", DiskEncryptionEnabled: ptr.Bool(false)},
+ appConfig: &fleet.AppConfig{
+ MDM: fleet.MDM{EnabledAndConfigured: true},
+ },
+ diskEncryptionConfigured: true,
+ isConnectedToFleetMDM: true,
+ mdmInfo: &fleet.HostMDM{IsServer: false},
+ getHostDiskEncryptionKey: func(ctx context.Context, id uint) (*fleet.HostDiskEncryptionKey, error) {
+ return nil, newNotFoundError()
+ },
+ expectedNotifications: &fleet.OrbitConfigNotifications{
+ EnforceBitLockerEncryption: true,
+ },
+ expectedError: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if tt.getHostDiskEncryptionKey != nil {
+ ds.GetHostDiskEncryptionKeyFunc = tt.getHostDiskEncryptionKey
+ }
+ ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
+ return tt.appConfig, nil
+ }
+
+ notifs := &fleet.OrbitConfigNotifications{}
+ err := svc.setDiskEncryptionNotifications(ctx, notifs, tt.host, tt.appConfig, tt.diskEncryptionConfigured, tt.isConnectedToFleetMDM, tt.mdmInfo)
+ if tt.expectedError {
+ require.Error(t, err)
+ } else {
+ require.NoError(t, err)
+ }
+ require.Equal(t, tt.expectedNotifications.RotateDiskEncryptionKey, notifs.RotateDiskEncryptionKey)
+ })
+ }
+}
diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go
index 647f298c29..44b1e95271 100644
--- a/server/service/integration_mdm_test.go
+++ b/server/service/integration_mdm_test.go
@@ -1729,7 +1729,7 @@ func (s *integrationMDMTestSuite) TestMDMAppleHostDiskEncryption() {
require.NotNil(t, getHostResp.Host.MDM.MacOSSettings.DiskEncryption)
require.Equal(t, fleet.DiskEncryptionActionRequired, *getHostResp.Host.MDM.MacOSSettings.DiskEncryption)
require.NotNil(t, getHostResp.Host.MDM.MacOSSettings.ActionRequired)
- require.Equal(t, fleet.ActionRequiredLogOut, *getHostResp.Host.MDM.MacOSSettings.ActionRequired)
+ require.Equal(t, fleet.ActionRequiredRotateKey, *getHostResp.Host.MDM.MacOSSettings.ActionRequired)
require.NotNil(t, getHostResp.Host.MDM.OSSettings)
require.NotNil(t, getHostResp.Host.MDM.OSSettings.DiskEncryption.Status)
require.Equal(t, fleet.DiskEncryptionActionRequired, *getHostResp.Host.MDM.OSSettings.DiskEncryption.Status)
@@ -2521,36 +2521,6 @@ func (s *integrationMDMTestSuite) TestEnrollOrbitAfterDEPSync() {
require.Equal(t, h.ID, got.ID)
}
-func (s *integrationMDMTestSuite) TestDiskEncryptionRotation() {
- t := s.T()
- h := createOrbitEnrolledHost(t, "darwin", "h", s.ds)
-
- // false by default
- resp := orbitGetConfigResponse{}
- s.DoJSON("POST", "/api/fleet/orbit/config", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *h.OrbitNodeKey)), http.StatusOK, &resp)
- require.False(t, resp.Notifications.RotateDiskEncryptionKey)
-
- // create an auth token for h
- token := "much_valid"
- mysql.ExecAdhocSQL(t, s.ds, func(db sqlx.ExtContext) error {
- _, err := db.ExecContext(context.Background(), `INSERT INTO host_device_auth (host_id, token) VALUES (?, ?)`, h.ID, token)
- return err
- })
-
- tokRes := s.DoRawNoAuth("POST", "/api/latest/fleet/device/"+token+"/rotate_encryption_key", nil, http.StatusOK)
- tokRes.Body.Close()
-
- // true after the POST request
- resp = orbitGetConfigResponse{}
- s.DoJSON("POST", "/api/fleet/orbit/config", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *h.OrbitNodeKey)), http.StatusOK, &resp)
- require.True(t, resp.Notifications.RotateDiskEncryptionKey)
-
- // false on following requests
- resp = orbitGetConfigResponse{}
- s.DoJSON("POST", "/api/fleet/orbit/config", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *h.OrbitNodeKey)), http.StatusOK, &resp)
- require.False(t, resp.Notifications.RotateDiskEncryptionKey)
-}
-
func (s *integrationMDMTestSuite) TestFleetdConfiguration() {
t := s.T()
s.assertConfigProfilesByIdentifier(nil, mobileconfig.FleetdConfigPayloadIdentifier, false)
diff --git a/server/service/orbit.go b/server/service/orbit.go
index 0afa2631e4..40f64ef2d3 100644
--- a/server/service/orbit.go
+++ b/server/service/orbit.go
@@ -207,15 +207,6 @@ func (svc *Service) GetOrbitConfig(ctx context.Context) (fleet.OrbitConfig, erro
notifs.NeedsMDMMigration = true
}
- if host.DiskEncryptionResetRequested != nil && *host.DiskEncryptionResetRequested {
- notifs.RotateDiskEncryptionKey = true
-
- // Since this is an user initiated action, we disable
- // the flag when we deliver the notification to Orbit
- if err := svc.ds.SetDiskEncryptionResetStatus(ctx, host.ID, false); err != nil {
- return fleet.OrbitConfig{}, err
- }
- }
}
// set the host's orbit notifications for Windows MDM
@@ -309,9 +300,17 @@ func (svc *Service) GetOrbitConfig(ctx context.Context) (fleet.OrbitConfig, erro
}
}
- if mdmConfig.EnableDiskEncryption &&
- fleet.IsEligibleForBitLockerEncryption(host, mdmInfo, isConnectedToFleetMDM) {
- notifs.EnforceBitLockerEncryption = true
+ err = svc.setDiskEncryptionNotifications(
+ ctx,
+ ¬ifs,
+ host,
+ appConfig,
+ mdmConfig.EnableDiskEncryption,
+ isConnectedToFleetMDM,
+ mdmInfo,
+ )
+ if err != nil {
+ return fleet.OrbitConfig{}, ctxerr.Wrap(ctx, err, "setting team disk encryption notifications")
}
var updateChannels *fleet.OrbitUpdateChannels
@@ -371,10 +370,17 @@ func (svc *Service) GetOrbitConfig(ctx context.Context) (fleet.OrbitConfig, erro
}
}
- if appConfig.MDM.WindowsEnabledAndConfigured &&
- appConfig.MDM.EnableDiskEncryption.Value &&
- fleet.IsEligibleForBitLockerEncryption(host, mdmInfo, isConnectedToFleetMDM) {
- notifs.EnforceBitLockerEncryption = true
+ err = svc.setDiskEncryptionNotifications(
+ ctx,
+ ¬ifs,
+ host,
+ appConfig,
+ appConfig.MDM.EnableDiskEncryption.Value,
+ isConnectedToFleetMDM,
+ mdmInfo,
+ )
+ if err != nil {
+ return fleet.OrbitConfig{}, ctxerr.Wrap(ctx, err, "setting no-team disk encryption notifications")
}
var updateChannels *fleet.OrbitUpdateChannels
@@ -396,6 +402,46 @@ func (svc *Service) GetOrbitConfig(ctx context.Context) (fleet.OrbitConfig, erro
}, nil
}
+func (svc *Service) setDiskEncryptionNotifications(
+ ctx context.Context,
+ notifs *fleet.OrbitConfigNotifications,
+ host *fleet.Host,
+ appConfig *fleet.AppConfig,
+ diskEncryptionConfigured bool,
+ isConnectedToFleetMDM bool,
+ mdmInfo *fleet.HostMDM,
+) error {
+ anyMDMConfigured := appConfig.MDM.EnabledAndConfigured || appConfig.MDM.WindowsEnabledAndConfigured
+ if !anyMDMConfigured ||
+ !isConnectedToFleetMDM ||
+ !host.IsOsqueryEnrolled() ||
+ !diskEncryptionConfigured {
+ return nil
+ }
+
+ encryptionKey, err := svc.ds.GetHostDiskEncryptionKey(ctx, host.ID)
+ if err != nil {
+ if !fleet.IsNotFound(err) {
+ return ctxerr.Wrap(ctx, err, "fetching host disk encryption key")
+ }
+ }
+
+ switch host.FleetPlatform() {
+ case "darwin":
+ notifs.RotateDiskEncryptionKey = encryptionKey.Decryptable != nil && !*encryptionKey.Decryptable
+ case "windows":
+ isServer := mdmInfo != nil && mdmInfo.IsServer
+ needsEncryption := host.DiskEncryptionEnabled != nil && !*host.DiskEncryptionEnabled
+ keyWasDecrypted := encryptionKey != nil && encryptionKey.Decryptable != nil && *encryptionKey.Decryptable
+ encryptedWithoutKey := host.DiskEncryptionEnabled != nil && *host.DiskEncryptionEnabled && !keyWasDecrypted
+ notifs.EnforceBitLockerEncryption = !isServer &&
+ mdmInfo != nil &&
+ (needsEncryption || encryptedWithoutKey)
+ }
+
+ return nil
+}
+
// filterExtensionsForHost filters a extensions configuration depending on the host platform and label membership.
//
// If all extensions are filtered, then it returns (nil, nil) (Orbit expects empty extensions if there
diff --git a/tools/tuf/README.md b/tools/tuf/README.md
index 9f30f296f2..3915fdb892 100644
--- a/tools/tuf/README.md
+++ b/tools/tuf/README.md
@@ -225,6 +225,19 @@ make nudge-app-tar-gz version=1.1.10.81462 out-path=.
fleetctl updates add --target /path/to/macos/nudge.app.tar.gz --platform macos --name nudge --version 1.1.10.81462 -t edge
```
+#### Releasing `Escrow Buddy` to `stable`
+
+> `releaser.sh` doesn't support `Escrow Buddy` yet.
+> macOS only component
+
+The `Escrow Buddy` pkg installer can be generated by running:
+```sh
+make escrow-buddy-pkg version=1.0.0 out-path=.
+```
+```sh
+fleetctl updates add --target /path/to/escrowBuddy.pkg --platform macos --name escrowBuddy --version 1.0.0 -t stable
+```
+
#### Updating timestamp
```sh
diff --git a/tools/tuf/test/create_repository.sh b/tools/tuf/test/create_repository.sh
index 86fa0f5e17..0f13a21357 100755
--- a/tools/tuf/test/create_repository.sh
+++ b/tools/tuf/test/create_repository.sh
@@ -28,8 +28,7 @@ SYSTEMS=${SYSTEMS:-macos linux linux-arm64 windows}
echo "Generating packages for $SYSTEMS"
NUDGE_VERSION=stable
-SWIFT_DIALOG_MACOS_APP_VERSION=2.2.1
-SWIFT_DIALOG_MACOS_APP_BUILD_VERSION=4591
+ESCROW_BUDDY_PKG_VERSION=1.0.0
if [[ -z "$OSQUERY_VERSION" ]]; then
OSQUERY_VERSION=5.12.2
@@ -168,6 +167,20 @@ for system in $SYSTEMS; do
rm swiftDialog.app.tar.gz
fi
+ # Add Escrow Buddy on macos (if enabled).
+ if [[ $system == "macos" && -n "$ESCROW_BUDDY" ]]; then
+ make escrow-buddy-pkg version=$ESCROW_BUDDY_PKG_VERSION out-path=.
+
+ ./build/fleetctl updates add \
+ --path $TUF_PATH \
+ --target escrowBuddy.pkg \
+ --platform macos \
+ --name escrowBuddy \
+ --version 42.0.0 -t 42.0 -t 42 -t stable
+ rm escrowBuddy.pkg
+ fi
+
+
# Add Fleet Desktop application on windows (if enabled).
if [[ $system == "windows" && -n "$FLEET_DESKTOP" ]]; then
FLEET_DESKTOP_VERSION=42.0.0 \
From 8b5094ff3787984789666f734b660df03e70c553 Mon Sep 17 00:00:00 2001
From: Grant Bilstad <82750216+pacamaster@users.noreply.github.com>
Date: Wed, 31 Jul 2024 16:10:46 -0600
Subject: [PATCH 12/88] Fix link for fleetctl install (#20835)
---
docs/Using Fleet/enroll-hosts.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/Using Fleet/enroll-hosts.md b/docs/Using Fleet/enroll-hosts.md
index 952383ec57..6112eec51e 100644
--- a/docs/Using Fleet/enroll-hosts.md
+++ b/docs/Using Fleet/enroll-hosts.md
@@ -12,7 +12,7 @@ Fleet supports the [latest version of osquery](https://github.com/osquery/osquer
## CLI
-> You must have `fleetctl` installed. [Learn how to install `fleetctl`](https://fleetdm.com/fleetctl-preview).
+> You must have `fleetctl` installed. [Learn how to install `fleetctl`](https://fleetdm.com/docs/using-fleet/fleetctl-cli#installing-fleetctl).
The `fleetctl package` command is used to generate Fleet's agent (fleetd).
From 6fd628463dbf5306cbddc77b407b0a79715d2dda Mon Sep 17 00:00:00 2001
From: Eric
Date: Wed, 31 Jul 2024 17:25:18 -0500
Subject: [PATCH 13/88] Website: fix pricing table filtering (#20912)
Changes:
- Fixed the IT and Security filtering on the pricing features table.
---
website/api/controllers/view-pricing.js | 53 +++++++++++++------------
1 file changed, 28 insertions(+), 25 deletions(-)
diff --git a/website/api/controllers/view-pricing.js b/website/api/controllers/view-pricing.js
index e3c914e0ae..7d07b6b770 100644
--- a/website/api/controllers/view-pricing.js
+++ b/website/api/controllers/view-pricing.js
@@ -44,36 +44,39 @@ module.exports = {
pricingTable.push(allFeaturesInThisCategory);
}
- let pricingTableForSecurity = _.filter(pricingTable, (category)=>{
- return category.categoryName !== 'Device management' && (category.usualDepartment === 'Security' || category.usualDepartment === undefined);
- });
+ let pricingTableForSecurity = [];
let categoryOrderForSecurityPricingTable = ['Support', 'Deployment', 'Integrations', 'Endpoint operations', 'Vulnerability management'];
- // Sort the security-focused pricing table from the order of the elements in the categoryOrderForSecurityPricingTable array.
- pricingTableForSecurity.sort((a, b)=>{
- // If there is a category that is not in the list above, sort it to the end of the list.
- if(categoryOrderForSecurityPricingTable.indexOf(a.categoryName) === -1){
- return 1;
- } else if(categoryOrderForSecurityPricingTable.indexOf(b.categoryName) === -1) {
- return -1;
- }
- return categoryOrderForSecurityPricingTable.indexOf(a.categoryName) - categoryOrderForSecurityPricingTable.indexOf(b.categoryName);
- });
+ for(let category of categoryOrderForSecurityPricingTable) {
+ // Get all the features in that have a pricingTableFeatures array that contains this category.
+ let featuresInThisCategory = _.filter(pricingTableFeatures, (feature)=>{
+ return _.contains(feature.pricingTableCategories, category) && (feature.usualDepartment === 'Security' || feature.usualDepartment === undefined);
+ });
+ // Build a dictionary containing the category name, and all features in the category
+ let allSecurityFeaturesInThisCategory = {
+ categoryName: category,
+ features: featuresInThisCategory,
+ };
+ // Add the dictionaries to the arrays that we'll use to build the features table.
+ pricingTableForSecurity.push(allSecurityFeaturesInThisCategory);
+ }
- let pricingTableForIt = _.filter(pricingTable, (category)=>{
- return category.categoryName !== 'Vulnerability management' && (category.usualDepartment === 'Security' || category.usualDepartment === undefined);
- });
let categoryOrderForITPricingTable = [ 'Deployment','Device management', 'Endpoint operations', 'Integrations', 'Support'];
+ let pricingTableForIt = [];
// Sort the IT-focused pricing table from the order of the elements in the categoryOrderForITPricingTable array.
- pricingTableForIt.sort((a, b)=>{
- // If there is a category that is not in the list above, sort it to the end of the list.
- if(categoryOrderForITPricingTable.indexOf(a.categoryName) === -1){
- return 1;
- } else if(categoryOrderForITPricingTable.indexOf(b.categoryName) === -1) {
- return -1;
- }
- return categoryOrderForITPricingTable.indexOf(a.categoryName) - categoryOrderForITPricingTable.indexOf(b.categoryName);
- });
+ for(let category of categoryOrderForITPricingTable) {
+ // Get all the features in that have a pricingTableFeatures array that contains this category.
+ let featuresInThisCategory = _.filter(pricingTableFeatures, (feature)=>{
+ return _.contains(feature.pricingTableCategories, category) && (feature.usualDepartment === 'IT' || feature.usualDepartment === undefined);
+ });
+ // Build a dictionary containing the category name, and all features in the category, sorting premium features to the bottom of the list.
+ let allItFeaturesInThisCategory = {
+ categoryName: category,
+ features: featuresInThisCategory,
+ };
+ // Add the dictionaries to the arrays that we'll use to build the features table.
+ pricingTableForIt.push(allItFeaturesInThisCategory);
+ }
// Respond with view.
From ba2f79e1c4f5bb2d47aca419fd854ebb22ea35f3 Mon Sep 17 00:00:00 2001
From: Eric
Date: Wed, 31 Jul 2024 17:40:41 -0500
Subject: [PATCH 14/88] Website: personalize /better page for Secure Frame
users (#20890)
Related to: https://github.com/fleetdm/confidential/issues/7227
Changes:
- Added a banner to the /better page that is visible to users who visit
it with a `?utm_content=secureframe` query param.
- Updated the `` component to support customizing
text and arrow color.
---
website/api/controllers/view-transparency.js | 4 +--
.../images/logo-secureframe-46x48@2x.png | Bin 0 -> 2053 bytes
.../animated-arrow-button.component.js | 8 +++--
website/assets/js/pages/transparency.page.js | 1 +
.../animated-arrow-button.component.less | 2 +-
website/assets/styles/pages/transparency.less | 31 ++++++++++++++++++
website/views/pages/transparency.ejs | 9 ++++-
7 files changed, 48 insertions(+), 7 deletions(-)
create mode 100644 website/assets/images/logo-secureframe-46x48@2x.png
diff --git a/website/api/controllers/view-transparency.js b/website/api/controllers/view-transparency.js
index c079ed5300..7267792026 100644
--- a/website/api/controllers/view-transparency.js
+++ b/website/api/controllers/view-transparency.js
@@ -6,7 +6,6 @@ module.exports = {
description: 'Display "Transparency" page.',
-
exits: {
success: {
@@ -17,9 +16,8 @@ module.exports = {
fn: async function () {
-
// Respond with view.
- return {};
+ return {showSecureframeBanner: this.req.param('utm_content') === 'secureframe'};
}
diff --git a/website/assets/images/logo-secureframe-46x48@2x.png b/website/assets/images/logo-secureframe-46x48@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..86107888f9aa26af557972d36d6d9b5ddad47c98
GIT binary patch
literal 2053
zcmV+g2>SPlP)0D|OQzr+NiI7HK&=IkH*!^b
zq9AYax7|nSQK$~;uwmG8bA5ZH5LyV>0zf`B{mY?>Y(#Kt$-0e79U^>dDTIh)t%9@A
zxvo7A7gSA}T%2+5~@m^_GS1D8x$m87~
z;HKYicKT2OM1rqFY%4PUL23O(Rm7c#SMXKEwnD?}e)ClqB7PrE(a3qh8)QbsnrBhy
zN%XQGQk;DX5FYwbw)sI}-i%_;ve2bCSMn!>(WgSyi+=Ou0wQx+=u({D4uM=q?P=aR?Acw@>JKz}&h%9Bsr4)!t$c^kZqu4TLES&1uG&>`=ZX1ad6WdL>
zHOPU{l$+jHfTf;pQ#g1rA-J(_#5M?_3q$es$!7>8xqRC~1*bIS#$p?Rt1;zPOQtMb
zw?OVnvVQZl0|DIC&Jn6xWtS8?ljH?ljG}6vD7oR%KooltBREGVU?K(q
zh#w`x_?YBIIuMz_S$It*wjme!?gFKOO5g-HJ$_!CJMm5)&<&XH2Ps5mFkvBC*K}@z9kl1kToGwwPD${P(!o_d@fxrSy
zZ3HCP(m+_Yy6b2gp~#R~q3nJD1xf>DKnIM?M5B~`A{r*VACwIeyq3ZmL2-Fi2Ui)(
zK!}A$)&L1U$lyV!W^ZChZ=uLlD9ryt&>Og$
zrpiEs_7=I^)fHbrRNuaoO!PF@df_|62(C=)u%BF7GdJnFn9_;Pa{z7zZT5F?Lqu*s
ziD!k(bXrJlq;#S$nja1loJba3xpDZu5NPGhE{GoRH(_g@R@xpc#P7fWmfeNNQ8;)C;bLsH={1$Q?(X*QNDi#4Yyo9I?62#8vA
zd-&DV55zTxWwGWwc0}|u?thewbx(OnJ;LGe9Fqs(XhnAOv_uidD0NXCQb(j8g
znB&WDp7RB_CmcM3fYh8){3pBg*FrmBzpiMPeFuDBJ0Kb=yn9HzDHDz(c0xQ_-X6w=85nD
z1mzv2Tmo6=M1$(u!98?40}lTSLSs!-rj|dDmVY(Jg~$wcEdl=;Fz$ik7KqTsr9{DJ
z$)wipmOBwL=$cnRWCC86M7qL}bh(r92a2QMW7x+SWzGBc0RdrnIW|dAnXu4@U2pO9
zOgcVfF&}`v;YEys7g(~Dty`L1T6Z#XQB@Y)b@<|0$YbX=feNthsphK63GUV|{~9Xv
z=m4gLA{CC#rk91S^J9bbmI~=b*+Ep_*1!kd{tAmI@YG@0GJ~@{KK`6EV&J|*?mrB8
zNp{^h`EmN2F)S2NKpB*YTcuE5?4xfXoou3!k
z7)qJ_E*6&tqTpjp?v@z7i66)Hl&xMY9Rrb}&}z6XWf4^Gy78*%8&z2gAFf@l@emQ(
zD{f0kxy(S+LRYsZe)M2{8u!O-YL@@du@8ZaW51Ld8>T4Q_~ps!(vs
zZ9b}}u>Le;hkzIs7KCdu>_$SjV~9~o0o+y$9VkE)JPcPmKj}h2qDv97uI>ka77o7yR{@`9TOTTL4SuQM>{ZF*VlI;EoUJ
-
-
+
+
diff --git a/website/assets/js/pages/transparency.page.js b/website/assets/js/pages/transparency.page.js
index 211b9f441f..be7cfde1f3 100644
--- a/website/assets/js/pages/transparency.page.js
+++ b/website/assets/js/pages/transparency.page.js
@@ -4,6 +4,7 @@ parasails.registerPage('transparency', {
// ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
data: {
//…
+ showSecureframeBanner: false,
},
// ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
diff --git a/website/assets/styles/components/animated-arrow-button.component.less b/website/assets/styles/components/animated-arrow-button.component.less
index 60102c4d7b..61d9b459b8 100644
--- a/website/assets/styles/components/animated-arrow-button.component.less
+++ b/website/assets/styles/components/animated-arrow-button.component.less
@@ -21,7 +21,7 @@
height: 12px;
width: 12px;
fill: none;
- stroke: #FF5C83;
+ // stroke: #FF5C83; « Note: this is overridden by the arrow-color prop;
}
[purpose='arrow-line'] {
opacity: 0;
diff --git a/website/assets/styles/pages/transparency.less b/website/assets/styles/pages/transparency.less
index ac5831ccf0..4e787318f6 100644
--- a/website/assets/styles/pages/transparency.less
+++ b/website/assets/styles/pages/transparency.less
@@ -128,6 +128,31 @@
}
}
}
+ [purpose='secureframe-banner'] {
+ width: 100%;
+ text-align: center;
+ color: #FFF;
+ h2, p {
+ color: #FFF;
+ }
+ h2 {
+ margin-bottom: 16px;
+ }
+ [parasails-component='animated-arrow-button'] {
+ font-weight: 600;
+ }
+ border-radius: 16px;
+ background: #091922;
+ padding: 32px;
+ img {
+ height: 48px;
+ width: 45.474px;
+ margin-bottom: 16px;
+ margin-left: auto;
+ margin-right: auto;
+ }
+ }
+
[purpose='feature-headline'] {
max-width: 510px;
margin-bottom: 80px;
@@ -332,6 +357,12 @@
margin-right: auto;
}
}
+ [purpose='secureframe-banner'] {
+ padding: 32px 16px;
+ h2 {
+ font-size: 24px;
+ }
+ }
}
@media (max-width: 375px) {
diff --git a/website/views/pages/transparency.ejs b/website/views/pages/transparency.ejs
index 12f255e580..4e478e147c 100644
--- a/website/views/pages/transparency.ejs
+++ b/website/views/pages/transparency.ejs
@@ -25,7 +25,14 @@
-
+
+
+
+
Automate compliance. Improve security. Reduce risk.
+
Build trust with customers using automation backed by world-class experts
+
Learn more
+
+
Don’t make me think about IT
From 68f2f58ebefc34f1854f470fe9487156cdfb8ae1 Mon Sep 17 00:00:00 2001
From: Mike McNeil
Date: Wed, 31 Jul 2024 18:24:31 -0500
Subject: [PATCH 15/88] Website: test bringing below-the-fold content higher
(#20913)
Co-authored-by: Eric
---
website/assets/styles/pages/transparency.less | 6 +-
website/views/pages/device-management.ejs | 11 ++-
website/views/pages/endpoint-ops.ejs | 5 +-
website/views/pages/homepage.ejs | 3 -
website/views/pages/transparency.ejs | 70 ++++---------------
.../views/pages/vulnerability-management.ejs | 1 -
.../partials/calendar-banner.partial.ejs | 2 +-
7 files changed, 24 insertions(+), 74 deletions(-)
diff --git a/website/assets/styles/pages/transparency.less b/website/assets/styles/pages/transparency.less
index 4e787318f6..15334ab0a9 100644
--- a/website/assets/styles/pages/transparency.less
+++ b/website/assets/styles/pages/transparency.less
@@ -54,7 +54,7 @@
}
[purpose='hero-text'] {
text-align: center;
- margin-bottom: 40px;
+ margin-bottom: 80px;
h1 {
font-size: 48px;
font-weight: 800;
@@ -193,13 +193,13 @@
[purpose='accordion'] {
- padding-top: 64px;
+
h2 {
padding-bottom: 16px;
}
[purpose='accordion-body'] {
width: 100%;
- padding-top: 64px;
+
[purpose='accordion-item'] {
border-bottom: 1px solid #E2E4EA;
padding-bottom: ;
diff --git a/website/views/pages/device-management.ejs b/website/views/pages/device-management.ejs
index 93215dc33a..f7b0f37a84 100644
--- a/website/views/pages/device-management.ejs
+++ b/website/views/pages/device-management.ejs
@@ -416,7 +416,10 @@
<%/* Shorten the feedback loop section */%>
-
+
+
+
+
Shorten the feedback loop
Spend less time debugging whether changes actually happened.
@@ -426,14 +429,8 @@
Every change to a policy or security control is tracked and auditable in Fleet’s UI, or in your logs.
-
-
-
-
- <%- partial('../partials/calendar-banner.partial.ejs') %>
-
<%/* Scope transparency section */%>
diff --git a/website/views/pages/endpoint-ops.ejs b/website/views/pages/endpoint-ops.ejs
index fa80f91e7d..dc107d725d 100644
--- a/website/views/pages/endpoint-ops.ejs
+++ b/website/views/pages/endpoint-ops.ejs
@@ -123,10 +123,8 @@
Remote-control IT tasks on every kind of computer – even you, Linux.
Write and run scripts remotely, report progress, and replay queued up tasks on computers that went offline.
-
Integrate Google Calendar to install updates and force restarts when your users’ computers are actually free.
-
Reduce human error by automating click-tastic tasks directly on the host with Open Interpreter .*
+
Optionally integrate Google Calendar to make changes when certain users’ devices are actually free.
-
*Coming soon
@@ -228,7 +226,6 @@
- <%- partial('../partials/calendar-banner.partial.ejs') %>
diff --git a/website/views/pages/homepage.ejs b/website/views/pages/homepage.ejs
index f46f1b6c48..e068fde91b 100644
--- a/website/views/pages/homepage.ejs
+++ b/website/views/pages/homepage.ejs
@@ -180,9 +180,6 @@
<% } %>
-
- <%- partial('../partials/calendar-banner.partial.ejs') %>
-
<%/* Integration cards */%>