From 14b654648ea461df908c435aadb6691a9620c9f4 Mon Sep 17 00:00:00 2001 From: Martin Angers Date: Mon, 9 Sep 2024 11:45:38 -0400 Subject: [PATCH 001/385] Fleet App Library: DB schema WIP (#21820) --- ...20240909145426_AddFleetLibraryAppsTable.go | 69 +++++++++++++++++++ ...909145426_AddFleetLibraryAppsTable_test.go | 33 +++++++++ server/datastore/mysql/schema.sql | 32 ++++++++- server/datastore/mysql/scripts.go | 6 +- 4 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 server/datastore/mysql/migrations/tables/20240909145426_AddFleetLibraryAppsTable.go create mode 100644 server/datastore/mysql/migrations/tables/20240909145426_AddFleetLibraryAppsTable_test.go diff --git a/server/datastore/mysql/migrations/tables/20240909145426_AddFleetLibraryAppsTable.go b/server/datastore/mysql/migrations/tables/20240909145426_AddFleetLibraryAppsTable.go new file mode 100644 index 000000000000..7a75dfef709a --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20240909145426_AddFleetLibraryAppsTable.go @@ -0,0 +1,69 @@ +package tables + +import ( + "database/sql" + "fmt" +) + +func init() { + MigrationClient.AddMigration(Up_20240909145426, Down_20240909145426) +} + +func Up_20240909145426(tx *sql.Tx) error { + _, err := tx.Exec(` +CREATE TABLE fleet_library_apps ( + id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, + name varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + -- the "full_token" field from homebrew's JSON API response + -- see e.g. https://formulae.brew.sh/api/cask/1password.json + token varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + version varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + platform varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + installer_url varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + filename varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + -- hash of the binary downloaded from installer_url, allows us to validate we got the right bytes + -- before sending to S3 (and we store installers on S3 under that sha256 hash as identifier). + sha256 varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + + created_at timestamp(6) NULL DEFAULT CURRENT_TIMESTAMP(6), + updated_at timestamp(6) NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + + -- foreign-key ids of the script_contents table. + install_script_content_id int unsigned NOT NULL, + uninstall_script_content_id int unsigned NOT NULL, + + -- The idea with this unique constraint is that if homebrew's version is updated, + -- this new version should update the app in the Fleet library, not create a new entry. + UNIQUE KEY idx_fleet_library_apps_token (token), + CONSTRAINT fk_fleet_library_apps_install_script_content FOREIGN KEY (install_script_content_id) + REFERENCES script_contents (id) ON DELETE CASCADE, + CONSTRAINT fk_fleet_library_apps_uninstall_script_content FOREIGN KEY (uninstall_script_content_id) + REFERENCES script_contents (id) ON DELETE CASCADE +)`) + if err != nil { + return fmt.Errorf("creating fleet_library_apps table: %w", err) + } + + // New column fleet_library_app_id in software_installers allows to keep + // track of which Fleet library app the installer comes from, if any. + // + // NOTE: it is not _crucial_ to have this now, as even if a user adds the + // same installer _name_ in software_installers (without passing by the Fleet + // Library, so this fleet_library_app_id would be NULL), it shouldn't see the + // same _name_ from the Fleet library as available. But it's probably good in + // any case to keep track of this, even if not obviously useful now. + _, err = tx.Exec(` +ALTER TABLE software_installers + ADD COLUMN fleet_library_app_id int unsigned DEFAULT NULL, + ADD FOREIGN KEY fk_software_installers_fleet_library_app_id (fleet_library_app_id) + REFERENCES fleet_library_apps (id) ON DELETE SET NULL + `) + if err != nil { + return fmt.Errorf("failed to alter software_installers to add fleet_library_app_id: %w", err) + } + return nil +} + +func Down_20240909145426(tx *sql.Tx) error { + return nil +} diff --git a/server/datastore/mysql/migrations/tables/20240909145426_AddFleetLibraryAppsTable_test.go b/server/datastore/mysql/migrations/tables/20240909145426_AddFleetLibraryAppsTable_test.go new file mode 100644 index 000000000000..66a80a4ddfae --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20240909145426_AddFleetLibraryAppsTable_test.go @@ -0,0 +1,33 @@ +package tables + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestUp_20240909145426(t *testing.T) { + db := applyUpToPrev(t) + + // create an existing software installer before the migration + execNoErr(t, db, `INSERT INTO script_contents (id, md5_checksum, contents) VALUES (1, 'checksum', 'script content')`) + swiID := execNoErrLastID(t, db, ` + INSERT INTO software_installers + (filename, version, platform, install_script_content_id, storage_id) + VALUES + (?,?,?,?,?)`, "sw1-installer.pkg", "1.2", "darwin", 1, "storage-id1") + + // Apply current migration. + applyNext(t, db) + + var count int + err := db.Get(&count, "SELECT COUNT(*) FROM fleet_library_apps") + require.NoError(t, err) + require.Zero(t, count) + + // column was added and value is NULL + var fleetLibraryAppID *uint + err = db.Get(&fleetLibraryAppID, "SELECT fleet_library_app_id FROM software_installers WHERE id = ?", swiID) + require.NoError(t, err) + require.Nil(t, fleetLibraryAppID) +} diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql index 595878a83909..8e4df14b7272 100644 --- a/server/datastore/mysql/schema.sql +++ b/server/datastore/mysql/schema.sql @@ -202,6 +202,29 @@ CREATE TABLE `eulas` ( /*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `fleet_library_apps` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `version` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `platform` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `installer_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `filename` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `sha256` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` timestamp(6) NULL DEFAULT CURRENT_TIMESTAMP(6), + `updated_at` timestamp(6) NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + `install_script_content_id` int unsigned NOT NULL, + `uninstall_script_content_id` int unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `idx_fleet_library_apps_token` (`token`), + KEY `fk_fleet_library_apps_install_script_content` (`install_script_content_id`), + KEY `fk_fleet_library_apps_uninstall_script_content` (`uninstall_script_content_id`), + CONSTRAINT `fk_fleet_library_apps_install_script_content` FOREIGN KEY (`install_script_content_id`) REFERENCES `script_contents` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_fleet_library_apps_uninstall_script_content` FOREIGN KEY (`uninstall_script_content_id`) REFERENCES `script_contents` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `host_activities` ( `host_id` int unsigned NOT NULL, `activity_id` int unsigned NOT NULL, @@ -1034,9 +1057,9 @@ CREATE TABLE `migration_status_tables` ( `tstamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `id` (`id`) -) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=311 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=312 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; -INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'); +INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240909145426,1,'2020-01-01 01:01:01'); /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `mobile_device_management_solutions` ( @@ -1678,6 +1701,7 @@ CREATE TABLE `software_installers` ( `user_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', `user_email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `fleet_library_app_id` int unsigned DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `idx_software_installers_team_id_title_id` (`global_or_team_id`,`title_id`), KEY `fk_software_installers_title` (`title_id`), @@ -1686,11 +1710,13 @@ CREATE TABLE `software_installers` ( KEY `fk_software_installers_team_id` (`team_id`), KEY `idx_software_installers_platform_title_id` (`platform`,`title_id`), KEY `fk_software_installers_user_id` (`user_id`), + KEY `fk_software_installers_fleet_library_app_id` (`fleet_library_app_id`), CONSTRAINT `fk_software_installers_install_script_content_id` FOREIGN KEY (`install_script_content_id`) REFERENCES `script_contents` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT `fk_software_installers_post_install_script_content_id` FOREIGN KEY (`post_install_script_content_id`) REFERENCES `script_contents` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT `fk_software_installers_team_id` FOREIGN KEY (`team_id`) REFERENCES `teams` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `fk_software_installers_title` FOREIGN KEY (`title_id`) REFERENCES `software_titles` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT `fk_software_installers_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL + CONSTRAINT `fk_software_installers_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL, + CONSTRAINT `software_installers_ibfk_1` FOREIGN KEY (`fleet_library_app_id`) REFERENCES `fleet_library_apps` (`id`) ON DELETE SET NULL ) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET @saved_cs_client = @@character_set_client */; diff --git a/server/datastore/mysql/scripts.go b/server/datastore/mysql/scripts.go index e9acb25254c9..1334a9c866ce 100644 --- a/server/datastore/mysql/scripts.go +++ b/server/datastore/mysql/scripts.go @@ -1111,7 +1111,11 @@ WHERE SELECT 1 FROM scripts WHERE script_content_id = script_contents.id) AND NOT EXISTS ( SELECT 1 FROM software_installers si - WHERE script_contents.id IN (si.install_script_content_id, si.post_install_script_content_id) + WHERE script_contents.id IN (si.install_script_content_id, si.post_install_script_content_id) + ) + AND NOT EXISTS ( + SELECT 1 FROM fleet_library_apps fla + WHERE script_contents.id IN (fla.install_script_content_id, fla.uninstall_script_content_id) ) ` _, err := ds.writer(ctx).ExecContext(ctx, deleteStmt) From 9abd5a59d027eb14d405bc6adc0ec721a0ab7436 Mon Sep 17 00:00:00 2001 From: Martin Angers Date: Tue, 10 Sep 2024 13:55:13 -0400 Subject: [PATCH 002/385] Maintained Apps: define app list, implement ingestion (#21946) --- changes/21773-ingest-fleet-maintained-apps | 1 + pkg/optjson/stringor.go | 31 + pkg/optjson/stringor_test.go | 137 +++ server/datastore/mysql/maintained_apps.go | 55 + .../datastore/mysql/maintained_apps_test.go | 87 ++ ...20240909145426_AddFleetLibraryAppsTable.go | 23 +- server/datastore/mysql/schema.sql | 1 + server/fleet/datastore.go | 4 + server/fleet/maintained_apps.go | 21 + server/mdm/maintainedapps/apps.json | 97 ++ server/mdm/maintainedapps/ingest.go | 220 ++++ server/mdm/maintainedapps/ingest_test.go | 168 +++ .../maintainedapps/testdata/1password.json | 130 +++ .../testdata/adobe-acrobat-reader.json | 97 ++ .../maintainedapps/testdata/box-drive.json | 102 ++ .../testdata/brave-browser.json | 106 ++ .../testdata/cloudflare-warp.json | 95 ++ .../mdm/maintainedapps/testdata/docker.json | 1004 +++++++++++++++++ .../testdata/expected_apps.json | 97 ++ server/mdm/maintainedapps/testdata/figma.json | 114 ++ .../mdm/maintainedapps/testdata/firefox.json | 169 +++ .../testdata/google-chrome.json | 105 ++ .../testdata/microsoft-edge.json | 154 +++ .../testdata/microsoft-excel.json | 441 ++++++++ .../testdata/microsoft-teams.json | 132 +++ .../testdata/microsoft-word.json | 433 +++++++ .../mdm/maintainedapps/testdata/notion.json | 109 ++ server/mdm/maintainedapps/testdata/slack.json | 126 +++ .../maintainedapps/testdata/teamviewer.json | 201 ++++ .../testdata/visual-studio-code.json | 122 ++ .../mdm/maintainedapps/testdata/whatsapp.json | 84 ++ server/mdm/maintainedapps/testdata/zoom.json | 161 +++ server/mdm/maintainedapps/testing_utils.go | 74 ++ server/mock/datastore_mock.go | 12 + 34 files changed, 4903 insertions(+), 10 deletions(-) create mode 100644 changes/21773-ingest-fleet-maintained-apps create mode 100644 pkg/optjson/stringor.go create mode 100644 pkg/optjson/stringor_test.go create mode 100644 server/datastore/mysql/maintained_apps.go create mode 100644 server/datastore/mysql/maintained_apps_test.go create mode 100644 server/fleet/maintained_apps.go create mode 100644 server/mdm/maintainedapps/apps.json create mode 100644 server/mdm/maintainedapps/ingest.go create mode 100644 server/mdm/maintainedapps/ingest_test.go create mode 100644 server/mdm/maintainedapps/testdata/1password.json create mode 100644 server/mdm/maintainedapps/testdata/adobe-acrobat-reader.json create mode 100644 server/mdm/maintainedapps/testdata/box-drive.json create mode 100644 server/mdm/maintainedapps/testdata/brave-browser.json create mode 100644 server/mdm/maintainedapps/testdata/cloudflare-warp.json create mode 100644 server/mdm/maintainedapps/testdata/docker.json create mode 100644 server/mdm/maintainedapps/testdata/expected_apps.json create mode 100644 server/mdm/maintainedapps/testdata/figma.json create mode 100644 server/mdm/maintainedapps/testdata/firefox.json create mode 100644 server/mdm/maintainedapps/testdata/google-chrome.json create mode 100644 server/mdm/maintainedapps/testdata/microsoft-edge.json create mode 100644 server/mdm/maintainedapps/testdata/microsoft-excel.json create mode 100644 server/mdm/maintainedapps/testdata/microsoft-teams.json create mode 100644 server/mdm/maintainedapps/testdata/microsoft-word.json create mode 100644 server/mdm/maintainedapps/testdata/notion.json create mode 100644 server/mdm/maintainedapps/testdata/slack.json create mode 100644 server/mdm/maintainedapps/testdata/teamviewer.json create mode 100644 server/mdm/maintainedapps/testdata/visual-studio-code.json create mode 100644 server/mdm/maintainedapps/testdata/whatsapp.json create mode 100644 server/mdm/maintainedapps/testdata/zoom.json create mode 100644 server/mdm/maintainedapps/testing_utils.go diff --git a/changes/21773-ingest-fleet-maintained-apps b/changes/21773-ingest-fleet-maintained-apps new file mode 100644 index 000000000000..f5f01a3995e5 --- /dev/null +++ b/changes/21773-ingest-fleet-maintained-apps @@ -0,0 +1 @@ +* Added the definition of the Fleet maintained apps and its ingestion. diff --git a/pkg/optjson/stringor.go b/pkg/optjson/stringor.go new file mode 100644 index 000000000000..8ec112fd6045 --- /dev/null +++ b/pkg/optjson/stringor.go @@ -0,0 +1,31 @@ +package optjson + +import ( + "bytes" + "encoding/json" +) + +// StringOr is a JSON value that can be a string or a different type of object +// (e.g. somewhat common for a string or an array of strings, but can also be +// a string or an object, etc.). +type StringOr[T any] struct { + String string + Other T + IsOther bool +} + +func (s StringOr[T]) MarshalJSON() ([]byte, error) { + if s.IsOther { + return json.Marshal(s.Other) + } + return json.Marshal(s.String) +} + +func (s *StringOr[T]) UnmarshalJSON(data []byte) error { + if bytes.HasPrefix(data, []byte(`"`)) { + s.IsOther = false + return json.Unmarshal(data, &s.String) + } + s.IsOther = true + return json.Unmarshal(data, &s.Other) +} diff --git a/pkg/optjson/stringor_test.go b/pkg/optjson/stringor_test.go new file mode 100644 index 000000000000..21c5d511d518 --- /dev/null +++ b/pkg/optjson/stringor_test.go @@ -0,0 +1,137 @@ +package optjson + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestStringOr(t *testing.T) { + type child struct { + Name string `json:"name"` + } + + type target struct { + Field StringOr[[]string] `json:"field"` + Array []StringOr[*child] `json:"array"` + } + + type nested struct { + Inception StringOr[*target] `json:"inception"` + } + + cases := []struct { + name string + getVar func() any + src string // json source to unmarshal into the value returned by getVar + marshalAs string // how the value should marshal back to json + unmarshalErr string // if non-empty, unmarshal should fail with this error + }{ + { + name: "simple string", + getVar: func() any { var s StringOr[int]; return &s }, + src: `"abc"`, + marshalAs: `"abc"`, + }, + { + name: "simple integer", + getVar: func() any { var s StringOr[int]; return &s }, + src: `123`, + marshalAs: `123`, + }, + { + name: "invalid bool", + getVar: func() any { var s StringOr[int]; return &s }, + src: `true`, + unmarshalErr: "cannot unmarshal bool into Go value of type int", + }, + { + name: "field string", + getVar: func() any { var s target; return &s }, + src: `{"field":"abc"}`, + marshalAs: `{"field":"abc", "array": null}`, + }, + { + name: "field strings", + getVar: func() any { var s target; return &s }, + src: `{"field":["a", "b", "c"]}`, + marshalAs: `{"field":["a", "b", "c"], "array": null}`, + }, + { + name: "field empty array", + getVar: func() any { var s target; return &s }, + src: `{"field":[]}`, + marshalAs: `{"field":[], "array": null}`, + }, + { + name: "field invalid object", + getVar: func() any { var s target; return &s }, + src: `{"field":{}}`, + unmarshalErr: "cannot unmarshal object into Go struct field target.field of type []string", + }, + { + name: "array field null", + getVar: func() any { var s target; return &s }, + src: `{"array":null}`, + marshalAs: `{"array":null, "field": ""}`, + }, + { + name: "array field empty", + getVar: func() any { var s target; return &s }, + src: `{"array":[]}`, + marshalAs: `{"array":[], "field": ""}`, + }, + { + name: "array field single", + getVar: func() any { var s target; return &s }, + src: `{"array":["a"]}`, + marshalAs: `{"array":["a"], "field": ""}`, + }, + { + name: "array field empty child", + getVar: func() any { var s target; return &s }, + src: `{"array":["a", {}]}`, + marshalAs: `{"array":["a", {"name":""}], "field": ""}`, + }, + { + name: "array field set child", + getVar: func() any { var s target; return &s }, + src: `{"array":["a", {"name": "x"}]}`, + marshalAs: `{"array":["a", {"name":"x"}], "field": ""}`, + }, + { + name: "inception string", + getVar: func() any { var s nested; return &s }, + src: `{"inception":"a"}`, + marshalAs: `{"inception":"a"}`, + }, + { + name: "inception target field", + getVar: func() any { var s nested; return &s }, + src: `{"inception":{"field":["a", "b"]}}`, + marshalAs: `{"inception":{"field":["a", "b"], "array": null}}`, + }, + { + name: "inception target field and array", + getVar: func() any { var s nested; return &s }, + src: `{"inception":{"field":["a", "b"], "array": ["c", {"name": "x"}]}}`, + marshalAs: `{"inception":{"field":["a", "b"], "array": ["c", {"name": "x"}]}}`, + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + target := c.getVar() + err := json.Unmarshal([]byte(c.src), target) + if c.unmarshalErr != "" { + require.ErrorContains(t, err, c.unmarshalErr) + return + } + require.NoError(t, err) + + data, err := json.Marshal(target) + require.NoError(t, err) + require.JSONEq(t, c.marshalAs, string(data)) + }) + } +} diff --git a/server/datastore/mysql/maintained_apps.go b/server/datastore/mysql/maintained_apps.go new file mode 100644 index 000000000000..5080872d9a77 --- /dev/null +++ b/server/datastore/mysql/maintained_apps.go @@ -0,0 +1,55 @@ +package mysql + +import ( + "context" + + "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" + "github.com/fleetdm/fleet/v4/server/fleet" + "github.com/jmoiron/sqlx" +) + +func (ds *Datastore) UpsertMaintainedApp(ctx context.Context, app *fleet.MaintainedApp) error { + const upsertStmt = ` +INSERT INTO + fleet_library_apps ( + name, token, version, platform, installer_url, filename, + sha256, bundle_identifier, install_script_content_id, uninstall_script_content_id + ) +VALUES + ( ?, ?, ?, ?, ?, ?, + ?, ?, ?, ? ) +ON DUPLICATE KEY UPDATE + name = VALUES(name), + version = VALUES(version), + platform = VALUES(platform), + installer_url = VALUES(installer_url), + filename = VALUES(filename), + sha256 = VALUES(sha256), + bundle_identifier = VALUES(bundle_identifier), + install_script_content_id = VALUES(install_script_content_id), + uninstall_script_content_id = VALUES(uninstall_script_content_id) +` + + return ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error { + var err error + + // ensure the install script exists + installRes, err := insertScriptContents(ctx, tx, app.InstallScript) + if err != nil { + return ctxerr.Wrap(ctx, err, "insert install script content") + } + installScriptID, _ := installRes.LastInsertId() + + // ensure the uninstall script exists + uninstallRes, err := insertScriptContents(ctx, tx, app.UninstallScript) + if err != nil { + return ctxerr.Wrap(ctx, err, "insert uninstall script content") + } + uninstallScriptID, _ := uninstallRes.LastInsertId() + + // upsert the maintained app + _, err = tx.ExecContext(ctx, upsertStmt, app.Name, app.Token, app.Version, app.Platform, app.InstallerURL, app.Filename, + app.SHA256, app.BundleIdentifier, installScriptID, uninstallScriptID) + return ctxerr.Wrap(ctx, err, "upsert maintained app") + }) +} diff --git a/server/datastore/mysql/maintained_apps_test.go b/server/datastore/mysql/maintained_apps_test.go new file mode 100644 index 000000000000..b011ad8fa2f2 --- /dev/null +++ b/server/datastore/mysql/maintained_apps_test.go @@ -0,0 +1,87 @@ +package mysql + +import ( + "context" + "os" + "testing" + + "github.com/fleetdm/fleet/v4/server/fleet" + "github.com/fleetdm/fleet/v4/server/mdm/maintainedapps" + "github.com/go-kit/kit/log" + "github.com/jmoiron/sqlx" + "github.com/stretchr/testify/require" +) + +func TestMaintainedApps(t *testing.T) { + ds := CreateMySQLDS(t) + + cases := []struct { + name string + fn func(t *testing.T, ds *Datastore) + }{ + {"UpsertMaintainedApps", testUpsertMaintainedApps}, + {"IngestWithBrew", testIngestWithBrew}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + defer TruncateTables(t, ds) + c.fn(t, ds) + }) + } +} + +func testUpsertMaintainedApps(t *testing.T, ds *Datastore) { + ctx := context.Background() + + listSavedApps := func() []*fleet.MaintainedApp { + var apps []*fleet.MaintainedApp + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { + return sqlx.SelectContext(ctx, q, &apps, "SELECT name, version, platform FROM fleet_library_apps ORDER BY token") + }) + return apps + } + + expectedApps := maintainedapps.IngestMaintainedApps(t, ds) + require.Equal(t, expectedApps, listSavedApps()) + + // ingesting again results in no changes + maintainedapps.IngestMaintainedApps(t, ds) + require.Equal(t, expectedApps, listSavedApps()) + + // upsert the figma app, changing the version + err := ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ + Name: "Figma", + Token: "figma", + InstallerURL: "https://desktop.figma.com/mac-arm/Figma-999.9.9.zip", + Version: "999.9.9", + Platform: fleet.MacOSPlatform, + }) + require.NoError(t, err) + + // change the expected app data for figma + for _, app := range expectedApps { + if app.Name == "Figma" { + app.Version = "999.9.9" + break + } + } + require.Equal(t, expectedApps, listSavedApps()) +} + +func testIngestWithBrew(t *testing.T, ds *Datastore) { + if os.Getenv("NETWORK_TEST") == "" { + t.Skip("set environment variable NETWORK_TEST=1 to run") + } + + ctx := context.Background() + err := maintainedapps.Refresh(ctx, ds, log.NewNopLogger()) + require.NoError(t, err) + + expectedTokens := maintainedapps.ExpectedAppTokens(t) + var actualTokens []string + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { + return sqlx.SelectContext(ctx, q, &actualTokens, "SELECT token FROM fleet_library_apps ORDER BY token") + }) + require.ElementsMatch(t, expectedTokens, actualTokens) +} diff --git a/server/datastore/mysql/migrations/tables/20240909145426_AddFleetLibraryAppsTable.go b/server/datastore/mysql/migrations/tables/20240909145426_AddFleetLibraryAppsTable.go index 7a75dfef709a..8fdaed81a997 100644 --- a/server/datastore/mysql/migrations/tables/20240909145426_AddFleetLibraryAppsTable.go +++ b/server/datastore/mysql/migrations/tables/20240909145426_AddFleetLibraryAppsTable.go @@ -12,21 +12,24 @@ func init() { func Up_20240909145426(tx *sql.Tx) error { _, err := tx.Exec(` CREATE TABLE fleet_library_apps ( - id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, - name varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, + name varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, -- the "full_token" field from homebrew's JSON API response -- see e.g. https://formulae.brew.sh/api/cask/1password.json - token varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - version varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - platform varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - installer_url varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - filename varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + token varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + version varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + platform varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + installer_url varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + filename varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, -- hash of the binary downloaded from installer_url, allows us to validate we got the right bytes -- before sending to S3 (and we store installers on S3 under that sha256 hash as identifier). - sha256 varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + sha256 varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + -- bundle_identifier is used to match the library app with a software title in the software_titles table, + -- it is expected to be provided by the hard-coded JSON list of apps in the Fleet library. + bundle_identifier varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - created_at timestamp(6) NULL DEFAULT CURRENT_TIMESTAMP(6), - updated_at timestamp(6) NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + created_at timestamp(6) NULL DEFAULT CURRENT_TIMESTAMP(6), + updated_at timestamp(6) NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), -- foreign-key ids of the script_contents table. install_script_content_id int unsigned NOT NULL, diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql index 8e4df14b7272..dd5e0c12e3e1 100644 --- a/server/datastore/mysql/schema.sql +++ b/server/datastore/mysql/schema.sql @@ -211,6 +211,7 @@ CREATE TABLE `fleet_library_apps` ( `installer_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, `filename` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, `sha256` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `bundle_identifier` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, `created_at` timestamp(6) NULL DEFAULT CURRENT_TIMESTAMP(6), `updated_at` timestamp(6) NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), `install_script_content_id` int unsigned NOT NULL, diff --git a/server/fleet/datastore.go b/server/fleet/datastore.go index 2689b1114c55..203a126a1618 100644 --- a/server/fleet/datastore.go +++ b/server/fleet/datastore.go @@ -1685,6 +1685,10 @@ type Datastore interface { GetPastActivityDataForVPPAppInstall(ctx context.Context, commandResults *mdm.CommandResults) (*User, *ActivityInstalledAppStoreApp, error) GetVPPTokenByLocation(ctx context.Context, loc string) (*VPPTokenDB, error) + + // UpsertMaintainedApp inserts or updates a maintained app using the updated + // metadata provided via app. + UpsertMaintainedApp(ctx context.Context, app *MaintainedApp) error } // MDMAppleStore wraps nanomdm's storage and adds methods to deal with diff --git a/server/fleet/maintained_apps.go b/server/fleet/maintained_apps.go new file mode 100644 index 000000000000..e78c2a810031 --- /dev/null +++ b/server/fleet/maintained_apps.go @@ -0,0 +1,21 @@ +package fleet + +// MaintainedApp represets an app in the Fleet library of maintained apps, +// as stored in the fleet_library_apps table. +type MaintainedApp struct { + ID uint `json:"id" db:"id"` + Name string `json:"name" db:"name"` + Token string `json:"-" db:"name"` + Version string `json:"version" db:"version"` + Platform AppleDevicePlatform `json:"platform" db:"platform"` + InstallerURL string `json:"-" db:"installer_url"` + Filename string `json:"filename" db:"filename"` + SHA256 string `json:"-" db:"sha256"` + BundleIdentifier string `json:"-" db:"bundle_identifier"` + + // InstallScript and UninstallScript are not stored directly in the table, they + // must be filled via a JOIN on script_contents. On insert/update/upsert, these + // fields are used to provide the content of those scripts. + InstallScript string `json:"install_script" db:"install_script"` + UninstallScript string `json:"uninstall_script" db:"uninstall_script"` +} diff --git a/server/mdm/maintainedapps/apps.json b/server/mdm/maintainedapps/apps.json new file mode 100644 index 000000000000..86aa33ba5cdf --- /dev/null +++ b/server/mdm/maintainedapps/apps.json @@ -0,0 +1,97 @@ +[ + { + "identifier": "1password", + "bundle_identifier": "com.1password.1password", + "installer_format": "zip:app" + }, + { + "identifier": "adobe-acrobat-reader", + "bundle_identifier": "com.adobe.Reader", + "installer_format": "dmg:pkg" + }, + { + "identifier": "box-drive", + "bundle_identifier": "???", + "installer_format": "pkg" + }, + { + "identifier": "brave-browser", + "bundle_identifier": "com.brave.Browser", + "installer_format": "dmg:app" + }, + { + "identifier": "cloudflare-warp", + "bundle_identifier": "com.cloudflare.1dot1dot1dot1.macos", + "installer_format": "pkg" + }, + { + "identifier": "docker", + "bundle_identifier": "com.electron.dockerdesktop", + "installer_format": "dmg:app" + }, + { + "identifier": "figma", + "bundle_identifier": "com.figma.Desktop", + "installer_format": "zip:app" + }, + { + "identifier": "firefox", + "bundle_identifier": "org.mozilla.firefox", + "installer_format": "dmg:app" + }, + { + "identifier": "google-chrome", + "bundle_identifier": "com.google.Chrome", + "installer_format": "dmg:app" + }, + { + "identifier": "microsoft-edge", + "bundle_identifier": "com.microsoft.edgemac", + "installer_format": "pkg" + }, + { + "identifier": "microsoft-excel", + "bundle_identifier": "com.microsoft.Excel", + "installer_format": "pkg" + }, + { + "identifier": "microsoft-teams", + "bundle_identifier": "com.microsoft.teams", + "installer_format": "pkg" + }, + { + "identifier": "microsoft-word", + "bundle_identifier": "com.microsoft.Word", + "installer_format": "pkg" + }, + { + "identifier": "notion", + "bundle_identifier": "notion.id", + "installer_format": "dmg:app" + }, + { + "identifier": "slack", + "bundle_identifier": "com.tinyspeck.slackmacgap", + "installer_format": "dmg:app" + }, + { + "identifier": "teamviewer", + "bundle_identifier": "???", + "installer_format": "pkg" + }, + { + "identifier": "visual-studio-code", + "bundle_identifier": "com.microsoft.VSCode", + "installer_format": "zip:app" + }, + { + "identifier": "whatsapp", + "bundle_identifier": "desktop.WhatsApp", + "installer_format": "zip:app" + }, + { + "identifier": "zoom", + "bundle_identifier": "us.zoom.xos", + "installer_format": "pkg" + } +] diff --git a/server/mdm/maintainedapps/ingest.go b/server/mdm/maintainedapps/ingest.go new file mode 100644 index 000000000000..96fa8cc415a7 --- /dev/null +++ b/server/mdm/maintainedapps/ingest.go @@ -0,0 +1,220 @@ +package maintainedapps + +import ( + "context" + _ "embed" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path" + "strings" + "time" + + "github.com/fleetdm/fleet/v4/pkg/fleethttp" + "github.com/fleetdm/fleet/v4/pkg/optjson" + "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" + "github.com/fleetdm/fleet/v4/server/fleet" + kitlog "github.com/go-kit/log" + "golang.org/x/sync/errgroup" +) + +//go:embed apps.json +var appsJSON []byte + +type maintainedApp struct { + Identifier string `json:"identifier"` + BundleIdentifier string `json:"bundle_identifier"` + InstallerFormat string `json:"installer_format"` +} + +const baseBrewAPIURL = "https://formulae.brew.sh/api/" + +// Refresh fetches the latest information about maintained apps from the brew +// API and updates the Fleet database with the new information. +func Refresh(ctx context.Context, ds fleet.Datastore, logger kitlog.Logger) error { + var apps []maintainedApp + if err := json.Unmarshal(appsJSON, &apps); err != nil { + return ctxerr.Wrap(ctx, err, "unmarshal embedded apps.json") + } + + // allow mocking of the brew API for tests + baseURL := baseBrewAPIURL + if v := os.Getenv("FLEET_DEV_BREW_API_URL"); v != "" { + baseURL = v + } + + i := ingester{ + baseURL: baseURL, + ds: ds, + logger: logger, + } + return i.ingest(ctx, apps) +} + +type ingester struct { + baseURL string + ds fleet.Datastore + logger kitlog.Logger +} + +func (i ingester) ingest(ctx context.Context, apps []maintainedApp) error { + var g errgroup.Group + + if !strings.HasSuffix(i.baseURL, "/") { + i.baseURL += "/" + } + + client := fleethttp.NewClient(fleethttp.WithTimeout(10 * time.Second)) + + // run at most 3 concurrent requests to avoid overwhelming the brew API + g.SetLimit(3) + for _, app := range apps { + app := app // capture loop variable, not required in Go 1.23+ + g.Go(func() error { + return i.ingestOne(ctx, app, client) + }) + } + return ctxerr.Wrap(ctx, g.Wait(), "ingest apps") +} + +func (i ingester) ingestOne(ctx context.Context, app maintainedApp, client *http.Client) error { + apiURL := fmt.Sprintf("%scask/%s.json", i.baseURL, app.Identifier) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil) + if err != nil { + return ctxerr.Wrap(ctx, err, "create http request") + } + + res, err := client.Do(req) + if err != nil { + return ctxerr.Wrap(ctx, err, "execute http request") + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + if err != nil { + return ctxerr.Wrap(ctx, err, "read http response body") + } + + switch res.StatusCode { + case http.StatusOK: + // success, go on + case http.StatusNotFound: + // TODO: delete the existing entry? do nothing and succeed? doing the latter for now. + return nil + default: + if len(body) > 512 { + body = body[:512] + } + return ctxerr.Errorf(ctx, "brew API returned status %d: %s", res.StatusCode, string(body)) + } + + var cask brewCask + if err := json.Unmarshal(body, &cask); err != nil { + return ctxerr.Wrapf(ctx, err, "unmarshal brew cask for %s", app.Identifier) + } + + // validate required fields + if len(cask.Name) == 0 || cask.Name[0] == "" { + return ctxerr.Errorf(ctx, "missing name for cask %s", app.Identifier) + } + if cask.Token == "" { + return ctxerr.Errorf(ctx, "missing token for cask %s", app.Identifier) + } + if cask.Version == "" { + return ctxerr.Errorf(ctx, "missing version for cask %s", app.Identifier) + } + if cask.URL == "" { + return ctxerr.Errorf(ctx, "missing URL for cask %s", app.Identifier) + } + parsedURL, err := url.Parse(cask.URL) + if err != nil { + return ctxerr.Wrapf(ctx, err, "parse URL for cask %s", app.Identifier) + } + filename := path.Base(parsedURL.Path) + + installScript := installScriptForApp(app, &cask) + uninstallScript := uninstallScriptForApp(app, &cask) + + err = i.ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ + Name: cask.Name[0], + Token: cask.Token, + Version: cask.Version, + // for now, maintained apps are always macOS (darwin) + Platform: fleet.MacOSPlatform, + InstallerURL: cask.URL, + Filename: filename, + SHA256: cask.SHA256, + BundleIdentifier: app.BundleIdentifier, + InstallScript: installScript, + UninstallScript: uninstallScript, + }) + return ctxerr.Wrap(ctx, err, "upsert maintained app") +} + +func installScriptForApp(app maintainedApp, cask *brewCask) string { + // TODO: implement install script based on cask and app installer format + return "install" +} + +func uninstallScriptForApp(app maintainedApp, cask *brewCask) string { + // TODO: implement uninstall script based on cask and app installer format + return "uninstall" +} + +type brewCask struct { + Token string `json:"token"` + FullToken string `json:"full_token"` + Tap string `json:"tap"` + Name []string `json:"name"` + Desc string `json:"desc"` + URL string `json:"url"` + Version string `json:"version"` + SHA256 string `json:"sha256"` + Artifacts []*brewArtifact `json:"artifacts"` +} + +// brew artifacts are objects that have one and only one of their fields set. +type brewArtifact struct { + App []string `json:"app"` + // Pkg is a bit like Binary, it is an array with a string and an object as + // first two elements. The object has a choices field with an array of + // objects. See Microsoft Edge. + Pkg []optjson.StringOr[*brewPkgChoices] `json:"pkg"` + Uninstall []*brewUninstall `json:"uninstall"` + Zap []*brewZap `json:"zap"` + // Binary is an array with a string and an object as first two elements. See + // the "docker" and "firefox" casks. + Binary []optjson.StringOr[*brewBinaryTarget] `json:"binary"` +} + +type brewPkgChoices struct { + // At the moment we don't care about the "choices" field on the pkg. + Choices []any `json:"choices"` +} + +type brewBinaryTarget struct { + Target string `json:"target"` +} + +// unlike brewArtifact, a single brewUninstall can have many fields set. +// All fields can have one or multiple strings (string or []string). +type brewUninstall struct { + LaunchCtl optjson.StringOr[[]string] `json:"launchctl"` + Quit optjson.StringOr[[]string] `json:"quit"` + PkgUtil optjson.StringOr[[]string] `json:"pkgutil"` + Script optjson.StringOr[[]string] `json:"script"` + // format: [0]=signal, [1]=process name + Signal optjson.StringOr[[]string] `json:"signal"` + Delete optjson.StringOr[[]string] `json:"delete"` + RmDir optjson.StringOr[[]string] `json:"rmdir"` +} + +// same as brewUninstall, can be []string or string (see Microsoft Teams). +type brewZap struct { + Trash optjson.StringOr[[]string] `json:"trash"` + RmDir optjson.StringOr[[]string] `json:"rmdir"` +} diff --git a/server/mdm/maintainedapps/ingest_test.go b/server/mdm/maintainedapps/ingest_test.go new file mode 100644 index 000000000000..3f1ae68f67ac --- /dev/null +++ b/server/mdm/maintainedapps/ingest_test.go @@ -0,0 +1,168 @@ +package maintainedapps + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "path" + "strings" + "testing" + + "github.com/fleetdm/fleet/v4/server/fleet" + "github.com/fleetdm/fleet/v4/server/mock" + "github.com/go-kit/log" + "github.com/stretchr/testify/require" +) + +func TestIngestValidations(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var cask brewCask + + appToken := strings.TrimSuffix(path.Base(r.URL.Path), ".json") + switch appToken { + case "fail": + w.WriteHeader(http.StatusInternalServerError) + return + + case "notfound": + w.WriteHeader(http.StatusNotFound) + return + + case "noname": + cask = brewCask{ + Token: appToken, + Name: nil, + URL: "https://example.com", + Version: "1.0", + } + + case "emptyname": + cask = brewCask{ + Token: appToken, + Name: []string{""}, + URL: "https://example.com", + Version: "1.0", + } + + case "notoken": + cask = brewCask{ + Token: "", + Name: []string{appToken}, + URL: "https://example.com", + Version: "1.0", + } + + case "noversion": + cask = brewCask{ + Token: appToken, + Name: []string{appToken}, + URL: "https://example.com", + Version: "", + } + + case "nourl": + cask = brewCask{ + Token: appToken, + Name: []string{appToken}, + URL: "", + Version: "1.0", + } + + case "invalidurl": + cask = brewCask{ + Token: appToken, + Name: []string{appToken}, + URL: "https://\x00\x01\x02", + Version: "1.0", + } + + case "ok": + cask = brewCask{ + Token: appToken, + Name: []string{appToken}, + URL: "https://example.com", + Version: "1.0", + } + + default: + w.WriteHeader(http.StatusBadRequest) + t.Fatalf("unexpected app token %s", appToken) + } + + err := json.NewEncoder(w).Encode(cask) + require.NoError(t, err) + })) + t.Cleanup(srv.Close) + + ctx := context.Background() + ds := new(mock.Store) + ds.UpsertMaintainedAppFunc = func(ctx context.Context, app *fleet.MaintainedApp) error { + return nil + } + + cases := []struct { + appToken string + wantErr string + upsertCalled bool + }{ + {"fail", "brew API returned status 500", false}, + {"notfound", "", false}, + {"noname", "missing name for cask noname", false}, + {"emptyname", "missing name for cask emptyname", false}, + {"notoken", "missing token for cask notoken", false}, + {"noversion", "missing version for cask noversion", false}, + {"nourl", "missing URL for cask nourl", false}, + {"invalidurl", "parse URL for cask invalidurl", false}, + {"ok", "", true}, + {"multi:ok", "", true}, + {"multi:fail", "brew API returned status 500", true}, + } + for _, c := range cases { + t.Run(c.appToken, func(t *testing.T) { + i := ingester{baseURL: srv.URL, ds: ds, logger: log.NewNopLogger()} + + var apps []maintainedApp + var ignoreDSCheck bool + if strings.HasPrefix(c.appToken, "multi:") { + token := strings.TrimPrefix(c.appToken, "multi:") + if token == "fail" { + // do not check the DS call, as it may or may not have happened depending + // on the concurrent execution + ignoreDSCheck = true + // send 3 ok, one fail + apps = []maintainedApp{ + {Identifier: "ok", BundleIdentifier: "abc", InstallerFormat: "pkg"}, + {Identifier: "fail", BundleIdentifier: "def", InstallerFormat: "pkg"}, + {Identifier: "ok", BundleIdentifier: "ghi", InstallerFormat: "pkg"}, + {Identifier: "ok", BundleIdentifier: "klm", InstallerFormat: "pkg"}, + } + } else { + // send 4 apps with ok + apps = []maintainedApp{ + {Identifier: token, BundleIdentifier: "abc", InstallerFormat: "pkg"}, + {Identifier: token, BundleIdentifier: "def", InstallerFormat: "pkg"}, + {Identifier: token, BundleIdentifier: "ghi", InstallerFormat: "pkg"}, + {Identifier: token, BundleIdentifier: "klm", InstallerFormat: "pkg"}, + } + } + } else { + apps = []maintainedApp{ + {Identifier: c.appToken, BundleIdentifier: "abc", InstallerFormat: "pkg"}, + } + } + + err := i.ingest(ctx, apps) + if c.wantErr == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, c.wantErr) + } + + if !ignoreDSCheck { + require.Equal(t, c.upsertCalled, ds.UpsertMaintainedAppFuncInvoked) + } + ds.UpsertMaintainedAppFuncInvoked = false + }) + } +} diff --git a/server/mdm/maintainedapps/testdata/1password.json b/server/mdm/maintainedapps/testdata/1password.json new file mode 100644 index 000000000000..4b769c7b99fa --- /dev/null +++ b/server/mdm/maintainedapps/testdata/1password.json @@ -0,0 +1,130 @@ +{ + "token": "1password", + "full_token": "1password", + "old_tokens": [], + "tap": "homebrew/cask", + "name": [ + "1Password" + ], + "desc": "Password manager that keeps all passwords secure behind one password", + "homepage": "https://1password.com/", + "url": "https://downloads.1password.com/mac/1Password-8.10.44-aarch64.zip", + "url_specs": {}, + "version": "8.10.44", + "installed": null, + "installed_time": null, + "bundle_version": null, + "bundle_short_version": null, + "outdated": false, + "sha256": "2b9f242f27ba3a6e45893e4436a81eebd62478746abf655c761c08e1984024d3", + "artifacts": [ + { + "app": [ + "1Password.app" + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Scripts/2BUA8C4S2C.com.1password*", + "~/Library/Application Scripts/2BUA8C4S2C.com.agilebits", + "~/Library/Application Scripts/com.1password.1password-launcher", + "~/Library/Application Scripts/com.1password.browser-support", + "~/Library/Application Support/1Password", + "~/Library/Application Support/Arc/User Data/NativeMessagingHosts/com.1password.1password.json", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.1password.1password.sfl*", + "~/Library/Application Support/CrashReporter/1Password*", + "~/Library/Application Support/Google/Chrome Beta/NativeMessagingHosts/com.1password.1password.json", + "~/Library/Application Support/Google/Chrome Canary/NativeMessagingHosts/com.1password.1password.json", + "~/Library/Application Support/Google/Chrome Dev/NativeMessagingHosts/com.1password.1password.json", + "~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.1password.1password.json", + "~/Library/Application Support/Microsoft Edge Beta/NativeMessagingHosts/com.1password.1password.json", + "~/Library/Application Support/Microsoft Edge Canary/NativeMessagingHosts/com.1password.1password.json", + "~/Library/Application Support/Microsoft Edge Dev/NativeMessagingHosts/com.1password.1password.json", + "~/Library/Application Support/Microsoft Edge/NativeMessagingHosts/com.1password.1password.json", + "~/Library/Application Support/Mozilla/NativeMessagingHosts/com.1password.1password.json", + "~/Library/Application Support/Vivaldi/NativeMessagingHosts/com.1password.1password.json", + "~/Library/Containers/2BUA8C4S2C.com.1password.browser-helper", + "~/Library/Containers/com.1password.1password*", + "~/Library/Containers/com.1password.browser-support", + "~/Library/Group Containers/2BUA8C4S2C.com.1password", + "~/Library/Group Containers/2BUA8C4S2C.com.agilebits", + "~/Library/Logs/1Password", + "~/Library/Preferences/com.1password.1password.plist", + "~/Library/Preferences/group.com.1password.plist", + "~/Library/Saved Application State/com.1password.1password.savedState" + ] + } + ] + } + ], + "caveats": null, + "depends_on": { + "macos": { + ">=": [ + "10.15" + ] + } + }, + "conflicts_with": { + "cask": [ + "1password@beta", + "1password@nightly" + ] + }, + "container": null, + "auto_updates": true, + "deprecated": false, + "deprecation_date": null, + "deprecation_reason": null, + "disabled": false, + "disable_date": null, + "disable_reason": null, + "tap_git_head": "5e76d542a4e6e78701fdfae86e263074eab2a3e7", + "languages": [], + "ruby_source_path": "Casks/1/1password.rb", + "ruby_source_checksum": { + "sha256": "277e7b256383ac4dd2ea259c936a426120c95aeeae895fae3916c09da955d35d" + }, + "variations": { + "sequoia": { + "url": "https://downloads.1password.com/mac/1Password-8.10.44-x86_64.zip", + "sha256": "fd26d4fcac880c2ecd7c0886a1ecbc5a727b580c7790e2b7c1fdb8713d026f3f" + }, + "sonoma": { + "url": "https://downloads.1password.com/mac/1Password-8.10.44-x86_64.zip", + "sha256": "fd26d4fcac880c2ecd7c0886a1ecbc5a727b580c7790e2b7c1fdb8713d026f3f" + }, + "ventura": { + "url": "https://downloads.1password.com/mac/1Password-8.10.44-x86_64.zip", + "sha256": "fd26d4fcac880c2ecd7c0886a1ecbc5a727b580c7790e2b7c1fdb8713d026f3f" + }, + "monterey": { + "url": "https://downloads.1password.com/mac/1Password-8.10.44-x86_64.zip", + "sha256": "fd26d4fcac880c2ecd7c0886a1ecbc5a727b580c7790e2b7c1fdb8713d026f3f" + }, + "big_sur": { + "url": "https://downloads.1password.com/mac/1Password-8.10.44-x86_64.zip", + "sha256": "fd26d4fcac880c2ecd7c0886a1ecbc5a727b580c7790e2b7c1fdb8713d026f3f" + }, + "catalina": { + "url": "https://downloads.1password.com/mac/1Password-8.10.44-x86_64.zip", + "sha256": "fd26d4fcac880c2ecd7c0886a1ecbc5a727b580c7790e2b7c1fdb8713d026f3f" + } + }, + "analytics": { + "install": { + "30d": { + "1password": 3585 + }, + "90d": { + "1password": 10054 + }, + "365d": { + "1password": 41293 + } + } + }, + "generated_date": "2024-09-09" +} diff --git a/server/mdm/maintainedapps/testdata/adobe-acrobat-reader.json b/server/mdm/maintainedapps/testdata/adobe-acrobat-reader.json new file mode 100644 index 000000000000..719d8386f7ca --- /dev/null +++ b/server/mdm/maintainedapps/testdata/adobe-acrobat-reader.json @@ -0,0 +1,97 @@ +{ + "token": "adobe-acrobat-reader", + "full_token": "adobe-acrobat-reader", + "old_tokens": [], + "tap": "homebrew/cask", + "name": [ + "Adobe Acrobat Reader" + ], + "desc": "View, print, and comment on PDF documents", + "homepage": "https://www.adobe.com/acrobat/pdf-reader.html", + "url": "https://ardownload2.adobe.com/pub/adobe/reader/mac/AcrobatDC/2400221005/AcroRdrDC_2400221005_MUI.dmg", + "url_specs": {}, + "version": "24.002.21005", + "installed": null, + "installed_time": null, + "bundle_version": null, + "bundle_short_version": null, + "outdated": false, + "sha256": "55a53014b5e060658921f6d3a6d20fa64aae5b09fc9f6ea3d78e9c2378dab934", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": [ + "com.adobe.ARMDC.Communicator", + "com.adobe.ARMDC.SMJobBlessHelper", + "com.adobe.ARMDCHelper.cc24aef4a1b90ed56a725c38014c95072f92651fb65e1bf9c8e43c37a23d420d" + ], + "quit": [ + "com.adobe.AdobeRdrCEF", + "com.adobe.AdobeRdrCEFHelper", + "com.adobe.Reader" + ], + "pkgutil": [ + "com.adobe.acrobat.DC.reader.*", + "com.adobe.armdc.app.pkg", + "com.adobe.RdrServicesUpdater" + ], + "delete": [ + "/Applications/Adobe Acrobat Reader.app", + "/Library/Preferences/com.adobe.reader.DC.WebResource.plist" + ] + } + ] + }, + { + "pkg": [ + "AcroRdrDC_2400221005_MUI.pkg" + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Caches/com.adobe.Reader", + "~/Library/HTTPStorages/com.adobe.Reader.binarycookies", + "~/Library/Preferences/com.adobe.AdobeRdrCEFHelper.plist", + "~/Library/Preferences/com.adobe.crashreporter.plist", + "~/Library/Preferences/com.adobe.Reader.plist" + ] + } + ] + } + ], + "caveats": null, + "depends_on": {}, + "conflicts_with": null, + "container": null, + "auto_updates": true, + "deprecated": false, + "deprecation_date": null, + "deprecation_reason": null, + "disabled": false, + "disable_date": null, + "disable_reason": null, + "tap_git_head": "5e76d542a4e6e78701fdfae86e263074eab2a3e7", + "languages": [], + "ruby_source_path": "Casks/a/adobe-acrobat-reader.rb", + "ruby_source_checksum": { + "sha256": "8d9c2e12ab57cb1c4c33ef523a35caf2a5ae923631fdf8d3eae2162a901d44ad" + }, + "variations": {}, + "analytics": { + "install": { + "30d": { + "adobe-acrobat-reader": 1826 + }, + "90d": { + "adobe-acrobat-reader": 6682 + }, + "365d": { + "adobe-acrobat-reader": 36717 + } + } + }, + "generated_date": "2024-09-09" +} diff --git a/server/mdm/maintainedapps/testdata/box-drive.json b/server/mdm/maintainedapps/testdata/box-drive.json new file mode 100644 index 000000000000..543d1959ea1f --- /dev/null +++ b/server/mdm/maintainedapps/testdata/box-drive.json @@ -0,0 +1,102 @@ +{ + "token": "box-drive", + "full_token": "box-drive", + "old_tokens": [], + "tap": "homebrew/cask", + "name": [ + "Box Drive" + ], + "desc": "Client for the Box cloud storage service", + "homepage": "https://www.box.com/drive", + "url": "https://e3.boxcdn.net/desktop/releases/mac/BoxDrive-2.40.345.pkg", + "url_specs": { + "verified": "e3.boxcdn.net/desktop/releases/mac/" + }, + "version": "2.40.345", + "installed": null, + "installed_time": null, + "bundle_version": null, + "bundle_short_version": null, + "outdated": false, + "sha256": "203493ec8aedce5502d36464e02665a1d9e01ad0aed3d86468b3df12280ffb1c", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": "com.box.desktop.helper", + "quit": [ + "com.box.Box-Local-Com-Server", + "com.box.desktop", + "com.box.desktop.findersyncext", + "com.box.desktop.helper", + "com.box.desktop.ui" + ], + "script": "/Library/Application Support/Box/uninstall_box_drive", + "pkgutil": "com.box.desktop.installer.*" + } + ] + }, + { + "pkg": [ + "BoxDrive-2.40.345.pkg" + ] + }, + { + "zap": [ + { + "trash": [ + "~/.Box_*", + "~/Library/Application Support/Box/Box", + "~/Library/Application Support/FileProvider/com.box.desktop.boxfileprovider", + "~/Library/Containers/com.box.desktop.findersyncext", + "~/Library/Logs/Box/Box", + "~/Library/Preferences/com.box.desktop.plist", + "~/Library/Preferences/com.box.desktop.ui.plist" + ] + } + ] + } + ], + "caveats": null, + "depends_on": { + "macos": { + ">=": [ + "10.11" + ] + } + }, + "conflicts_with": { + "cask": [ + "box-sync" + ] + }, + "container": null, + "auto_updates": true, + "deprecated": false, + "deprecation_date": null, + "deprecation_reason": null, + "disabled": false, + "disable_date": null, + "disable_reason": null, + "tap_git_head": "5e76d542a4e6e78701fdfae86e263074eab2a3e7", + "languages": [], + "ruby_source_path": "Casks/b/box-drive.rb", + "ruby_source_checksum": { + "sha256": "8f773c3189e6f031e0f454c25943ce602bdc2a42dc891a1db7fe3b63e17be249" + }, + "variations": {}, + "analytics": { + "install": { + "30d": { + "box-drive": 81 + }, + "90d": { + "box-drive": 209 + }, + "365d": { + "box-drive": 1066 + } + } + }, + "generated_date": "2024-09-09" +} diff --git a/server/mdm/maintainedapps/testdata/brave-browser.json b/server/mdm/maintainedapps/testdata/brave-browser.json new file mode 100644 index 000000000000..dfe62913e855 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/brave-browser.json @@ -0,0 +1,106 @@ +{ + "token": "brave-browser", + "full_token": "brave-browser", + "old_tokens": [], + "tap": "homebrew/cask", + "name": [ + "Brave" + ], + "desc": "Web browser focusing on privacy", + "homepage": "https://brave.com/", + "url": "https://updates-cdn.bravesoftware.com/sparkle/Brave-Browser/stable-arm64/169.162/Brave-Browser-arm64.dmg", + "url_specs": { + "verified": "updates-cdn.bravesoftware.com/sparkle/Brave-Browser/" + }, + "version": "1.69.162.0", + "installed": null, + "installed_time": null, + "bundle_version": null, + "bundle_short_version": null, + "outdated": false, + "sha256": "f73ee45cf385182bde54bcbddad65d45ac2b1e9fd14798660af9d7e34f539d1a", + "artifacts": [ + { + "app": [ + "Brave Browser.app" + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Support/BraveSoftware", + "~/Library/Caches/BraveSoftware", + "~/Library/Caches/com.brave.Browser", + "~/Library/HTTPStorages/com.brave.Browser", + "~/Library/Preferences/com.brave.Browser.plist", + "~/Library/Saved Application State/com.brave.Browser.savedState" + ] + } + ] + } + ], + "caveats": null, + "depends_on": { + "macos": { + ">=": [ + "10.15" + ] + } + }, + "conflicts_with": null, + "container": null, + "auto_updates": true, + "deprecated": false, + "deprecation_date": null, + "deprecation_reason": null, + "disabled": false, + "disable_date": null, + "disable_reason": null, + "tap_git_head": "5e76d542a4e6e78701fdfae86e263074eab2a3e7", + "languages": [], + "ruby_source_path": "Casks/b/brave-browser.rb", + "ruby_source_checksum": { + "sha256": "5f2f1bab8cf16032cf73ab0a91c5f83df42333350245e28565abc08da1d0b575" + }, + "variations": { + "sequoia": { + "url": "https://updates-cdn.bravesoftware.com/sparkle/Brave-Browser/stable/169.162/Brave-Browser-x64.dmg", + "sha256": "26d0744c077c74684b09c378e6ee4c55138d3c935b651f9028d04f9b9d61b0e9" + }, + "sonoma": { + "url": "https://updates-cdn.bravesoftware.com/sparkle/Brave-Browser/stable/169.162/Brave-Browser-x64.dmg", + "sha256": "26d0744c077c74684b09c378e6ee4c55138d3c935b651f9028d04f9b9d61b0e9" + }, + "ventura": { + "url": "https://updates-cdn.bravesoftware.com/sparkle/Brave-Browser/stable/169.162/Brave-Browser-x64.dmg", + "sha256": "26d0744c077c74684b09c378e6ee4c55138d3c935b651f9028d04f9b9d61b0e9" + }, + "monterey": { + "url": "https://updates-cdn.bravesoftware.com/sparkle/Brave-Browser/stable/169.162/Brave-Browser-x64.dmg", + "sha256": "26d0744c077c74684b09c378e6ee4c55138d3c935b651f9028d04f9b9d61b0e9" + }, + "big_sur": { + "url": "https://updates-cdn.bravesoftware.com/sparkle/Brave-Browser/stable/169.162/Brave-Browser-x64.dmg", + "sha256": "26d0744c077c74684b09c378e6ee4c55138d3c935b651f9028d04f9b9d61b0e9" + }, + "catalina": { + "url": "https://updates-cdn.bravesoftware.com/sparkle/Brave-Browser/stable/169.162/Brave-Browser-x64.dmg", + "sha256": "26d0744c077c74684b09c378e6ee4c55138d3c935b651f9028d04f9b9d61b0e9" + } + }, + "analytics": { + "install": { + "30d": { + "brave-browser": 5244 + }, + "90d": { + "brave-browser": 14764 + }, + "365d": { + "brave-browser": 60400 + } + } + }, + "generated_date": "2024-09-09" +} diff --git a/server/mdm/maintainedapps/testdata/cloudflare-warp.json b/server/mdm/maintainedapps/testdata/cloudflare-warp.json new file mode 100644 index 000000000000..2981f0bc8911 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/cloudflare-warp.json @@ -0,0 +1,95 @@ +{ + "token": "cloudflare-warp", + "full_token": "cloudflare-warp", + "old_tokens": [], + "tap": "homebrew/cask", + "name": [ + "Cloudflare WARP" + ], + "desc": "Free app that makes your Internet safer", + "homepage": "https://cloudflarewarp.com/", + "url": "https://1111-releases.cloudflareclient.com/mac/Cloudflare_WARP_2024.6.474.0.pkg", + "url_specs": { + "verified": "1111-releases.cloudflareclient.com/mac/" + }, + "version": "2024.6.474.0", + "installed": null, + "installed_time": null, + "bundle_version": null, + "bundle_short_version": null, + "outdated": false, + "sha256": "2a07e1c509fc3d95f7fe9b90362b4d853fcc0433808a1d68aaabf10406145be0", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": [ + "com.cloudflare.1dot1dot1dot1.macos.loginlauncherapp", + "com.cloudflare.1dot1dot1dot1.macos.warp.daemon" + ], + "quit": "com.cloudflare.1dot1dot1dot1.macos", + "pkgutil": "com.cloudflare.1dot1dot1dot1.macos" + } + ] + }, + { + "pkg": [ + "Cloudflare_WARP_2024.6.474.0.pkg" + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Scripts/com.cloudflare.1dot1dot1dot1.macos.loginlauncherapp", + "~/Library/Application Support/com.cloudflare.1dot1dot1dot1.macos", + "~/Library/Caches/com.cloudflare.1dot1dot1dot1.macos", + "~/Library/Caches/com.plausiblelabs.crashreporter.data/com.cloudflare.1dot1dot1dot1.macos", + "~/Library/Containers/com.cloudflare.1dot1dot1dot1.macos.loginlauncherapp", + "~/Library/HTTPStorages/com.cloudflare.1dot1dot1dot1.macos", + "~/Library/HTTPStorages/com.cloudflare.1dot1dot1dot1.macos.binarycookies", + "~/Library/Preferences/com.cloudflare.1dot1dot1dot1.macos.plist" + ] + } + ] + } + ], + "caveats": null, + "depends_on": { + "macos": { + ">=": [ + "10.15" + ] + } + }, + "conflicts_with": null, + "container": null, + "auto_updates": true, + "deprecated": false, + "deprecation_date": null, + "deprecation_reason": null, + "disabled": false, + "disable_date": null, + "disable_reason": null, + "tap_git_head": "5e76d542a4e6e78701fdfae86e263074eab2a3e7", + "languages": [], + "ruby_source_path": "Casks/c/cloudflare-warp.rb", + "ruby_source_checksum": { + "sha256": "1b01184ffd16b49e97843b1cd3f7f7435199bac35487bea61d892b992b1f95f5" + }, + "variations": {}, + "analytics": { + "install": { + "30d": { + "cloudflare-warp": 675 + }, + "90d": { + "cloudflare-warp": 2045 + }, + "365d": { + "cloudflare-warp": 8333 + } + } + }, + "generated_date": "2024-09-09" +} diff --git a/server/mdm/maintainedapps/testdata/docker.json b/server/mdm/maintainedapps/testdata/docker.json new file mode 100644 index 000000000000..15aec7b0b8be --- /dev/null +++ b/server/mdm/maintainedapps/testdata/docker.json @@ -0,0 +1,1004 @@ +{ + "token": "docker", + "full_token": "docker", + "old_tokens": [], + "tap": "homebrew/cask", + "name": [ + "Docker Desktop", + "Docker Community Edition", + "Docker CE" + ], + "desc": "App to build and share containerised applications and microservices", + "homepage": "https://www.docker.com/products/docker-desktop", + "url": "https://desktop.docker.com/mac/main/arm64/165256/Docker.dmg", + "url_specs": {}, + "version": "4.34.0,165256", + "installed": null, + "installed_time": null, + "bundle_version": null, + "bundle_short_version": null, + "outdated": false, + "sha256": "1d5fdbacd97373bcda95645f4d1ee32cff278afbec86f591788232c169a7b243", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": [ + "com.docker.helper", + "com.docker.socket", + "com.docker.vmnetd" + ], + "quit": "com.docker.docker", + "delete": [ + "/Library/PrivilegedHelperTools/com.docker.socket", + "/Library/PrivilegedHelperTools/com.docker.vmnetd" + ], + "rmdir": "~/.docker/bin" + } + ] + }, + { + "app": [ + "Docker.app" + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker-compose.zsh-completion", + { + "target": "$HOMEBREW_PREFIX/share/zsh/site-functions/_docker-compose" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker-compose.fish-completion", + { + "target": "$HOMEBREW_PREFIX/share/fish/vendor_completions.d/docker-compose.fish" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker", + { + "target": "/usr/local/bin/docker" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-desktop", + { + "target": "/usr/local/bin/docker-credential-desktop" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-ecr-login", + { + "target": "/usr/local/bin/docker-credential-ecr-login" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-osxkeychain", + { + "target": "/usr/local/bin/docker-credential-osxkeychain" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker-index", + { + "target": "/usr/local/bin/docker-index" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/kubectl", + { + "target": "/usr/local/bin/kubectl.docker" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/cli-plugins/docker-compose", + { + "target": "/usr/local/cli-plugins/docker-compose" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker.bash-completion", + { + "target": "$HOMEBREW_PREFIX/etc/bash_completion.d/docker" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker.zsh-completion", + { + "target": "$HOMEBREW_PREFIX/share/zsh/site-functions/_docker" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker.fish-completion", + { + "target": "$HOMEBREW_PREFIX/share/fish/vendor_completions.d/docker.fish" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/hub-tool", + { + "target": "/usr/local/bin/hub-tool" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker-compose.bash-completion", + { + "target": "$HOMEBREW_PREFIX/etc/bash_completion.d/docker-compose" + } + ] + }, + { + "uninstall_postflight": null + }, + { + "postflight": null + }, + { + "zap": [ + { + "trash": [ + "/usr/local/bin/docker-compose.backup", + "/usr/local/bin/docker.backup", + "~/.docker", + "~/Library/Application Scripts/com.docker.helper", + "~/Library/Application Scripts/group.com.docker", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.docker.helper.sfl*", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.electron.dockerdesktop.sfl*", + "~/Library/Application Support/com.bugsnag.Bugsnag/com.docker.docker", + "~/Library/Application Support/Docker Desktop", + "~/Library/Caches/com.docker.docker", + "~/Library/Caches/com.plausiblelabs.crashreporter.data/com.docker.docker", + "~/Library/Caches/KSCrashReports/Docker", + "~/Library/Containers/com.docker.docker", + "~/Library/Containers/com.docker.helper", + "~/Library/Group Containers/group.com.docker", + "~/Library/HTTPStorages/com.docker.docker", + "~/Library/HTTPStorages/com.docker.docker.binarycookies", + "~/Library/Logs/Docker Desktop", + "~/Library/Preferences/com.docker.docker.plist", + "~/Library/Preferences/com.electron.docker-frontend.plist", + "~/Library/Preferences/com.electron.dockerdesktop.plist", + "~/Library/Saved Application State/com.electron.docker-frontend.savedState", + "~/Library/Saved Application State/com.electron.dockerdesktop.savedState" + ], + "rmdir": [ + "~/Library/Caches/com.plausiblelabs.crashreporter.data", + "~/Library/Caches/KSCrashReports" + ] + } + ] + } + ], + "caveats": null, + "depends_on": { + "macos": { + ">=": [ + "12" + ] + } + }, + "conflicts_with": { + "cask": [ + "rancher" + ], + "formula": [ + "docker", + "docker-completion", + "docker-compose", + "docker-credential-helper-ecr" + ] + }, + "container": null, + "auto_updates": true, + "deprecated": false, + "deprecation_date": null, + "deprecation_reason": null, + "disabled": false, + "disable_date": null, + "disable_reason": null, + "tap_git_head": "5e76d542a4e6e78701fdfae86e263074eab2a3e7", + "languages": [], + "ruby_source_path": "Casks/d/docker.rb", + "ruby_source_checksum": { + "sha256": "5e6d89c8cd84c92dc1d331f47ad61cd32193036bd7a6c5e1d00ff208f57bad08" + }, + "variations": { + "sequoia": { + "url": "https://desktop.docker.com/mac/main/amd64/165256/Docker.dmg", + "sha256": "ea4e558d38fac4caafeb73134f012524f3b5a3b90894485f19ae99b0aa7cde19", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": [ + "com.docker.helper", + "com.docker.socket", + "com.docker.vmnetd" + ], + "quit": "com.docker.docker", + "delete": [ + "/Library/PrivilegedHelperTools/com.docker.socket", + "/Library/PrivilegedHelperTools/com.docker.vmnetd" + ], + "rmdir": "~/.docker/bin" + } + ] + }, + { + "app": [ + "Docker.app" + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker-compose.zsh-completion", + { + "target": "$HOMEBREW_PREFIX/share/zsh/site-functions/_docker-compose" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker-compose.fish-completion", + { + "target": "$HOMEBREW_PREFIX/share/fish/vendor_completions.d/docker-compose.fish" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker", + { + "target": "/usr/local/bin/docker" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-desktop", + { + "target": "/usr/local/bin/docker-credential-desktop" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-ecr-login", + { + "target": "/usr/local/bin/docker-credential-ecr-login" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-osxkeychain", + { + "target": "/usr/local/bin/docker-credential-osxkeychain" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker-index", + { + "target": "/usr/local/bin/docker-index" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/hub-tool", + { + "target": "/usr/local/bin/hub-tool" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/kubectl", + { + "target": "/usr/local/bin/kubectl.docker" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/cli-plugins/docker-compose", + { + "target": "/usr/local/cli-plugins/docker-compose" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker.bash-completion", + { + "target": "$HOMEBREW_PREFIX/etc/bash_completion.d/docker" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker.zsh-completion", + { + "target": "$HOMEBREW_PREFIX/share/zsh/site-functions/_docker" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker.fish-completion", + { + "target": "$HOMEBREW_PREFIX/share/fish/vendor_completions.d/docker.fish" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/bin/com.docker.hyperkit", + { + "target": "/usr/local/bin/hyperkit" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker-compose.bash-completion", + { + "target": "$HOMEBREW_PREFIX/etc/bash_completion.d/docker-compose" + } + ] + }, + { + "uninstall_postflight": null + }, + { + "postflight": null + }, + { + "zap": [ + { + "trash": [ + "/usr/local/bin/docker-compose.backup", + "/usr/local/bin/docker.backup", + "~/.docker", + "~/Library/Application Scripts/com.docker.helper", + "~/Library/Application Scripts/group.com.docker", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.docker.helper.sfl*", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.electron.dockerdesktop.sfl*", + "~/Library/Application Support/com.bugsnag.Bugsnag/com.docker.docker", + "~/Library/Application Support/Docker Desktop", + "~/Library/Caches/com.docker.docker", + "~/Library/Caches/com.plausiblelabs.crashreporter.data/com.docker.docker", + "~/Library/Caches/KSCrashReports/Docker", + "~/Library/Containers/com.docker.docker", + "~/Library/Containers/com.docker.helper", + "~/Library/Group Containers/group.com.docker", + "~/Library/HTTPStorages/com.docker.docker", + "~/Library/HTTPStorages/com.docker.docker.binarycookies", + "~/Library/Logs/Docker Desktop", + "~/Library/Preferences/com.docker.docker.plist", + "~/Library/Preferences/com.electron.docker-frontend.plist", + "~/Library/Preferences/com.electron.dockerdesktop.plist", + "~/Library/Saved Application State/com.electron.docker-frontend.savedState", + "~/Library/Saved Application State/com.electron.dockerdesktop.savedState" + ], + "rmdir": [ + "~/Library/Caches/com.plausiblelabs.crashreporter.data", + "~/Library/Caches/KSCrashReports" + ] + } + ] + } + ] + }, + "sonoma": { + "url": "https://desktop.docker.com/mac/main/amd64/165256/Docker.dmg", + "sha256": "ea4e558d38fac4caafeb73134f012524f3b5a3b90894485f19ae99b0aa7cde19", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": [ + "com.docker.helper", + "com.docker.socket", + "com.docker.vmnetd" + ], + "quit": "com.docker.docker", + "delete": [ + "/Library/PrivilegedHelperTools/com.docker.socket", + "/Library/PrivilegedHelperTools/com.docker.vmnetd" + ], + "rmdir": "~/.docker/bin" + } + ] + }, + { + "app": [ + "Docker.app" + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker-compose.zsh-completion", + { + "target": "$HOMEBREW_PREFIX/share/zsh/site-functions/_docker-compose" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker-compose.fish-completion", + { + "target": "$HOMEBREW_PREFIX/share/fish/vendor_completions.d/docker-compose.fish" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker", + { + "target": "/usr/local/bin/docker" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-desktop", + { + "target": "/usr/local/bin/docker-credential-desktop" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-ecr-login", + { + "target": "/usr/local/bin/docker-credential-ecr-login" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-osxkeychain", + { + "target": "/usr/local/bin/docker-credential-osxkeychain" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker-index", + { + "target": "/usr/local/bin/docker-index" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/hub-tool", + { + "target": "/usr/local/bin/hub-tool" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/kubectl", + { + "target": "/usr/local/bin/kubectl.docker" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/cli-plugins/docker-compose", + { + "target": "/usr/local/cli-plugins/docker-compose" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker.bash-completion", + { + "target": "$HOMEBREW_PREFIX/etc/bash_completion.d/docker" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker.zsh-completion", + { + "target": "$HOMEBREW_PREFIX/share/zsh/site-functions/_docker" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker.fish-completion", + { + "target": "$HOMEBREW_PREFIX/share/fish/vendor_completions.d/docker.fish" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/bin/com.docker.hyperkit", + { + "target": "/usr/local/bin/hyperkit" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker-compose.bash-completion", + { + "target": "$HOMEBREW_PREFIX/etc/bash_completion.d/docker-compose" + } + ] + }, + { + "uninstall_postflight": null + }, + { + "postflight": null + }, + { + "zap": [ + { + "trash": [ + "/usr/local/bin/docker-compose.backup", + "/usr/local/bin/docker.backup", + "~/.docker", + "~/Library/Application Scripts/com.docker.helper", + "~/Library/Application Scripts/group.com.docker", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.docker.helper.sfl*", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.electron.dockerdesktop.sfl*", + "~/Library/Application Support/com.bugsnag.Bugsnag/com.docker.docker", + "~/Library/Application Support/Docker Desktop", + "~/Library/Caches/com.docker.docker", + "~/Library/Caches/com.plausiblelabs.crashreporter.data/com.docker.docker", + "~/Library/Caches/KSCrashReports/Docker", + "~/Library/Containers/com.docker.docker", + "~/Library/Containers/com.docker.helper", + "~/Library/Group Containers/group.com.docker", + "~/Library/HTTPStorages/com.docker.docker", + "~/Library/HTTPStorages/com.docker.docker.binarycookies", + "~/Library/Logs/Docker Desktop", + "~/Library/Preferences/com.docker.docker.plist", + "~/Library/Preferences/com.electron.docker-frontend.plist", + "~/Library/Preferences/com.electron.dockerdesktop.plist", + "~/Library/Saved Application State/com.electron.docker-frontend.savedState", + "~/Library/Saved Application State/com.electron.dockerdesktop.savedState" + ], + "rmdir": [ + "~/Library/Caches/com.plausiblelabs.crashreporter.data", + "~/Library/Caches/KSCrashReports" + ] + } + ] + } + ] + }, + "ventura": { + "url": "https://desktop.docker.com/mac/main/amd64/165256/Docker.dmg", + "sha256": "ea4e558d38fac4caafeb73134f012524f3b5a3b90894485f19ae99b0aa7cde19", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": [ + "com.docker.helper", + "com.docker.socket", + "com.docker.vmnetd" + ], + "quit": "com.docker.docker", + "delete": [ + "/Library/PrivilegedHelperTools/com.docker.socket", + "/Library/PrivilegedHelperTools/com.docker.vmnetd" + ], + "rmdir": "~/.docker/bin" + } + ] + }, + { + "app": [ + "Docker.app" + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker-compose.zsh-completion", + { + "target": "$HOMEBREW_PREFIX/share/zsh/site-functions/_docker-compose" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker-compose.fish-completion", + { + "target": "$HOMEBREW_PREFIX/share/fish/vendor_completions.d/docker-compose.fish" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker", + { + "target": "/usr/local/bin/docker" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-desktop", + { + "target": "/usr/local/bin/docker-credential-desktop" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-ecr-login", + { + "target": "/usr/local/bin/docker-credential-ecr-login" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-osxkeychain", + { + "target": "/usr/local/bin/docker-credential-osxkeychain" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker-index", + { + "target": "/usr/local/bin/docker-index" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/hub-tool", + { + "target": "/usr/local/bin/hub-tool" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/kubectl", + { + "target": "/usr/local/bin/kubectl.docker" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/cli-plugins/docker-compose", + { + "target": "/usr/local/cli-plugins/docker-compose" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker.bash-completion", + { + "target": "$HOMEBREW_PREFIX/etc/bash_completion.d/docker" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker.zsh-completion", + { + "target": "$HOMEBREW_PREFIX/share/zsh/site-functions/_docker" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker.fish-completion", + { + "target": "$HOMEBREW_PREFIX/share/fish/vendor_completions.d/docker.fish" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/bin/com.docker.hyperkit", + { + "target": "/usr/local/bin/hyperkit" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker-compose.bash-completion", + { + "target": "$HOMEBREW_PREFIX/etc/bash_completion.d/docker-compose" + } + ] + }, + { + "uninstall_postflight": null + }, + { + "postflight": null + }, + { + "zap": [ + { + "trash": [ + "/usr/local/bin/docker-compose.backup", + "/usr/local/bin/docker.backup", + "~/.docker", + "~/Library/Application Scripts/com.docker.helper", + "~/Library/Application Scripts/group.com.docker", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.docker.helper.sfl*", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.electron.dockerdesktop.sfl*", + "~/Library/Application Support/com.bugsnag.Bugsnag/com.docker.docker", + "~/Library/Application Support/Docker Desktop", + "~/Library/Caches/com.docker.docker", + "~/Library/Caches/com.plausiblelabs.crashreporter.data/com.docker.docker", + "~/Library/Caches/KSCrashReports/Docker", + "~/Library/Containers/com.docker.docker", + "~/Library/Containers/com.docker.helper", + "~/Library/Group Containers/group.com.docker", + "~/Library/HTTPStorages/com.docker.docker", + "~/Library/HTTPStorages/com.docker.docker.binarycookies", + "~/Library/Logs/Docker Desktop", + "~/Library/Preferences/com.docker.docker.plist", + "~/Library/Preferences/com.electron.docker-frontend.plist", + "~/Library/Preferences/com.electron.dockerdesktop.plist", + "~/Library/Saved Application State/com.electron.docker-frontend.savedState", + "~/Library/Saved Application State/com.electron.dockerdesktop.savedState" + ], + "rmdir": [ + "~/Library/Caches/com.plausiblelabs.crashreporter.data", + "~/Library/Caches/KSCrashReports" + ] + } + ] + } + ] + }, + "monterey": { + "url": "https://desktop.docker.com/mac/main/amd64/165256/Docker.dmg", + "sha256": "ea4e558d38fac4caafeb73134f012524f3b5a3b90894485f19ae99b0aa7cde19", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": [ + "com.docker.helper", + "com.docker.socket", + "com.docker.vmnetd" + ], + "quit": "com.docker.docker", + "delete": [ + "/Library/PrivilegedHelperTools/com.docker.socket", + "/Library/PrivilegedHelperTools/com.docker.vmnetd" + ], + "rmdir": "~/.docker/bin" + } + ] + }, + { + "app": [ + "Docker.app" + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker-compose.zsh-completion", + { + "target": "$HOMEBREW_PREFIX/share/zsh/site-functions/_docker-compose" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker-compose.fish-completion", + { + "target": "$HOMEBREW_PREFIX/share/fish/vendor_completions.d/docker-compose.fish" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker", + { + "target": "/usr/local/bin/docker" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-desktop", + { + "target": "/usr/local/bin/docker-credential-desktop" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-ecr-login", + { + "target": "/usr/local/bin/docker-credential-ecr-login" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-osxkeychain", + { + "target": "/usr/local/bin/docker-credential-osxkeychain" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/docker-index", + { + "target": "/usr/local/bin/docker-index" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/hub-tool", + { + "target": "/usr/local/bin/hub-tool" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/bin/kubectl", + { + "target": "/usr/local/bin/kubectl.docker" + } + ] + }, + { + "binary": [ + "$APPDIR/Docker.app/Contents/Resources/cli-plugins/docker-compose", + { + "target": "/usr/local/cli-plugins/docker-compose" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker.bash-completion", + { + "target": "$HOMEBREW_PREFIX/etc/bash_completion.d/docker" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker.zsh-completion", + { + "target": "$HOMEBREW_PREFIX/share/zsh/site-functions/_docker" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker.fish-completion", + { + "target": "$HOMEBREW_PREFIX/share/fish/vendor_completions.d/docker.fish" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/bin/com.docker.hyperkit", + { + "target": "/usr/local/bin/hyperkit" + } + ] + }, + { + "binary": [ + "Docker.app/Contents/Resources/etc/docker-compose.bash-completion", + { + "target": "$HOMEBREW_PREFIX/etc/bash_completion.d/docker-compose" + } + ] + }, + { + "uninstall_postflight": null + }, + { + "postflight": null + }, + { + "zap": [ + { + "trash": [ + "/usr/local/bin/docker-compose.backup", + "/usr/local/bin/docker.backup", + "~/.docker", + "~/Library/Application Scripts/com.docker.helper", + "~/Library/Application Scripts/group.com.docker", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.docker.helper.sfl*", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.electron.dockerdesktop.sfl*", + "~/Library/Application Support/com.bugsnag.Bugsnag/com.docker.docker", + "~/Library/Application Support/Docker Desktop", + "~/Library/Caches/com.docker.docker", + "~/Library/Caches/com.plausiblelabs.crashreporter.data/com.docker.docker", + "~/Library/Caches/KSCrashReports/Docker", + "~/Library/Containers/com.docker.docker", + "~/Library/Containers/com.docker.helper", + "~/Library/Group Containers/group.com.docker", + "~/Library/HTTPStorages/com.docker.docker", + "~/Library/HTTPStorages/com.docker.docker.binarycookies", + "~/Library/Logs/Docker Desktop", + "~/Library/Preferences/com.docker.docker.plist", + "~/Library/Preferences/com.electron.docker-frontend.plist", + "~/Library/Preferences/com.electron.dockerdesktop.plist", + "~/Library/Saved Application State/com.electron.docker-frontend.savedState", + "~/Library/Saved Application State/com.electron.dockerdesktop.savedState" + ], + "rmdir": [ + "~/Library/Caches/com.plausiblelabs.crashreporter.data", + "~/Library/Caches/KSCrashReports" + ] + } + ] + } + ] + } + }, + "analytics": { + "install": { + "30d": { + "docker": 18303 + }, + "90d": { + "docker": 54826 + }, + "365d": { + "docker": 232250 + } + } + }, + "generated_date": "2024-09-09" +} diff --git a/server/mdm/maintainedapps/testdata/expected_apps.json b/server/mdm/maintainedapps/testdata/expected_apps.json new file mode 100644 index 000000000000..1da9cc4584a0 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/expected_apps.json @@ -0,0 +1,97 @@ +[ + { + "name": "1Password", + "version": "8.10.44", + "platform": "darwin" + }, + { + "name": "Adobe Acrobat Reader", + "version": "24.002.21005", + "platform": "darwin" + }, + { + "name": "Box Drive", + "version": "2.40.345", + "platform": "darwin" + }, + { + "name": "Brave", + "version": "1.69.162.0", + "platform": "darwin" + }, + { + "name": "Cloudflare WARP", + "version": "2024.6.474.0", + "platform": "darwin" + }, + { + "name": "Docker Desktop", + "version": "4.34.0,165256", + "platform": "darwin" + }, + { + "name": "Figma", + "version": "124.3.2", + "platform": "darwin" + }, + { + "name": "Mozilla Firefox", + "version": "130.0", + "platform": "darwin" + }, + { + "name": "Google Chrome", + "version": "128.0.6613.120", + "platform": "darwin" + }, + { + "name": "Microsoft Edge", + "version": "128.0.2739.67,be6d4c8f-ec75-405e-a5f7-fec66b2898a2", + "platform": "darwin" + }, + { + "name": "Microsoft Excel", + "version": "16.88.24081116", + "platform": "darwin" + }, + { + "name": "Microsoft Teams", + "version": "24215.1002.3039.5089", + "platform": "darwin" + }, + { + "name": "Microsoft Word", + "version": "16.88.24081116", + "platform": "darwin" + }, + { + "name": "Notion", + "version": "3.14.0", + "platform": "darwin" + }, + { + "name": "Slack", + "version": "4.40.126", + "platform": "darwin" + }, + { + "name": "TeamViewer", + "version": "15.57.5", + "platform": "darwin" + }, + { + "name": "Microsoft Visual Studio Code", + "version": "1.93.0", + "platform": "darwin" + }, + { + "name": "WhatsApp", + "version": "2.24.16.80", + "platform": "darwin" + }, + { + "name": "Zoom", + "version": "6.1.11.39163", + "platform": "darwin" + } +] diff --git a/server/mdm/maintainedapps/testdata/figma.json b/server/mdm/maintainedapps/testdata/figma.json new file mode 100644 index 000000000000..d8af9b3e4a8f --- /dev/null +++ b/server/mdm/maintainedapps/testdata/figma.json @@ -0,0 +1,114 @@ +{ + "token": "figma", + "full_token": "figma", + "old_tokens": [], + "tap": "homebrew/cask", + "name": [ + "Figma" + ], + "desc": "Collaborative team software", + "homepage": "https://www.figma.com/", + "url": "https://desktop.figma.com/mac-arm/Figma-124.3.2.zip", + "url_specs": {}, + "version": "124.3.2", + "installed": null, + "installed_time": null, + "bundle_version": null, + "bundle_short_version": null, + "outdated": false, + "sha256": "65fa321f505faf7eb55cb639c5104a502e65cd015437c02551d3c492a62a444e", + "artifacts": [ + { + "app": [ + "Figma.app" + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Support/Figma", + "~/Library/Application Support/figma-desktop", + "~/Library/Caches/com.figma.agent", + "~/Library/Caches/com.figma.Desktop", + "~/Library/Preferences/com.figma.Desktop.plist", + "~/Library/Saved Application State/com.figma.Desktop.savedState" + ] + } + ] + } + ], + "caveats": null, + "depends_on": {}, + "conflicts_with": null, + "container": null, + "auto_updates": true, + "deprecated": false, + "deprecation_date": null, + "deprecation_reason": null, + "disabled": false, + "disable_date": null, + "disable_reason": null, + "tap_git_head": "5e76d542a4e6e78701fdfae86e263074eab2a3e7", + "languages": [], + "ruby_source_path": "Casks/f/figma.rb", + "ruby_source_checksum": { + "sha256": "ee6620d8218afa5f7a5ba44f4a2a73232a90d2cf6b7d1be7f2dbed4afbc7571e" + }, + "variations": { + "sequoia": { + "url": "https://desktop.figma.com/mac/Figma-124.3.2.zip", + "sha256": "a847964fe508bcf9b193fc00eaafb54d2d21f0f2bcd047418e046b7ec3d5ce5f" + }, + "sonoma": { + "url": "https://desktop.figma.com/mac/Figma-124.3.2.zip", + "sha256": "a847964fe508bcf9b193fc00eaafb54d2d21f0f2bcd047418e046b7ec3d5ce5f" + }, + "ventura": { + "url": "https://desktop.figma.com/mac/Figma-124.3.2.zip", + "sha256": "a847964fe508bcf9b193fc00eaafb54d2d21f0f2bcd047418e046b7ec3d5ce5f" + }, + "monterey": { + "url": "https://desktop.figma.com/mac/Figma-124.3.2.zip", + "sha256": "a847964fe508bcf9b193fc00eaafb54d2d21f0f2bcd047418e046b7ec3d5ce5f" + }, + "big_sur": { + "url": "https://desktop.figma.com/mac/Figma-124.3.2.zip", + "sha256": "a847964fe508bcf9b193fc00eaafb54d2d21f0f2bcd047418e046b7ec3d5ce5f" + }, + "catalina": { + "url": "https://desktop.figma.com/mac/Figma-124.3.2.zip", + "sha256": "a847964fe508bcf9b193fc00eaafb54d2d21f0f2bcd047418e046b7ec3d5ce5f" + }, + "mojave": { + "url": "https://desktop.figma.com/mac/Figma-124.3.2.zip", + "sha256": "a847964fe508bcf9b193fc00eaafb54d2d21f0f2bcd047418e046b7ec3d5ce5f" + }, + "high_sierra": { + "url": "https://desktop.figma.com/mac/Figma-124.3.2.zip", + "sha256": "a847964fe508bcf9b193fc00eaafb54d2d21f0f2bcd047418e046b7ec3d5ce5f" + }, + "sierra": { + "url": "https://desktop.figma.com/mac/Figma-124.3.2.zip", + "sha256": "a847964fe508bcf9b193fc00eaafb54d2d21f0f2bcd047418e046b7ec3d5ce5f" + }, + "el_capitan": { + "url": "https://desktop.figma.com/mac/Figma-124.3.2.zip", + "sha256": "a847964fe508bcf9b193fc00eaafb54d2d21f0f2bcd047418e046b7ec3d5ce5f" + } + }, + "analytics": { + "install": { + "30d": { + "figma": 2271 + }, + "90d": { + "figma": 7022 + }, + "365d": { + "figma": 27805 + } + } + }, + "generated_date": "2024-09-09" +} diff --git a/server/mdm/maintainedapps/testdata/firefox.json b/server/mdm/maintainedapps/testdata/firefox.json new file mode 100644 index 000000000000..ed766c177114 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/firefox.json @@ -0,0 +1,169 @@ +{ + "token": "firefox", + "full_token": "firefox", + "old_tokens": [], + "tap": "homebrew/cask", + "name": [ + "Mozilla Firefox" + ], + "desc": "Web browser", + "homepage": "https://www.mozilla.org/firefox/", + "url": "https://download-installer.cdn.mozilla.net/pub/firefox/releases/130.0/mac/en-US/Firefox%20130.0.dmg", + "url_specs": { + "verified": "download-installer.cdn.mozilla.net/pub/firefox/releases/" + }, + "version": "130.0", + "installed": null, + "installed_time": null, + "bundle_version": null, + "bundle_short_version": null, + "outdated": false, + "sha256": "c89ee9abcf7a3554959a7d2db0a5a748be3ae2c30d7c170b879de10e46127602", + "artifacts": [ + { + "preflight": null + }, + { + "uninstall": [ + { + "quit": "org.mozilla.firefox" + } + ] + }, + { + "app": [ + "Firefox.app" + ] + }, + { + "binary": [ + "$HOMEBREW_PREFIX/Caskroom/firefox/130.0/firefox.wrapper.sh", + { + "target": "firefox" + } + ] + }, + { + "zap": [ + { + "trash": [ + "/Library/Logs/DiagnosticReports/firefox_*", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/org.mozilla.firefox.sfl*", + "~/Library/Application Support/CrashReporter/firefox_*", + "~/Library/Application Support/Firefox", + "~/Library/Caches/Firefox", + "~/Library/Caches/Mozilla/updates/Applications/Firefox", + "~/Library/Caches/org.mozilla.crashreporter", + "~/Library/Caches/org.mozilla.firefox", + "~/Library/Preferences/org.mozilla.crashreporter.plist", + "~/Library/Preferences/org.mozilla.firefox.plist", + "~/Library/Saved Application State/org.mozilla.firefox.savedState", + "~/Library/WebKit/org.mozilla.firefox" + ], + "rmdir": [ + "~/Library/Application Support/Mozilla", + "~/Library/Caches/Mozilla", + "~/Library/Caches/Mozilla/updates", + "~/Library/Caches/Mozilla/updates/Applications" + ] + } + ] + } + ], + "caveats": null, + "depends_on": { + "macos": { + ">=": [ + "10.15" + ] + } + }, + "conflicts_with": { + "cask": [ + "firefox@beta", + "firefox@cn", + "firefox@esr" + ] + }, + "container": null, + "auto_updates": true, + "deprecated": false, + "deprecation_date": null, + "deprecation_reason": null, + "disabled": false, + "disable_date": null, + "disable_reason": null, + "tap_git_head": "5e76d542a4e6e78701fdfae86e263074eab2a3e7", + "languages": [ + "af", + "ar", + "be", + "bg", + "bn", + "ca", + "cs", + "de", + "en-CA", + "en-GB", + "en", + "eo", + "es-AR", + "es-CL", + "es-ES", + "fa", + "ff", + "fi", + "fr", + "gl", + "gn", + "gu", + "he", + "hi", + "in", + "it", + "ja", + "ka", + "ko", + "mr", + "my", + "ne", + "nl", + "pa-IN", + "pl", + "pt-BR", + "pt", + "ru", + "si", + "sq", + "sr", + "sv", + "ta", + "te", + "th", + "tl", + "tr", + "uk", + "ur", + "zh-TW", + "zh" + ], + "ruby_source_path": "Casks/f/firefox.rb", + "ruby_source_checksum": { + "sha256": "d50e081cb3abda7d7eecf60728b37a4d8e8f616473bd0b32d280f1327f23ac5e" + }, + "variations": {}, + "analytics": { + "install": { + "30d": { + "firefox": 14543 + }, + "90d": { + "firefox": 39158 + }, + "365d": { + "firefox": 148931 + } + } + }, + "generated_date": "2024-09-09" +} diff --git a/server/mdm/maintainedapps/testdata/google-chrome.json b/server/mdm/maintainedapps/testdata/google-chrome.json new file mode 100644 index 000000000000..aaae6a12910b --- /dev/null +++ b/server/mdm/maintainedapps/testdata/google-chrome.json @@ -0,0 +1,105 @@ +{ + "token": "google-chrome", + "full_token": "google-chrome", + "old_tokens": [], + "tap": "homebrew/cask", + "name": [ + "Google Chrome" + ], + "desc": "Web browser", + "homepage": "https://www.google.com/chrome/", + "url": "https://dl.google.com/chrome/mac/universal/stable/GGRO/googlechrome.dmg", + "url_specs": {}, + "version": "128.0.6613.120", + "installed": null, + "installed_time": null, + "bundle_version": "6613.120", + "bundle_short_version": "128.0.6613.120", + "outdated": false, + "sha256": "no_check", + "artifacts": [ + { + "app": [ + "Google Chrome.app" + ] + }, + { + "zap": [ + { + "launchctl": [ + "com.google.keystone.agent", + "com.google.keystone.daemon" + ], + "trash": [ + "/Library/Caches/com.google.SoftwareUpdate.*", + "/Library/Google/Google Chrome Brand.plist", + "/Library/Google/GoogleSoftwareUpdate", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.google.chrome.app.*.sfl*", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.google.chrome.sfl*", + "~/Library/Application Support/Google/Chrome", + "~/Library/Caches/com.google.Chrome", + "~/Library/Caches/com.google.Chrome.helper.*", + "~/Library/Caches/com.google.Keystone", + "~/Library/Caches/com.google.Keystone.Agent", + "~/Library/Caches/com.google.SoftwareUpdate", + "~/Library/Caches/Google/Chrome", + "~/Library/Google/Google Chrome Brand.plist", + "~/Library/Google/GoogleSoftwareUpdate", + "~/Library/LaunchAgents/com.google.keystone.agent.plist", + "~/Library/LaunchAgents/com.google.keystone.xpcservice.plist", + "~/Library/Logs/GoogleSoftwareUpdateAgent.log", + "~/Library/Preferences/com.google.Chrome.plist", + "~/Library/Preferences/com.google.Keystone.Agent.plist", + "~/Library/Saved Application State/com.google.Chrome.app.*.savedState", + "~/Library/Saved Application State/com.google.Chrome.savedState", + "~/Library/WebKit/com.google.Chrome" + ], + "rmdir": [ + "/Library/Google", + "~/Library/Application Support/Google", + "~/Library/Caches/Google", + "~/Library/Google" + ] + } + ] + } + ], + "caveats": null, + "depends_on": { + "macos": { + ">=": [ + "10.15" + ] + } + }, + "conflicts_with": null, + "container": null, + "auto_updates": true, + "deprecated": false, + "deprecation_date": null, + "deprecation_reason": null, + "disabled": false, + "disable_date": null, + "disable_reason": null, + "tap_git_head": "5e76d542a4e6e78701fdfae86e263074eab2a3e7", + "languages": [], + "ruby_source_path": "Casks/g/google-chrome.rb", + "ruby_source_checksum": { + "sha256": "b31aabe1674fdecda8d5178de43a5188cd1a58c24e844260ea1dccfa6f293340" + }, + "variations": {}, + "analytics": { + "install": { + "30d": { + "google-chrome": 19149 + }, + "90d": { + "google-chrome": 54038 + }, + "365d": { + "google-chrome": 216929 + } + } + }, + "generated_date": "2024-09-09" +} diff --git a/server/mdm/maintainedapps/testdata/microsoft-edge.json b/server/mdm/maintainedapps/testdata/microsoft-edge.json new file mode 100644 index 000000000000..e7b1827cc59f --- /dev/null +++ b/server/mdm/maintainedapps/testdata/microsoft-edge.json @@ -0,0 +1,154 @@ +{ + "token": "microsoft-edge", + "full_token": "microsoft-edge", + "old_tokens": [], + "tap": "homebrew/cask", + "name": [ + "Microsoft Edge" + ], + "desc": "Web browser", + "homepage": "https://www.microsoft.com/en-us/edge?form=", + "url": "https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/be6d4c8f-ec75-405e-a5f7-fec66b2898a2/MicrosoftEdge-128.0.2739.67.pkg", + "url_specs": {}, + "version": "128.0.2739.67,be6d4c8f-ec75-405e-a5f7-fec66b2898a2", + "installed": null, + "installed_time": null, + "bundle_version": null, + "bundle_short_version": null, + "outdated": false, + "sha256": "cb4ddcbe57be83d1ec82d4ac76062a49bcd92e5bb27e04e8ff01dbda9045f449", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": [ + "com.microsoft.EdgeUpdater.update-internal.109.0.1518.89.system", + "com.microsoft.EdgeUpdater.update.system", + "com.microsoft.EdgeUpdater.wake.system" + ], + "pkgutil": "com.microsoft.edgemac" + } + ] + }, + { + "pkg": [ + "MicrosoftEdge-128.0.2739.67.pkg", + { + "choices": [ + { + "choiceIdentifier": "com.microsoft.package.Microsoft_AutoUpdate.app", + "choiceAttribute": "selected", + "attributeSetting": 0 + } + ] + } + ] + }, + { + "zap": [ + { + "delete": "/Library/Application Support/Microsoft/EdgeUpdater", + "trash": [ + "~/Library/Application Scripts/com.microsoft.edgemac.wdgExtension", + "~/Library/Application Support/Microsoft Edge", + "~/Library/Application Support/Microsoft/EdgeUpdater", + "~/Library/Caches/com.microsoft.edgemac", + "~/Library/Caches/com.microsoft.EdgeUpdater", + "~/Library/Caches/Microsoft Edge", + "~/Library/Containers/com.microsoft.edgemac.wdgExtension", + "~/Library/HTTPStorages/com.microsoft.edge*", + "~/Library/LaunchAgents/com.microsoft.EdgeUpdater.*.plist", + "~/Library/Microsoft/EdgeUpdater", + "~/Library/Preferences/com.microsoft.edgemac.plist", + "~/Library/Saved Application State/com.microsoft.edgemac.*", + "~/Library/WebKit/com.microsoft.edgemac" + ], + "rmdir": "/Library/Application Support/Microsoft" + } + ] + } + ], + "caveats": null, + "depends_on": {}, + "conflicts_with": null, + "container": null, + "auto_updates": true, + "deprecated": false, + "deprecation_date": null, + "deprecation_reason": null, + "disabled": false, + "disable_date": null, + "disable_reason": null, + "tap_git_head": "5e76d542a4e6e78701fdfae86e263074eab2a3e7", + "languages": [], + "ruby_source_path": "Casks/m/microsoft-edge.rb", + "ruby_source_checksum": { + "sha256": "71d9afd8b4f0ef22a30f7b987fd94daed8fbe3fcd0c0fb9b55c1738c554cb887" + }, + "variations": { + "sequoia": { + "url": "https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/3d8bc4f8-f1af-448e-b039-10a86db63085/MicrosoftEdge-128.0.2739.67.pkg", + "version": "128.0.2739.67,3d8bc4f8-f1af-448e-b039-10a86db63085", + "sha256": "4d79cc82038de72dd9efab38e69ceb6ed0f1fd90551f0d4764a593a428dfbfb1" + }, + "sonoma": { + "url": "https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/3d8bc4f8-f1af-448e-b039-10a86db63085/MicrosoftEdge-128.0.2739.67.pkg", + "version": "128.0.2739.67,3d8bc4f8-f1af-448e-b039-10a86db63085", + "sha256": "4d79cc82038de72dd9efab38e69ceb6ed0f1fd90551f0d4764a593a428dfbfb1" + }, + "ventura": { + "url": "https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/3d8bc4f8-f1af-448e-b039-10a86db63085/MicrosoftEdge-128.0.2739.67.pkg", + "version": "128.0.2739.67,3d8bc4f8-f1af-448e-b039-10a86db63085", + "sha256": "4d79cc82038de72dd9efab38e69ceb6ed0f1fd90551f0d4764a593a428dfbfb1" + }, + "monterey": { + "url": "https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/3d8bc4f8-f1af-448e-b039-10a86db63085/MicrosoftEdge-128.0.2739.67.pkg", + "version": "128.0.2739.67,3d8bc4f8-f1af-448e-b039-10a86db63085", + "sha256": "4d79cc82038de72dd9efab38e69ceb6ed0f1fd90551f0d4764a593a428dfbfb1" + }, + "big_sur": { + "url": "https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/3d8bc4f8-f1af-448e-b039-10a86db63085/MicrosoftEdge-128.0.2739.67.pkg", + "version": "128.0.2739.67,3d8bc4f8-f1af-448e-b039-10a86db63085", + "sha256": "4d79cc82038de72dd9efab38e69ceb6ed0f1fd90551f0d4764a593a428dfbfb1" + }, + "catalina": { + "url": "https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/3d8bc4f8-f1af-448e-b039-10a86db63085/MicrosoftEdge-128.0.2739.67.pkg", + "version": "128.0.2739.67,3d8bc4f8-f1af-448e-b039-10a86db63085", + "sha256": "4d79cc82038de72dd9efab38e69ceb6ed0f1fd90551f0d4764a593a428dfbfb1" + }, + "mojave": { + "url": "https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/3d8bc4f8-f1af-448e-b039-10a86db63085/MicrosoftEdge-128.0.2739.67.pkg", + "version": "128.0.2739.67,3d8bc4f8-f1af-448e-b039-10a86db63085", + "sha256": "4d79cc82038de72dd9efab38e69ceb6ed0f1fd90551f0d4764a593a428dfbfb1" + }, + "high_sierra": { + "url": "https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/3d8bc4f8-f1af-448e-b039-10a86db63085/MicrosoftEdge-128.0.2739.67.pkg", + "version": "128.0.2739.67,3d8bc4f8-f1af-448e-b039-10a86db63085", + "sha256": "4d79cc82038de72dd9efab38e69ceb6ed0f1fd90551f0d4764a593a428dfbfb1" + }, + "sierra": { + "url": "https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/3d8bc4f8-f1af-448e-b039-10a86db63085/MicrosoftEdge-128.0.2739.67.pkg", + "version": "128.0.2739.67,3d8bc4f8-f1af-448e-b039-10a86db63085", + "sha256": "4d79cc82038de72dd9efab38e69ceb6ed0f1fd90551f0d4764a593a428dfbfb1" + }, + "el_capitan": { + "url": "https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/3d8bc4f8-f1af-448e-b039-10a86db63085/MicrosoftEdge-128.0.2739.67.pkg", + "version": "128.0.2739.67,3d8bc4f8-f1af-448e-b039-10a86db63085", + "sha256": "4d79cc82038de72dd9efab38e69ceb6ed0f1fd90551f0d4764a593a428dfbfb1" + } + }, + "analytics": { + "install": { + "30d": { + "microsoft-edge": 3588 + }, + "90d": { + "microsoft-edge": 10949 + }, + "365d": { + "microsoft-edge": 51951 + } + } + }, + "generated_date": "2024-09-09" +} diff --git a/server/mdm/maintainedapps/testdata/microsoft-excel.json b/server/mdm/maintainedapps/testdata/microsoft-excel.json new file mode 100644 index 000000000000..d62356f284a8 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/microsoft-excel.json @@ -0,0 +1,441 @@ +{ + "token": "microsoft-excel", + "full_token": "microsoft-excel", + "old_tokens": [], + "tap": "homebrew/cask", + "name": [ + "Microsoft Excel" + ], + "desc": "Spreadsheet software", + "homepage": "https://www.microsoft.com/en-US/microsoft-365/excel", + "url": "https://officecdnmac.microsoft.com/pr/C1297A47-86C4-4C1F-97FA-950631F94777/MacAutoupdate/Microsoft_Excel_16.88.24081116_Installer.pkg", + "url_specs": {}, + "version": "16.88.24081116", + "installed": null, + "installed_time": null, + "bundle_version": null, + "bundle_short_version": null, + "outdated": false, + "sha256": "2af9d2d80d46942aa634223f54608ee75f26b973d89afbe79de2a56e6dcbc3b4", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": "com.microsoft.office.licensingV2.helper", + "quit": "com.microsoft.autoupdate2", + "pkgutil": [ + "com.microsoft.package.Microsoft_Excel.app", + "com.microsoft.pkg.licensing" + ] + } + ] + }, + { + "pkg": [ + "Microsoft_Excel_16.88.24081116_Installer.pkg", + { + "choices": [ + { + "choiceIdentifier": "com.microsoft.autoupdate", + "choiceAttribute": "selected", + "attributeSetting": 0 + } + ] + } + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Scripts/com.microsoft.Excel", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.microsoft.excel.sfl*", + "~/Library/Caches/com.microsoft.Excel", + "~/Library/Containers/com.microsoft.Excel", + "~/Library/Preferences/com.microsoft.Excel.plist", + "~/Library/Saved Application State/com.microsoft.Excel.savedState", + "~/Library/Webkit/com.microsoft.Excel" + ] + } + ] + } + ], + "caveats": null, + "depends_on": { + "cask": [ + "microsoft-auto-update" + ] + }, + "conflicts_with": { + "cask": [ + "microsoft-office", + "microsoft-office-businesspro" + ] + }, + "container": null, + "auto_updates": true, + "deprecated": false, + "deprecation_date": null, + "deprecation_reason": null, + "disabled": false, + "disable_date": null, + "disable_reason": null, + "tap_git_head": "5e76d542a4e6e78701fdfae86e263074eab2a3e7", + "languages": [], + "ruby_source_path": "Casks/m/microsoft-excel.rb", + "ruby_source_checksum": { + "sha256": "ba9a8018661312bf6e7f2bab84b37ccd4ab2e7f25185d626602b058498a13d0a" + }, + "variations": { + "big_sur": { + "url": "https://officecdnmac.microsoft.com/pr/C1297A47-86C4-4C1F-97FA-950631F94777/MacAutoupdate/Microsoft_Excel_16.77.23091703_Installer.pkg", + "version": "16.77.23091703", + "sha256": "582fca32104e828e01c0928e674122f2d8044d84fd2dc1d7964e0a807e2f4695", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": "com.microsoft.office.licensingV2.helper", + "quit": "com.microsoft.autoupdate2", + "pkgutil": [ + "com.microsoft.package.Microsoft_Excel.app", + "com.microsoft.pkg.licensing" + ] + } + ] + }, + { + "pkg": [ + "Microsoft_Excel_16.77.23091703_Installer.pkg", + { + "choices": [ + { + "choiceIdentifier": "com.microsoft.autoupdate", + "choiceAttribute": "selected", + "attributeSetting": 0 + } + ] + } + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Scripts/com.microsoft.Excel", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.microsoft.excel.sfl*", + "~/Library/Caches/com.microsoft.Excel", + "~/Library/Containers/com.microsoft.Excel", + "~/Library/Preferences/com.microsoft.Excel.plist", + "~/Library/Saved Application State/com.microsoft.Excel.savedState", + "~/Library/Webkit/com.microsoft.Excel" + ] + } + ] + } + ] + }, + "arm64_big_sur": { + "url": "https://officecdnmac.microsoft.com/pr/C1297A47-86C4-4C1F-97FA-950631F94777/MacAutoupdate/Microsoft_Excel_16.77.23091703_Installer.pkg", + "version": "16.77.23091703", + "sha256": "582fca32104e828e01c0928e674122f2d8044d84fd2dc1d7964e0a807e2f4695", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": "com.microsoft.office.licensingV2.helper", + "quit": "com.microsoft.autoupdate2", + "pkgutil": [ + "com.microsoft.package.Microsoft_Excel.app", + "com.microsoft.pkg.licensing" + ] + } + ] + }, + { + "pkg": [ + "Microsoft_Excel_16.77.23091703_Installer.pkg", + { + "choices": [ + { + "choiceIdentifier": "com.microsoft.autoupdate", + "choiceAttribute": "selected", + "attributeSetting": 0 + } + ] + } + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Scripts/com.microsoft.Excel", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.microsoft.excel.sfl*", + "~/Library/Caches/com.microsoft.Excel", + "~/Library/Containers/com.microsoft.Excel", + "~/Library/Preferences/com.microsoft.Excel.plist", + "~/Library/Saved Application State/com.microsoft.Excel.savedState", + "~/Library/Webkit/com.microsoft.Excel" + ] + } + ] + } + ] + }, + "catalina": { + "url": "https://officecdnmac.microsoft.com/pr/C1297A47-86C4-4C1F-97FA-950631F94777/MacAutoupdate/Microsoft_Excel_16.66.22101101_Installer.pkg", + "version": "16.66.22101101", + "sha256": "94148628c6f143f07555b3d2a70cea61cef817d963539d281b092834496f8f16", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": "com.microsoft.office.licensingV2.helper", + "quit": "com.microsoft.autoupdate2", + "pkgutil": [ + "com.microsoft.package.Microsoft_Excel.app", + "com.microsoft.pkg.licensing" + ] + } + ] + }, + { + "pkg": [ + "Microsoft_Excel_16.66.22101101_Installer.pkg", + { + "choices": [ + { + "choiceIdentifier": "com.microsoft.autoupdate", + "choiceAttribute": "selected", + "attributeSetting": 0 + } + ] + } + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Scripts/com.microsoft.Excel", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.microsoft.excel.sfl*", + "~/Library/Caches/com.microsoft.Excel", + "~/Library/Containers/com.microsoft.Excel", + "~/Library/Preferences/com.microsoft.Excel.plist", + "~/Library/Saved Application State/com.microsoft.Excel.savedState", + "~/Library/Webkit/com.microsoft.Excel" + ] + } + ] + } + ] + }, + "mojave": { + "url": "https://officecdnmac.microsoft.com/pr/C1297A47-86C4-4C1F-97FA-950631F94777/MacAutoupdate/Microsoft_Excel_16.54.21101001_Installer.pkg", + "version": "16.54.21101001", + "sha256": "e09fe9f49a36b37af3745673a385be4de9ae8ec774965fd1753f8479a775fc54", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": "com.microsoft.office.licensingV2.helper", + "quit": "com.microsoft.autoupdate2", + "pkgutil": [ + "com.microsoft.package.Microsoft_Excel.app", + "com.microsoft.pkg.licensing" + ] + } + ] + }, + { + "pkg": [ + "Microsoft_Excel_16.54.21101001_Installer.pkg", + { + "choices": [ + { + "choiceIdentifier": "com.microsoft.autoupdate", + "choiceAttribute": "selected", + "attributeSetting": 0 + } + ] + } + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Scripts/com.microsoft.Excel", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.microsoft.excel.sfl*", + "~/Library/Caches/com.microsoft.Excel", + "~/Library/Containers/com.microsoft.Excel", + "~/Library/Preferences/com.microsoft.Excel.plist", + "~/Library/Saved Application State/com.microsoft.Excel.savedState", + "~/Library/Webkit/com.microsoft.Excel" + ] + } + ] + } + ] + }, + "high_sierra": { + "url": "https://officecdnmac.microsoft.com/pr/C1297A47-86C4-4C1F-97FA-950631F94777/MacAutoupdate/Microsoft_Excel_16.43.20110804_Installer.pkg", + "version": "16.43.20110804", + "sha256": "2711a1b8864f7474458086b4b0a56673fee0097d2049f276788c50e004c47d72", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": "com.microsoft.office.licensingV2.helper", + "quit": "com.microsoft.autoupdate2", + "pkgutil": [ + "com.microsoft.package.Microsoft_Excel.app", + "com.microsoft.pkg.licensing" + ] + } + ] + }, + { + "pkg": [ + "Microsoft_Excel_16.43.20110804_Installer.pkg", + { + "choices": [ + { + "choiceIdentifier": "com.microsoft.autoupdate", + "choiceAttribute": "selected", + "attributeSetting": 0 + } + ] + } + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Scripts/com.microsoft.Excel", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.microsoft.excel.sfl*", + "~/Library/Caches/com.microsoft.Excel", + "~/Library/Containers/com.microsoft.Excel", + "~/Library/Preferences/com.microsoft.Excel.plist", + "~/Library/Saved Application State/com.microsoft.Excel.savedState", + "~/Library/Webkit/com.microsoft.Excel" + ] + } + ] + } + ] + }, + "sierra": { + "url": "https://officecdnmac.microsoft.com/pr/C1297A47-86C4-4C1F-97FA-950631F94777/MacAutoupdate/Microsoft_Excel_16.30.19101301_Installer.pkg", + "version": "16.30.19101301", + "sha256": "9886b661067f4a99de544d140980fb0f8ef2f4871baa519024781fb814a02fe5", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": "com.microsoft.office.licensingV2.helper", + "quit": "com.microsoft.autoupdate2", + "pkgutil": [ + "com.microsoft.package.Microsoft_Excel.app", + "com.microsoft.pkg.licensing" + ] + } + ] + }, + { + "pkg": [ + "Microsoft_Excel_16.30.19101301_Installer.pkg", + { + "choices": [ + { + "choiceIdentifier": "com.microsoft.autoupdate", + "choiceAttribute": "selected", + "attributeSetting": 0 + } + ] + } + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Scripts/com.microsoft.Excel", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.microsoft.excel.sfl*", + "~/Library/Caches/com.microsoft.Excel", + "~/Library/Containers/com.microsoft.Excel", + "~/Library/Preferences/com.microsoft.Excel.plist", + "~/Library/Saved Application State/com.microsoft.Excel.savedState", + "~/Library/Webkit/com.microsoft.Excel" + ] + } + ] + } + ] + }, + "el_capitan": { + "url": "https://officecdnmac.microsoft.com/pr/C1297A47-86C4-4C1F-97FA-950631F94777/MacAutoupdate/Microsoft_Excel_16.16.20101200_Installer.pkg", + "version": "16.16.20101200", + "sha256": "bdd23b696d54e5ffeb40f30a9bd7f968d2936380ab78a6eaf29d05f5fc8eb78e", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": "com.microsoft.office.licensingV2.helper", + "quit": "com.microsoft.autoupdate2", + "pkgutil": [ + "com.microsoft.package.Microsoft_Excel.app", + "com.microsoft.pkg.licensing" + ] + } + ] + }, + { + "pkg": [ + "Microsoft_Excel_16.16.20101200_Installer.pkg", + { + "choices": [ + { + "choiceIdentifier": "com.microsoft.autoupdate", + "choiceAttribute": "selected", + "attributeSetting": 0 + } + ] + } + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Scripts/com.microsoft.Excel", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.microsoft.excel.sfl*", + "~/Library/Caches/com.microsoft.Excel", + "~/Library/Containers/com.microsoft.Excel", + "~/Library/Preferences/com.microsoft.Excel.plist", + "~/Library/Saved Application State/com.microsoft.Excel.savedState", + "~/Library/Webkit/com.microsoft.Excel" + ] + } + ] + } + ] + } + }, + "analytics": { + "install": { + "30d": { + "microsoft-excel": 873 + }, + "90d": { + "microsoft-excel": 2501 + }, + "365d": { + "microsoft-excel": 10581 + } + } + }, + "generated_date": "2024-09-09" +} diff --git a/server/mdm/maintainedapps/testdata/microsoft-teams.json b/server/mdm/maintainedapps/testdata/microsoft-teams.json new file mode 100644 index 000000000000..07af90bdc742 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/microsoft-teams.json @@ -0,0 +1,132 @@ +{ + "token": "microsoft-teams", + "full_token": "microsoft-teams", + "old_tokens": [], + "tap": "homebrew/cask", + "name": [ + "Microsoft Teams" + ], + "desc": "Meet, chat, call, and collaborate in just one place", + "homepage": "https://www.microsoft.com/en/microsoft-teams/group-chat-software/", + "url": "https://statics.teams.cdn.office.net/production-osx/24215.1002.3039.5089/MicrosoftTeams.pkg", + "url_specs": { + "verified": "statics.teams.cdn.office.net/production-osx/" + }, + "version": "24215.1002.3039.5089", + "installed": null, + "installed_time": null, + "bundle_version": null, + "bundle_short_version": null, + "outdated": false, + "sha256": "df7ca5415b8ab0acd9f1002d9f04e0dab5782fa4cf1768331e22d14665a2b148", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": "com.microsoft.teams.TeamsUpdaterDaemon", + "quit": "com.microsoft.autoupdate2", + "pkgutil": [ + "com.microsoft.MSTeamsAudioDevice", + "com.microsoft.package.Microsoft_AutoUpdate.app", + "com.microsoft.teams2" + ], + "delete": [ + "/Applications/Microsoft Teams.app", + "/Library/Application Support/Microsoft/TeamsUpdaterDaemon", + "/Library/Logs/Microsoft/MSTeams", + "/Library/Logs/Microsoft/Teams", + "/Library/Preferences/com.microsoft.teams.plist" + ] + } + ] + }, + { + "pkg": [ + "MicrosoftTeams.pkg", + { + "choices": [ + { + "choiceIdentifier": "com.microsoft.autoupdate", + "choiceAttribute": "selected", + "attributeSetting": 0 + } + ] + } + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Scripts/com.microsoft.teams2", + "~/Library/Application Scripts/com.microsoft.teams2.launcher", + "~/Library/Application Scripts/com.microsoft.teams2.notificationcenter", + "~/Library/Application Support/com.microsoft.teams", + "~/Library/Application Support/Microsoft/Teams", + "~/Library/Application Support/Teams", + "~/Library/Caches/com.microsoft.teams", + "~/Library/Containers/com.microsoft.teams2", + "~/Library/Containers/com.microsoft.teams2.launcher", + "~/Library/Containers/com.microsoft.teams2.notificationcenter", + "~/Library/Cookies/com.microsoft.teams.binarycookies", + "~/Library/Group Containers/*.com.microsoft.teams", + "~/Library/HTTPStorages/com.microsoft.teams", + "~/Library/HTTPStorages/com.microsoft.teams.binarycookies", + "~/Library/Logs/Microsoft Teams Helper (Renderer)", + "~/Library/Logs/Microsoft Teams", + "~/Library/Preferences/com.microsoft.teams.plist", + "~/Library/Saved Application State/com.microsoft.teams.savedState", + "~/Library/Saved Application State/com.microsoft.teams2.savedState", + "~/Library/WebKit/com.microsoft.teams" + ], + "rmdir": "~/Library/Application Support/Microsoft" + } + ] + } + ], + "caveats": null, + "depends_on": { + "cask": [ + "microsoft-auto-update" + ], + "macos": { + ">=": [ + "11" + ] + } + }, + "conflicts_with": { + "cask": [ + "microsoft-office-businesspro" + ] + }, + "container": null, + "auto_updates": true, + "deprecated": false, + "deprecation_date": null, + "deprecation_reason": null, + "disabled": false, + "disable_date": null, + "disable_reason": null, + "tap_git_head": "5e76d542a4e6e78701fdfae86e263074eab2a3e7", + "languages": [], + "ruby_source_path": "Casks/m/microsoft-teams.rb", + "ruby_source_checksum": { + "sha256": "6deae2be2e0862656eaedff01b767ebc8cb787dc2800ec38209255b2f7580e9a" + }, + "variations": {}, + "analytics": { + "install": { + "30d": { + "microsoft-teams": 3791 + }, + "90d": { + "microsoft-teams": 12487 + }, + "365d": { + "microsoft-teams": 53526 + } + } + }, + "generated_date": "2024-09-09" +} diff --git a/server/mdm/maintainedapps/testdata/microsoft-word.json b/server/mdm/maintainedapps/testdata/microsoft-word.json new file mode 100644 index 000000000000..9b639ed38a73 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/microsoft-word.json @@ -0,0 +1,433 @@ +{ + "token": "microsoft-word", + "full_token": "microsoft-word", + "old_tokens": [], + "tap": "homebrew/cask", + "name": [ + "Microsoft Word" + ], + "desc": "Word processor", + "homepage": "https://www.microsoft.com/en-US/microsoft-365/word", + "url": "https://officecdnmac.microsoft.com/pr/C1297A47-86C4-4C1F-97FA-950631F94777/MacAutoupdate/Microsoft_Word_16.88.24081116_Installer.pkg", + "url_specs": {}, + "version": "16.88.24081116", + "installed": null, + "installed_time": null, + "bundle_version": null, + "bundle_short_version": null, + "outdated": false, + "sha256": "c6ff9937d3a872e87fbd9f83c838baadafbe32025a32a01f87e438a21258031d", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": "com.microsoft.office.licensingV2.helper", + "quit": "com.microsoft.autoupdate2", + "pkgutil": [ + "com.microsoft.package.Microsoft_Word.app", + "com.microsoft.pkg.licensing" + ] + } + ] + }, + { + "pkg": [ + "Microsoft_Word_16.88.24081116_Installer.pkg", + { + "choices": [ + { + "choiceIdentifier": "com.microsoft.autoupdate", + "choiceAttribute": "selected", + "attributeSetting": 0 + } + ] + } + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Scripts/com.microsoft.Word", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.microsoft.word.sfl*", + "~/Library/Application Support/CrashReporter/Microsoft Word_*.plist", + "~/Library/Containers/com.microsoft.Word", + "~/Library/Preferences/com.microsoft.Word.plist", + "~/Library/Saved Application State/com.microsoft.Word.savedState" + ] + } + ] + } + ], + "caveats": null, + "depends_on": { + "cask": [ + "microsoft-auto-update" + ] + }, + "conflicts_with": { + "cask": [ + "microsoft-office", + "microsoft-office-businesspro" + ] + }, + "container": null, + "auto_updates": true, + "deprecated": false, + "deprecation_date": null, + "deprecation_reason": null, + "disabled": false, + "disable_date": null, + "disable_reason": null, + "tap_git_head": "5e76d542a4e6e78701fdfae86e263074eab2a3e7", + "languages": [], + "ruby_source_path": "Casks/m/microsoft-word.rb", + "ruby_source_checksum": { + "sha256": "c1485bc53b17944212ed65da58450fe2d4512cbf524425e251a88900417ea752" + }, + "variations": { + "big_sur": { + "url": "https://officecdnmac.microsoft.com/pr/C1297A47-86C4-4C1F-97FA-950631F94777/MacAutoupdate/Microsoft_Word_16.77.23091703_Installer.pkg", + "version": "16.77.23091703", + "sha256": "10c8db978206275a557faf3650763a656b1f7170c9b2a65fa6fdce220bd23066", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": "com.microsoft.office.licensingV2.helper", + "quit": "com.microsoft.autoupdate2", + "pkgutil": [ + "com.microsoft.package.Microsoft_Word.app", + "com.microsoft.pkg.licensing" + ] + } + ] + }, + { + "pkg": [ + "Microsoft_Word_16.77.23091703_Installer.pkg", + { + "choices": [ + { + "choiceIdentifier": "com.microsoft.autoupdate", + "choiceAttribute": "selected", + "attributeSetting": 0 + } + ] + } + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Scripts/com.microsoft.Word", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.microsoft.word.sfl*", + "~/Library/Application Support/CrashReporter/Microsoft Word_*.plist", + "~/Library/Containers/com.microsoft.Word", + "~/Library/Preferences/com.microsoft.Word.plist", + "~/Library/Saved Application State/com.microsoft.Word.savedState" + ] + } + ] + } + ] + }, + "arm64_big_sur": { + "url": "https://officecdnmac.microsoft.com/pr/C1297A47-86C4-4C1F-97FA-950631F94777/MacAutoupdate/Microsoft_Word_16.77.23091703_Installer.pkg", + "version": "16.77.23091703", + "sha256": "10c8db978206275a557faf3650763a656b1f7170c9b2a65fa6fdce220bd23066", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": "com.microsoft.office.licensingV2.helper", + "quit": "com.microsoft.autoupdate2", + "pkgutil": [ + "com.microsoft.package.Microsoft_Word.app", + "com.microsoft.pkg.licensing" + ] + } + ] + }, + { + "pkg": [ + "Microsoft_Word_16.77.23091703_Installer.pkg", + { + "choices": [ + { + "choiceIdentifier": "com.microsoft.autoupdate", + "choiceAttribute": "selected", + "attributeSetting": 0 + } + ] + } + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Scripts/com.microsoft.Word", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.microsoft.word.sfl*", + "~/Library/Application Support/CrashReporter/Microsoft Word_*.plist", + "~/Library/Containers/com.microsoft.Word", + "~/Library/Preferences/com.microsoft.Word.plist", + "~/Library/Saved Application State/com.microsoft.Word.savedState" + ] + } + ] + } + ] + }, + "catalina": { + "url": "https://officecdnmac.microsoft.com/pr/C1297A47-86C4-4C1F-97FA-950631F94777/MacAutoupdate/Microsoft_Word_16.66.22101101_Installer.pkg", + "version": "16.66.22101101", + "sha256": "5a6a75d9a5b46cceeff5a1b7925c0eab6e4976cba529149b7b291a0355e7a7c9", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": "com.microsoft.office.licensingV2.helper", + "quit": "com.microsoft.autoupdate2", + "pkgutil": [ + "com.microsoft.package.Microsoft_Word.app", + "com.microsoft.pkg.licensing" + ] + } + ] + }, + { + "pkg": [ + "Microsoft_Word_16.66.22101101_Installer.pkg", + { + "choices": [ + { + "choiceIdentifier": "com.microsoft.autoupdate", + "choiceAttribute": "selected", + "attributeSetting": 0 + } + ] + } + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Scripts/com.microsoft.Word", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.microsoft.word.sfl*", + "~/Library/Application Support/CrashReporter/Microsoft Word_*.plist", + "~/Library/Containers/com.microsoft.Word", + "~/Library/Preferences/com.microsoft.Word.plist", + "~/Library/Saved Application State/com.microsoft.Word.savedState" + ] + } + ] + } + ] + }, + "mojave": { + "url": "https://officecdnmac.microsoft.com/pr/C1297A47-86C4-4C1F-97FA-950631F94777/MacAutoupdate/Microsoft_Word_16.54.21101001_Installer.pkg", + "version": "16.54.21101001", + "sha256": "7f3ed397b517aac3637d8b8f8b4233f9e7132941f0657eaca8ec423ac068616e", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": "com.microsoft.office.licensingV2.helper", + "quit": "com.microsoft.autoupdate2", + "pkgutil": [ + "com.microsoft.package.Microsoft_Word.app", + "com.microsoft.pkg.licensing" + ] + } + ] + }, + { + "pkg": [ + "Microsoft_Word_16.54.21101001_Installer.pkg", + { + "choices": [ + { + "choiceIdentifier": "com.microsoft.autoupdate", + "choiceAttribute": "selected", + "attributeSetting": 0 + } + ] + } + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Scripts/com.microsoft.Word", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.microsoft.word.sfl*", + "~/Library/Application Support/CrashReporter/Microsoft Word_*.plist", + "~/Library/Containers/com.microsoft.Word", + "~/Library/Preferences/com.microsoft.Word.plist", + "~/Library/Saved Application State/com.microsoft.Word.savedState" + ] + } + ] + } + ] + }, + "high_sierra": { + "url": "https://officecdnmac.microsoft.com/pr/C1297A47-86C4-4C1F-97FA-950631F94777/MacAutoupdate/Microsoft_Word_16.43.20110804_Installer.pkg", + "version": "16.43.20110804", + "sha256": "3d957d534fb2142f6e95a688552890a31f0d942796f0128ca837a3e98405d413", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": "com.microsoft.office.licensingV2.helper", + "quit": "com.microsoft.autoupdate2", + "pkgutil": [ + "com.microsoft.package.Microsoft_Word.app", + "com.microsoft.pkg.licensing" + ] + } + ] + }, + { + "pkg": [ + "Microsoft_Word_16.43.20110804_Installer.pkg", + { + "choices": [ + { + "choiceIdentifier": "com.microsoft.autoupdate", + "choiceAttribute": "selected", + "attributeSetting": 0 + } + ] + } + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Scripts/com.microsoft.Word", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.microsoft.word.sfl*", + "~/Library/Application Support/CrashReporter/Microsoft Word_*.plist", + "~/Library/Containers/com.microsoft.Word", + "~/Library/Preferences/com.microsoft.Word.plist", + "~/Library/Saved Application State/com.microsoft.Word.savedState" + ] + } + ] + } + ] + }, + "sierra": { + "url": "https://officecdnmac.microsoft.com/pr/C1297A47-86C4-4C1F-97FA-950631F94777/MacAutoupdate/Microsoft_Word_16.30.19101301_Installer.pkg", + "version": "16.30.19101301", + "sha256": "6abd7939b0d935023ebb8fabeb206c4cbbe8eb8f9a3ff7d318448d2ba5f332e4", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": "com.microsoft.office.licensingV2.helper", + "quit": "com.microsoft.autoupdate2", + "pkgutil": [ + "com.microsoft.package.Microsoft_Word.app", + "com.microsoft.pkg.licensing" + ] + } + ] + }, + { + "pkg": [ + "Microsoft_Word_16.30.19101301_Installer.pkg", + { + "choices": [ + { + "choiceIdentifier": "com.microsoft.autoupdate", + "choiceAttribute": "selected", + "attributeSetting": 0 + } + ] + } + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Scripts/com.microsoft.Word", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.microsoft.word.sfl*", + "~/Library/Application Support/CrashReporter/Microsoft Word_*.plist", + "~/Library/Containers/com.microsoft.Word", + "~/Library/Preferences/com.microsoft.Word.plist", + "~/Library/Saved Application State/com.microsoft.Word.savedState" + ] + } + ] + } + ] + }, + "el_capitan": { + "url": "https://officecdnmac.microsoft.com/pr/C1297A47-86C4-4C1F-97FA-950631F94777/MacAutoupdate/Microsoft_Word_16.16.20101200_Installer.pkg", + "version": "16.16.20101200", + "sha256": "0c61b7db7a6a13653270795c085a909aa54668e8de2f2ca749257ce6aa5957d1", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": "com.microsoft.office.licensingV2.helper", + "quit": "com.microsoft.autoupdate2", + "pkgutil": [ + "com.microsoft.package.Microsoft_Word.app", + "com.microsoft.pkg.licensing" + ] + } + ] + }, + { + "pkg": [ + "Microsoft_Word_16.16.20101200_Installer.pkg", + { + "choices": [ + { + "choiceIdentifier": "com.microsoft.autoupdate", + "choiceAttribute": "selected", + "attributeSetting": 0 + } + ] + } + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Scripts/com.microsoft.Word", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.microsoft.word.sfl*", + "~/Library/Application Support/CrashReporter/Microsoft Word_*.plist", + "~/Library/Containers/com.microsoft.Word", + "~/Library/Preferences/com.microsoft.Word.plist", + "~/Library/Saved Application State/com.microsoft.Word.savedState" + ] + } + ] + } + ] + } + }, + "analytics": { + "install": { + "30d": { + "microsoft-word": 793 + }, + "90d": { + "microsoft-word": 2262 + }, + "365d": { + "microsoft-word": 10083 + } + } + }, + "generated_date": "2024-09-09" +} diff --git a/server/mdm/maintainedapps/testdata/notion.json b/server/mdm/maintainedapps/testdata/notion.json new file mode 100644 index 000000000000..3dcb88dff861 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/notion.json @@ -0,0 +1,109 @@ +{ + "token": "notion", + "full_token": "notion", + "old_tokens": [], + "tap": "homebrew/cask", + "name": [ + "Notion" + ], + "desc": "App to write, plan, collaborate, and get organised", + "homepage": "https://www.notion.so/", + "url": "https://desktop-release.notion-static.com/Notion-3.14.0-arm64.dmg", + "url_specs": { + "verified": "desktop-release.notion-static.com/" + }, + "version": "3.14.0", + "installed": null, + "installed_time": null, + "bundle_version": null, + "bundle_short_version": null, + "outdated": false, + "sha256": "281b681315f22fdb7919dabb7da40bec606cf0553e8d3dc3b95b1e280e22c048", + "artifacts": [ + { + "app": [ + "Notion.app" + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Support/Caches/notion-updater", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/notion.id.sfl*", + "~/Library/Application Support/Notion", + "~/Library/Caches/notion.id*", + "~/Library/Logs/Notion", + "~/Library/Preferences/ByHost/notion.id.*", + "~/Library/Preferences/notion.id.*", + "~/Library/Saved Application State/notion.id.savedState", + "~/Library/WebKit/notion.id" + ] + } + ] + } + ], + "caveats": null, + "depends_on": { + "macos": { + ">=": [ + "10.15" + ] + } + }, + "conflicts_with": null, + "container": null, + "auto_updates": true, + "deprecated": false, + "deprecation_date": null, + "deprecation_reason": null, + "disabled": false, + "disable_date": null, + "disable_reason": null, + "tap_git_head": "5e76d542a4e6e78701fdfae86e263074eab2a3e7", + "languages": [], + "ruby_source_path": "Casks/n/notion.rb", + "ruby_source_checksum": { + "sha256": "d3819cf16d3d149e8c5da4d324ce3a61ba11cd5f31034c11f568fa9ec3b48bc1" + }, + "variations": { + "sequoia": { + "url": "https://desktop-release.notion-static.com/Notion-3.14.0.dmg", + "sha256": "5d7b762f522754b74c78bd0f0ebf292e6750224ee7bc4a3837156bdb5722ab11" + }, + "sonoma": { + "url": "https://desktop-release.notion-static.com/Notion-3.14.0.dmg", + "sha256": "5d7b762f522754b74c78bd0f0ebf292e6750224ee7bc4a3837156bdb5722ab11" + }, + "ventura": { + "url": "https://desktop-release.notion-static.com/Notion-3.14.0.dmg", + "sha256": "5d7b762f522754b74c78bd0f0ebf292e6750224ee7bc4a3837156bdb5722ab11" + }, + "monterey": { + "url": "https://desktop-release.notion-static.com/Notion-3.14.0.dmg", + "sha256": "5d7b762f522754b74c78bd0f0ebf292e6750224ee7bc4a3837156bdb5722ab11" + }, + "big_sur": { + "url": "https://desktop-release.notion-static.com/Notion-3.14.0.dmg", + "sha256": "5d7b762f522754b74c78bd0f0ebf292e6750224ee7bc4a3837156bdb5722ab11" + }, + "catalina": { + "url": "https://desktop-release.notion-static.com/Notion-3.14.0.dmg", + "sha256": "5d7b762f522754b74c78bd0f0ebf292e6750224ee7bc4a3837156bdb5722ab11" + } + }, + "analytics": { + "install": { + "30d": { + "notion": 4752 + }, + "90d": { + "notion": 13516 + }, + "365d": { + "notion": 52768 + } + } + }, + "generated_date": "2024-09-09" +} diff --git a/server/mdm/maintainedapps/testdata/slack.json b/server/mdm/maintainedapps/testdata/slack.json new file mode 100644 index 000000000000..eaca79517a9c --- /dev/null +++ b/server/mdm/maintainedapps/testdata/slack.json @@ -0,0 +1,126 @@ +{ + "token": "slack", + "full_token": "slack", + "old_tokens": [], + "tap": "homebrew/cask", + "name": [ + "Slack" + ], + "desc": "Team communication and collaboration software", + "homepage": "https://slack.com/", + "url": "https://downloads.slack-edge.com/desktop-releases/mac/arm64/4.40.126/Slack-4.40.126-macOS.dmg", + "url_specs": { + "verified": "downloads.slack-edge.com/" + }, + "version": "4.40.126", + "installed": null, + "installed_time": null, + "bundle_version": null, + "bundle_short_version": null, + "outdated": false, + "sha256": "87dcde01a382e7d4a23f72f0fdd64315b52390dd2d5800df2a645d4316b3bc52", + "artifacts": [ + { + "uninstall": [ + { + "quit": "com.tinyspeck.slackmacgap" + } + ] + }, + { + "app": [ + "Slack.app" + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Scripts/com.tinyspeck.slackmacgap", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.tinyspeck.slackmacgap.sfl*", + "~/Library/Application Support/Slack", + "~/Library/Caches/com.tinyspeck.slackmacgap*", + "~/Library/Containers/com.tinyspeck.slackmacgap*", + "~/Library/Cookies/com.tinyspeck.slackmacgap.binarycookies", + "~/Library/Group Containers/*.com.tinyspeck.slackmacgap", + "~/Library/Group Containers/*.slack", + "~/Library/HTTPStorages/com.tinyspeck.slackmacgap*", + "~/Library/Logs/Slack", + "~/Library/Preferences/ByHost/com.tinyspeck.slackmacgap.ShipIt.*.plist", + "~/Library/Preferences/com.tinyspeck.slackmacgap*", + "~/Library/Saved Application State/com.tinyspeck.slackmacgap.savedState", + "~/Library/WebKit/com.tinyspeck.slackmacgap" + ] + } + ] + } + ], + "caveats": null, + "depends_on": { + "macos": { + ">=": [ + "10.15" + ] + } + }, + "conflicts_with": { + "cask": [ + "slack@beta" + ] + }, + "container": null, + "auto_updates": true, + "deprecated": false, + "deprecation_date": null, + "deprecation_reason": null, + "disabled": false, + "disable_date": null, + "disable_reason": null, + "tap_git_head": "5e76d542a4e6e78701fdfae86e263074eab2a3e7", + "languages": [], + "ruby_source_path": "Casks/s/slack.rb", + "ruby_source_checksum": { + "sha256": "55b1b37ace62e6297b1861e2ab2b21b834c35112d8d422ccf0b9634843a9aa8d" + }, + "variations": { + "sequoia": { + "url": "https://downloads.slack-edge.com/desktop-releases/mac/x64/4.40.126/Slack-4.40.126-macOS.dmg", + "sha256": "f4246f416b84ce8a4a9abbfba6975f0fbdb90880c217544836b53c3fff5900ae" + }, + "sonoma": { + "url": "https://downloads.slack-edge.com/desktop-releases/mac/x64/4.40.126/Slack-4.40.126-macOS.dmg", + "sha256": "f4246f416b84ce8a4a9abbfba6975f0fbdb90880c217544836b53c3fff5900ae" + }, + "ventura": { + "url": "https://downloads.slack-edge.com/desktop-releases/mac/x64/4.40.126/Slack-4.40.126-macOS.dmg", + "sha256": "f4246f416b84ce8a4a9abbfba6975f0fbdb90880c217544836b53c3fff5900ae" + }, + "monterey": { + "url": "https://downloads.slack-edge.com/desktop-releases/mac/x64/4.40.126/Slack-4.40.126-macOS.dmg", + "sha256": "f4246f416b84ce8a4a9abbfba6975f0fbdb90880c217544836b53c3fff5900ae" + }, + "big_sur": { + "url": "https://downloads.slack-edge.com/desktop-releases/mac/x64/4.40.126/Slack-4.40.126-macOS.dmg", + "sha256": "f4246f416b84ce8a4a9abbfba6975f0fbdb90880c217544836b53c3fff5900ae" + }, + "catalina": { + "url": "https://downloads.slack-edge.com/releases/macos/4.33.90/prod/x64/Slack-4.33.90-macOS.dmg", + "version": "4.33.90", + "sha256": "7e0ba8a18a9cf95090ad80f58437d647eee5d1842ac4f15ea053c16c1629edde" + } + }, + "analytics": { + "install": { + "30d": { + "slack": 6334 + }, + "90d": { + "slack": 18458 + }, + "365d": { + "slack": 78914 + } + } + }, + "generated_date": "2024-09-09" +} diff --git a/server/mdm/maintainedapps/testdata/teamviewer.json b/server/mdm/maintainedapps/testdata/teamviewer.json new file mode 100644 index 000000000000..a8e6a12dc99d --- /dev/null +++ b/server/mdm/maintainedapps/testdata/teamviewer.json @@ -0,0 +1,201 @@ +{ + "token": "teamviewer", + "full_token": "teamviewer", + "old_tokens": [], + "tap": "homebrew/cask", + "name": [ + "TeamViewer" + ], + "desc": "Remote access and connectivity software focused on security", + "homepage": "https://www.teamviewer.com/", + "url": "https://dl.teamviewer.com/download/version_15x/update/15.57.5/TeamViewer.pkg", + "url_specs": {}, + "version": "15.57.5", + "installed": null, + "installed_time": null, + "bundle_version": null, + "bundle_short_version": null, + "outdated": false, + "sha256": "5f53e81921bf7b15e8605ad3cfa39c4769303bbb06d57dc50a683be0d5889e1f", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": [ + "com.teamviewer.desktop", + "com.teamviewer.Helper", + "com.teamviewer.service", + "com.teamviewer.teamviewer", + "com.teamviewer.teamviewer_desktop", + "com.teamviewer.teamviewer_service", + "com.teamviewer.UninstallerHelper", + "com.teamviewer.UninstallerWatcher" + ], + "quit": [ + "com.teamviewer.TeamViewer", + "com.teamviewer.TeamViewerUninstaller" + ], + "pkgutil": [ + "com.teamviewer.AuthorizationPlugin", + "com.teamviewer.remoteaudiodriver", + "com.teamviewer.teamviewer.*", + "TeamViewerUninstaller" + ], + "delete": [ + "/Applications/TeamViewer.app", + "/Library/Preferences/com.teamviewer*" + ] + } + ] + }, + { + "pkg": [ + "TeamViewer.pkg" + ] + }, + { + "postflight": null + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Support/TeamViewer", + "~/Library/Caches/com.teamviewer.TeamViewer", + "~/Library/Caches/TeamViewer", + "~/Library/Cookies/com.teamviewer.TeamViewer.binarycookies", + "~/Library/Logs/TeamViewer", + "~/Library/Preferences/com.teamviewer*", + "~/Library/Saved Application State/com.teamviewer.TeamViewer.savedState" + ] + } + ] + } + ], + "caveats": null, + "depends_on": { + "macos": { + ">=": [ + "10.11" + ] + } + }, + "conflicts_with": { + "cask": [ + "teamviewer-host" + ] + }, + "container": null, + "auto_updates": true, + "deprecated": false, + "deprecation_date": null, + "deprecation_reason": null, + "disabled": false, + "disable_date": null, + "disable_reason": null, + "tap_git_head": "5e76d542a4e6e78701fdfae86e263074eab2a3e7", + "languages": [], + "ruby_source_path": "Casks/t/teamviewer.rb", + "ruby_source_checksum": { + "sha256": "86b4666b24b1645a599ce70f9a6841a4ec81ea5792655b08d362f9f474beebc8" + }, + "variations": { + "catalina": { + "url": "https://dl.teamviewer.com/download/version_15x/update/15.42.4/TeamViewer.pkg", + "version": "15.42.4", + "sha256": "3357bc366cd0295dd100b790d6af6216d349d34451ea18ba08692a51eadd6cf7", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": [ + "com.teamviewer.desktop", + "com.teamviewer.Helper", + "com.teamviewer.service", + "com.teamviewer.teamviewer", + "com.teamviewer.teamviewer_desktop", + "com.teamviewer.teamviewer_service", + "com.teamviewer.UninstallerHelper", + "com.teamviewer.UninstallerWatcher" + ], + "quit": [ + "com.teamviewer.TeamViewer", + "com.teamviewer.TeamViewerUninstaller" + ], + "pkgutil": [ + "com.teamviewer.AuthorizationPlugin", + "com.teamviewer.remoteaudiodriver", + "com.teamviewer.teamviewer.*", + "TeamViewerUninstaller" + ], + "delete": [ + "/Applications/TeamViewer.app", + "/Library/Preferences/com.teamviewer*" + ] + } + ] + }, + { + "installer": [ + { + "manual": "TeamViewer.pkg" + } + ] + }, + { + "postflight": null + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Support/TeamViewer", + "~/Library/Caches/com.teamviewer.TeamViewer", + "~/Library/Caches/TeamViewer", + "~/Library/Cookies/com.teamviewer.TeamViewer.binarycookies", + "~/Library/Logs/TeamViewer", + "~/Library/Preferences/com.teamviewer*", + "~/Library/Saved Application State/com.teamviewer.TeamViewer.savedState" + ] + } + ] + } + ], + "caveats": "WARNING: teamviewer has a bug in Catalina where it doesn't deal well with being uninstalled by other utilities.\nThe recommended way to remove it is by running their uninstaller under:\n\n Preferences → Advanced\n" + }, + "mojave": { + "url": "https://dl.teamviewer.com/download/version_15x/update/15.42.4/TeamViewer.pkg", + "version": "15.42.4", + "sha256": "3357bc366cd0295dd100b790d6af6216d349d34451ea18ba08692a51eadd6cf7" + }, + "high_sierra": { + "url": "https://dl.teamviewer.com/download/version_15x/update/15.2.2756/TeamViewer.pkg", + "version": "15.2.2756", + "sha256": "fe7daf80f9aee056f97d11183941470fa1c5823101a0951990340b6264a2651a" + }, + "sierra": { + "url": "https://dl.teamviewer.com/download/version_15x/update/15.2.2756/TeamViewer.pkg", + "version": "15.2.2756", + "sha256": "fe7daf80f9aee056f97d11183941470fa1c5823101a0951990340b6264a2651a" + }, + "el_capitan": { + "url": "https://dl.teamviewer.com/download/version_15x/update/15.2.2756/TeamViewer.pkg", + "version": "15.2.2756", + "sha256": "fe7daf80f9aee056f97d11183941470fa1c5823101a0951990340b6264a2651a" + } + }, + "analytics": { + "install": { + "30d": { + "teamviewer": 1271 + }, + "90d": { + "teamviewer": 3644 + }, + "365d": { + "teamviewer": 18205 + } + } + }, + "generated_date": "2024-09-09" +} diff --git a/server/mdm/maintainedapps/testdata/visual-studio-code.json b/server/mdm/maintainedapps/testdata/visual-studio-code.json new file mode 100644 index 000000000000..398e6abb04d3 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/visual-studio-code.json @@ -0,0 +1,122 @@ +{ + "token": "visual-studio-code", + "full_token": "visual-studio-code", + "old_tokens": [], + "tap": "homebrew/cask", + "name": [ + "Microsoft Visual Studio Code", + "VS Code" + ], + "desc": "Open-source code editor", + "homepage": "https://code.visualstudio.com/", + "url": "https://update.code.visualstudio.com/1.93.0/darwin-arm64/stable", + "url_specs": {}, + "version": "1.93.0", + "installed": null, + "installed_time": null, + "bundle_version": null, + "bundle_short_version": null, + "outdated": false, + "sha256": "79a69b0e02f8dc79106276600b437ff3f831925d1f4d6665cf972ef45dc158f6", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": "com.microsoft.VSCode.ShipIt", + "quit": "com.microsoft.VSCode" + } + ] + }, + { + "app": [ + "Visual Studio Code.app" + ] + }, + { + "binary": [ + "$APPDIR/Visual Studio Code.app/Contents/Resources/app/bin/code" + ] + }, + { + "zap": [ + { + "trash": [ + "~/.vscode", + "~/Library/Application Support/Code", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.microsoft.vscode.sfl*", + "~/Library/Caches/com.microsoft.VSCode", + "~/Library/Caches/com.microsoft.VSCode.ShipIt", + "~/Library/HTTPStorages/com.microsoft.VSCode", + "~/Library/Preferences/ByHost/com.microsoft.VSCode.ShipIt.*.plist", + "~/Library/Preferences/com.microsoft.VSCode.helper.plist", + "~/Library/Preferences/com.microsoft.VSCode.plist", + "~/Library/Saved Application State/com.microsoft.VSCode.savedState" + ] + } + ] + } + ], + "caveats": null, + "depends_on": { + "macos": { + ">=": [ + "10.15" + ] + } + }, + "conflicts_with": null, + "container": null, + "auto_updates": true, + "deprecated": false, + "deprecation_date": null, + "deprecation_reason": null, + "disabled": false, + "disable_date": null, + "disable_reason": null, + "tap_git_head": "5e76d542a4e6e78701fdfae86e263074eab2a3e7", + "languages": [], + "ruby_source_path": "Casks/v/visual-studio-code.rb", + "ruby_source_checksum": { + "sha256": "fead740931be479fa70f4e3a8f707d096891a6c9c3c4bd1a2213b10b6fb7d684" + }, + "variations": { + "sequoia": { + "url": "https://update.code.visualstudio.com/1.93.0/darwin/stable", + "sha256": "321f2a51afe42498cd141124dda8f43be2d46821e035b0286a3d0605a519bea3" + }, + "sonoma": { + "url": "https://update.code.visualstudio.com/1.93.0/darwin/stable", + "sha256": "321f2a51afe42498cd141124dda8f43be2d46821e035b0286a3d0605a519bea3" + }, + "ventura": { + "url": "https://update.code.visualstudio.com/1.93.0/darwin/stable", + "sha256": "321f2a51afe42498cd141124dda8f43be2d46821e035b0286a3d0605a519bea3" + }, + "monterey": { + "url": "https://update.code.visualstudio.com/1.93.0/darwin/stable", + "sha256": "321f2a51afe42498cd141124dda8f43be2d46821e035b0286a3d0605a519bea3" + }, + "big_sur": { + "url": "https://update.code.visualstudio.com/1.93.0/darwin/stable", + "sha256": "321f2a51afe42498cd141124dda8f43be2d46821e035b0286a3d0605a519bea3" + }, + "catalina": { + "url": "https://update.code.visualstudio.com/1.93.0/darwin/stable", + "sha256": "321f2a51afe42498cd141124dda8f43be2d46821e035b0286a3d0605a519bea3" + } + }, + "analytics": { + "install": { + "30d": { + "visual-studio-code": 26798 + }, + "90d": { + "visual-studio-code": 79608 + }, + "365d": { + "visual-studio-code": 321611 + } + } + }, + "generated_date": "2024-09-09" +} diff --git a/server/mdm/maintainedapps/testdata/whatsapp.json b/server/mdm/maintainedapps/testdata/whatsapp.json new file mode 100644 index 000000000000..97cbecbb810a --- /dev/null +++ b/server/mdm/maintainedapps/testdata/whatsapp.json @@ -0,0 +1,84 @@ +{ + "token": "whatsapp", + "full_token": "whatsapp", + "old_tokens": [], + "tap": "homebrew/cask", + "name": [ + "WhatsApp" + ], + "desc": "Native desktop client for WhatsApp", + "homepage": "https://www.whatsapp.com/", + "url": "https://web.whatsapp.com/desktop/mac_native/release/?version=2.24.16.80&extension=zip&configuration=Release&branch=relbranch", + "url_specs": {}, + "version": "2.24.16.80", + "installed": null, + "installed_time": null, + "bundle_version": null, + "bundle_short_version": null, + "outdated": false, + "sha256": "33026f9cc9b1c63010df546c1ac5ff3b49ff6e742a57807a817264db82487ce9", + "artifacts": [ + { + "app": [ + "WhatsApp.app" + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Scripts/net.whatsapp.WhatsApp*", + "~/Library/Caches/net.whatsapp.WhatsApp", + "~/Library/Containers/net.whatsapp.WhatsApp*", + "~/Library/Group Containers/group.com.facebook.family", + "~/Library/Group Containers/group.net.whatsapp*", + "~/Library/Saved Application State/net.whatsapp.WhatsApp.savedState" + ] + } + ] + } + ], + "caveats": null, + "depends_on": { + "macos": { + ">=": [ + "11" + ] + } + }, + "conflicts_with": { + "cask": [ + "whatsapp@beta", + "whatsapp@legacy" + ] + }, + "container": null, + "auto_updates": true, + "deprecated": false, + "deprecation_date": null, + "deprecation_reason": null, + "disabled": false, + "disable_date": null, + "disable_reason": null, + "tap_git_head": "5e76d542a4e6e78701fdfae86e263074eab2a3e7", + "languages": [], + "ruby_source_path": "Casks/w/whatsapp.rb", + "ruby_source_checksum": { + "sha256": "05889e4ba8c0e0fe97e653d1e4122385a6f67a2ce2ec6a1f7b19c702142c617f" + }, + "variations": {}, + "analytics": { + "install": { + "30d": { + "whatsapp": 3652 + }, + "90d": { + "whatsapp": 13126 + }, + "365d": { + "whatsapp": 47436 + } + } + }, + "generated_date": "2024-09-09" +} diff --git a/server/mdm/maintainedapps/testdata/zoom.json b/server/mdm/maintainedapps/testdata/zoom.json new file mode 100644 index 000000000000..40e4640a7500 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/zoom.json @@ -0,0 +1,161 @@ +{ + "token": "zoom", + "full_token": "zoom", + "old_tokens": [], + "tap": "homebrew/cask", + "name": [ + "Zoom" + ], + "desc": "Video communication and virtual meeting platform", + "homepage": "https://www.zoom.us/", + "url": "https://cdn.zoom.us/prod/6.1.11.39163/arm64/zoomusInstallerFull.pkg", + "url_specs": {}, + "version": "6.1.11.39163", + "installed": null, + "installed_time": null, + "bundle_version": null, + "bundle_short_version": null, + "outdated": false, + "sha256": "c567828838583df4c026db571b96c2c212eef9b98795c145fecdfe10009ea61e", + "artifacts": [ + { + "uninstall": [ + { + "launchctl": "us.zoom.ZoomDaemon", + "signal": [ + "KILL", + "us.zoom.xos" + ], + "pkgutil": "us.zoom.pkg.videomeeting", + "delete": [ + "/Applications/zoom.us.app", + "/Library/Internet Plug-Ins/ZoomUsPlugIn.plugin", + "/Library/Logs/DiagnosticReports/zoom.us*", + "/Library/PrivilegedHelperTools/us.zoom.ZoomDaemon" + ] + } + ] + }, + { + "pkg": [ + "zoomusInstallerFull.pkg" + ] + }, + { + "postflight": null + }, + { + "zap": [ + { + "trash": [ + "~/.zoomus", + "~/Desktop/Zoom", + "~/Documents/Zoom", + "~/Library/Application Scripts/*.ZoomClient3rd", + "~/Library/Application Support/CloudDocs/session/containers/iCloud.us.zoom.videomeetings", + "~/Library/Application Support/CloudDocs/session/containers/iCloud.us.zoom.videomeetings.plist", + "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/us.zoom*.sfl*", + "~/Library/Application Support/CrashReporter/zoom.us*", + "~/Library/Application Support/zoom.us", + "~/Library/Caches/us.zoom.xos", + "~/Library/Cookies/us.zoom.xos.binarycookies", + "~/Library/Group Containers/*.ZoomClient3rd", + "~/Library/HTTPStorages/us.zoom.xos", + "~/Library/HTTPStorages/us.zoom.xos.binarycookies", + "~/Library/Internet Plug-Ins/ZoomUsPlugIn.plugin", + "~/Library/Logs/zoom.us", + "~/Library/Logs/zoominstall.log", + "~/Library/Logs/ZoomPhone", + "~/Library/Preferences/us.zoom.airhost.plist", + "~/Library/Preferences/us.zoom.caphost.plist", + "~/Library/Preferences/us.zoom.Transcode.plist", + "~/Library/Preferences/us.zoom.xos.Hotkey.plist", + "~/Library/Preferences/us.zoom.xos.plist", + "~/Library/Preferences/us.zoom.ZoomAutoUpdater.plist", + "~/Library/Preferences/us.zoom.ZoomClips.plist", + "~/Library/Preferences/ZoomChat.plist", + "~/Library/Saved Application State/us.zoom.xos.savedState", + "~/Library/WebKit/us.zoom.xos" + ] + } + ] + } + ], + "caveats": null, + "depends_on": {}, + "conflicts_with": { + "cask": [ + "zoom-for-it-admins" + ] + }, + "container": null, + "auto_updates": true, + "deprecated": false, + "deprecation_date": null, + "deprecation_reason": null, + "disabled": false, + "disable_date": null, + "disable_reason": null, + "tap_git_head": "5e76d542a4e6e78701fdfae86e263074eab2a3e7", + "languages": [], + "ruby_source_path": "Casks/z/zoom.rb", + "ruby_source_checksum": { + "sha256": "7a76c4fbb2eaf6f2ef87a5756a5b7147cdfd4cee1f8cef275e007e924003fbda" + }, + "variations": { + "sequoia": { + "url": "https://cdn.zoom.us/prod/6.1.11.39163/zoomusInstallerFull.pkg", + "sha256": "001f905f1eabdeb6b299553bac427d74ee02a8923db67445de5eb5bee1a7aa92" + }, + "sonoma": { + "url": "https://cdn.zoom.us/prod/6.1.11.39163/zoomusInstallerFull.pkg", + "sha256": "001f905f1eabdeb6b299553bac427d74ee02a8923db67445de5eb5bee1a7aa92" + }, + "ventura": { + "url": "https://cdn.zoom.us/prod/6.1.11.39163/zoomusInstallerFull.pkg", + "sha256": "001f905f1eabdeb6b299553bac427d74ee02a8923db67445de5eb5bee1a7aa92" + }, + "monterey": { + "url": "https://cdn.zoom.us/prod/6.1.11.39163/zoomusInstallerFull.pkg", + "sha256": "001f905f1eabdeb6b299553bac427d74ee02a8923db67445de5eb5bee1a7aa92" + }, + "big_sur": { + "url": "https://cdn.zoom.us/prod/6.1.11.39163/zoomusInstallerFull.pkg", + "sha256": "001f905f1eabdeb6b299553bac427d74ee02a8923db67445de5eb5bee1a7aa92" + }, + "catalina": { + "url": "https://cdn.zoom.us/prod/6.1.11.39163/zoomusInstallerFull.pkg", + "sha256": "001f905f1eabdeb6b299553bac427d74ee02a8923db67445de5eb5bee1a7aa92" + }, + "mojave": { + "url": "https://cdn.zoom.us/prod/6.1.11.39163/zoomusInstallerFull.pkg", + "sha256": "001f905f1eabdeb6b299553bac427d74ee02a8923db67445de5eb5bee1a7aa92" + }, + "high_sierra": { + "url": "https://cdn.zoom.us/prod/6.1.11.39163/zoomusInstallerFull.pkg", + "sha256": "001f905f1eabdeb6b299553bac427d74ee02a8923db67445de5eb5bee1a7aa92" + }, + "sierra": { + "url": "https://cdn.zoom.us/prod/6.1.11.39163/zoomusInstallerFull.pkg", + "sha256": "001f905f1eabdeb6b299553bac427d74ee02a8923db67445de5eb5bee1a7aa92" + }, + "el_capitan": { + "url": "https://cdn.zoom.us/prod/6.1.11.39163/zoomusInstallerFull.pkg", + "sha256": "001f905f1eabdeb6b299553bac427d74ee02a8923db67445de5eb5bee1a7aa92" + } + }, + "analytics": { + "install": { + "30d": { + "zoom": 5878 + }, + "90d": { + "zoom": 17578 + }, + "365d": { + "zoom": 75807 + } + } + }, + "generated_date": "2024-09-09" +} diff --git a/server/mdm/maintainedapps/testing_utils.go b/server/mdm/maintainedapps/testing_utils.go new file mode 100644 index 000000000000..9c08af04b1a7 --- /dev/null +++ b/server/mdm/maintainedapps/testing_utils.go @@ -0,0 +1,74 @@ +package maintainedapps + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "os" + "path" + "path/filepath" + "runtime" + "testing" + + "github.com/fleetdm/fleet/v4/server/fleet" + "github.com/go-kit/log" + "github.com/stretchr/testify/require" +) + +// IngestMaintainedApps ingests the maintained apps from the testdata +// directory, to fill the library of maintained apps with valid data for tests. +// It returns the expected results of the ingestion as a slice of +// fleet.MaintainedApps with only a few fields filled - the result of +// unmarshaling the testdata/expected_apps.json file. +func IngestMaintainedApps(t *testing.T, ds fleet.Datastore) []*fleet.MaintainedApp { + _, filename, _, _ := runtime.Caller(0) + base := filepath.Dir(filename) + testdataDir := filepath.Join(base, "testdata") + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + token := path.Base(r.URL.Path) + b, err := os.ReadFile(filepath.Join(testdataDir, token)) + if err != nil { + if os.IsNotExist(err) { + w.WriteHeader(http.StatusNotFound) + return + } + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte(err.Error())) + return + } + _, _ = w.Write(b) + })) + defer srv.Close() + + // not using t.Setenv because we want the env var to be unset on return of + // this call + os.Setenv("FLEET_DEV_BREW_API_URL", srv.URL) + defer os.Unsetenv("FLEET_DEV_BREW_API_URL") + + err := Refresh(context.Background(), ds, log.NewNopLogger()) + require.NoError(t, err) + + var expected []*fleet.MaintainedApp + b, err := os.ReadFile(filepath.Join(testdataDir, "expected_apps.json")) + require.NoError(t, err) + err = json.Unmarshal(b, &expected) + require.NoError(t, err) + return expected +} + +// ExpectedAppTokens returns the list of app tokens (unique identifier) that are +// expected to be in the maintained apps library after ingestion. The tokens are +// taken from the apps.json list. +func ExpectedAppTokens(t *testing.T) []string { + var apps []maintainedApp + err := json.Unmarshal(appsJSON, &apps) + require.NoError(t, err) + + tokens := make([]string, len(apps)) + for i, app := range apps { + tokens[i] = app.Identifier + } + return tokens +} diff --git a/server/mock/datastore_mock.go b/server/mock/datastore_mock.go index 99d55d12158c..15e03deecf45 100644 --- a/server/mock/datastore_mock.go +++ b/server/mock/datastore_mock.go @@ -1068,6 +1068,8 @@ type GetPastActivityDataForVPPAppInstallFunc func(ctx context.Context, commandRe type GetVPPTokenByLocationFunc func(ctx context.Context, loc string) (*fleet.VPPTokenDB, error) +type UpsertMaintainedAppFunc func(ctx context.Context, app *fleet.MaintainedApp) error + type DataStore struct { HealthCheckFunc HealthCheckFunc HealthCheckFuncInvoked bool @@ -2641,6 +2643,9 @@ type DataStore struct { GetVPPTokenByLocationFunc GetVPPTokenByLocationFunc GetVPPTokenByLocationFuncInvoked bool + UpsertMaintainedAppFunc UpsertMaintainedAppFunc + UpsertMaintainedAppFuncInvoked bool + mu sync.Mutex } @@ -6311,3 +6316,10 @@ func (s *DataStore) GetVPPTokenByLocation(ctx context.Context, loc string) (*fle s.mu.Unlock() return s.GetVPPTokenByLocationFunc(ctx, loc) } + +func (s *DataStore) UpsertMaintainedApp(ctx context.Context, app *fleet.MaintainedApp) error { + s.mu.Lock() + s.UpsertMaintainedAppFuncInvoked = true + s.mu.Unlock() + return s.UpsertMaintainedAppFunc(ctx, app) +} From b30e765554e0b5d11840013a81ef902131fb0ed4 Mon Sep 17 00:00:00 2001 From: Martin Angers Date: Tue, 10 Sep 2024 16:27:18 -0400 Subject: [PATCH 003/385] Maintained Apps: add cron job, fix ingestion following latest specs (#21959) --- cmd/fleet/cron.go | 27 +++++++++++++++++++ cmd/fleet/serve.go | 6 +++++ server/datastore/mysql/maintained_apps.go | 11 ++++---- ...20240909145426_AddFleetLibraryAppsTable.go | 1 - server/datastore/mysql/schema.sql | 1 - server/fleet/cron_schedules.go | 1 + server/fleet/maintained_apps.go | 1 - server/mdm/maintainedapps/apps.json | 4 +-- server/mdm/maintainedapps/ingest.go | 10 +++---- server/service/schedule/schedule.go | 21 +++++++++++++-- 10 files changed, 65 insertions(+), 18 deletions(-) diff --git a/cmd/fleet/cron.go b/cmd/fleet/cron.go index 96e7f7998f6f..a0db9f5d0d0f 100644 --- a/cmd/fleet/cron.go +++ b/cmd/fleet/cron.go @@ -21,6 +21,7 @@ import ( "github.com/fleetdm/fleet/v4/server/mdm" apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple" "github.com/fleetdm/fleet/v4/server/mdm/assets" + "github.com/fleetdm/fleet/v4/server/mdm/maintainedapps" "github.com/fleetdm/fleet/v4/server/mdm/nanodep/godep" "github.com/fleetdm/fleet/v4/server/policies" "github.com/fleetdm/fleet/v4/server/ptr" @@ -1394,3 +1395,29 @@ func newIPhoneIPadRefetcher( return s, nil } + +func newMaintainedAppSchedule( + ctx context.Context, + instanceID string, + ds fleet.Datastore, + logger kitlog.Logger, +) (*schedule.Schedule, error) { + const ( + name = string(fleet.CronMaintainedApps) + defaultInterval = 24 * time.Hour + priorJobDiff = -(defaultInterval - 30*time.Second) + ) + + logger = kitlog.With(logger, "cron", name) + s := schedule.New( + ctx, name, instanceID, defaultInterval, ds, ds, + schedule.WithLogger(logger), + // ensures it runs a few seconds after Fleet is started + schedule.WithDefaultPrevRunCreatedAt(time.Now().Add(priorJobDiff)), + schedule.WithJob("refresh_maintained_apps", func(ctx context.Context) error { + return maintainedapps.Refresh(ctx, ds, logger) + }), + ) + + return s, nil +} diff --git a/cmd/fleet/serve.go b/cmd/fleet/serve.go index c8773ba765cd..dd1094369688 100644 --- a/cmd/fleet/serve.go +++ b/cmd/fleet/serve.go @@ -899,6 +899,12 @@ the way that the Fleet server works. }); err != nil { initFatal(err, "failed to register apple_mdm_iphone_ipad_refetcher schedule") } + + if err := cronSchedules.StartCronSchedule(func() (fleet.CronSchedule, error) { + return newMaintainedAppSchedule(ctx, instanceID, ds, logger) + }); err != nil { + initFatal(err, "failed to register maintained apps schedule") + } } if license.IsPremium() && config.Activity.EnableAuditLog { diff --git a/server/datastore/mysql/maintained_apps.go b/server/datastore/mysql/maintained_apps.go index 5080872d9a77..df38f1bcd6f8 100644 --- a/server/datastore/mysql/maintained_apps.go +++ b/server/datastore/mysql/maintained_apps.go @@ -10,20 +10,19 @@ import ( func (ds *Datastore) UpsertMaintainedApp(ctx context.Context, app *fleet.MaintainedApp) error { const upsertStmt = ` -INSERT INTO +INSERT INTO fleet_library_apps ( - name, token, version, platform, installer_url, filename, + name, token, version, platform, installer_url, sha256, bundle_identifier, install_script_content_id, uninstall_script_content_id ) -VALUES - ( ?, ?, ?, ?, ?, ?, +VALUES + ( ?, ?, ?, ?, ?, ?, ?, ?, ? ) ON DUPLICATE KEY UPDATE name = VALUES(name), version = VALUES(version), platform = VALUES(platform), installer_url = VALUES(installer_url), - filename = VALUES(filename), sha256 = VALUES(sha256), bundle_identifier = VALUES(bundle_identifier), install_script_content_id = VALUES(install_script_content_id), @@ -48,7 +47,7 @@ ON DUPLICATE KEY UPDATE uninstallScriptID, _ := uninstallRes.LastInsertId() // upsert the maintained app - _, err = tx.ExecContext(ctx, upsertStmt, app.Name, app.Token, app.Version, app.Platform, app.InstallerURL, app.Filename, + _, err = tx.ExecContext(ctx, upsertStmt, app.Name, app.Token, app.Version, app.Platform, app.InstallerURL, app.SHA256, app.BundleIdentifier, installScriptID, uninstallScriptID) return ctxerr.Wrap(ctx, err, "upsert maintained app") }) diff --git a/server/datastore/mysql/migrations/tables/20240909145426_AddFleetLibraryAppsTable.go b/server/datastore/mysql/migrations/tables/20240909145426_AddFleetLibraryAppsTable.go index 8fdaed81a997..a2565fa889d7 100644 --- a/server/datastore/mysql/migrations/tables/20240909145426_AddFleetLibraryAppsTable.go +++ b/server/datastore/mysql/migrations/tables/20240909145426_AddFleetLibraryAppsTable.go @@ -20,7 +20,6 @@ CREATE TABLE fleet_library_apps ( version varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, platform varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, installer_url varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - filename varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, -- hash of the binary downloaded from installer_url, allows us to validate we got the right bytes -- before sending to S3 (and we store installers on S3 under that sha256 hash as identifier). sha256 varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql index dd5e0c12e3e1..787db0d22b8a 100644 --- a/server/datastore/mysql/schema.sql +++ b/server/datastore/mysql/schema.sql @@ -209,7 +209,6 @@ CREATE TABLE `fleet_library_apps` ( `version` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, `platform` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, `installer_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - `filename` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, `sha256` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, `bundle_identifier` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, `created_at` timestamp(6) NULL DEFAULT CURRENT_TIMESTAMP(6), diff --git a/server/fleet/cron_schedules.go b/server/fleet/cron_schedules.go index 250a8dc3b5eb..1a0f8843cc7a 100644 --- a/server/fleet/cron_schedules.go +++ b/server/fleet/cron_schedules.go @@ -24,6 +24,7 @@ const ( CronAppleMDMIPhoneIPadRefetcher CronScheduleName = "apple_mdm_iphone_ipad_refetcher" CronAppleMDMAPNsPusher CronScheduleName = "apple_mdm_apns_pusher" CronCalendar CronScheduleName = "calendar" + CronMaintainedApps CronScheduleName = "maintained_apps" ) type CronSchedulesService interface { diff --git a/server/fleet/maintained_apps.go b/server/fleet/maintained_apps.go index e78c2a810031..5a34f8e4f2a8 100644 --- a/server/fleet/maintained_apps.go +++ b/server/fleet/maintained_apps.go @@ -9,7 +9,6 @@ type MaintainedApp struct { Version string `json:"version" db:"version"` Platform AppleDevicePlatform `json:"platform" db:"platform"` InstallerURL string `json:"-" db:"installer_url"` - Filename string `json:"filename" db:"filename"` SHA256 string `json:"-" db:"sha256"` BundleIdentifier string `json:"-" db:"bundle_identifier"` diff --git a/server/mdm/maintainedapps/apps.json b/server/mdm/maintainedapps/apps.json index 86aa33ba5cdf..159d18b05ca7 100644 --- a/server/mdm/maintainedapps/apps.json +++ b/server/mdm/maintainedapps/apps.json @@ -11,7 +11,7 @@ }, { "identifier": "box-drive", - "bundle_identifier": "???", + "bundle_identifier": "com.box.desktop", "installer_format": "pkg" }, { @@ -76,7 +76,7 @@ }, { "identifier": "teamviewer", - "bundle_identifier": "???", + "bundle_identifier": "com.teamviewer.TeamViewer", "installer_format": "pkg" }, { diff --git a/server/mdm/maintainedapps/ingest.go b/server/mdm/maintainedapps/ingest.go index 96fa8cc415a7..f829f33091da 100644 --- a/server/mdm/maintainedapps/ingest.go +++ b/server/mdm/maintainedapps/ingest.go @@ -9,7 +9,6 @@ import ( "net/http" "net/url" "os" - "path" "strings" "time" @@ -18,6 +17,7 @@ import ( "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" "github.com/fleetdm/fleet/v4/server/fleet" kitlog "github.com/go-kit/log" + "github.com/go-kit/log/level" "golang.org/x/sync/errgroup" ) @@ -103,7 +103,9 @@ func (i ingester) ingestOne(ctx context.Context, app maintainedApp, client *http case http.StatusOK: // success, go on case http.StatusNotFound: - // TODO: delete the existing entry? do nothing and succeed? doing the latter for now. + // nothing to do, either it currently exists in the DB and it keeps on + // existing, or it doesn't and keeps on being missing. + level.Warn(i.logger).Log("msg", "maintained app missing in brew API", "identifier", app.Identifier) return nil default: if len(body) > 512 { @@ -130,11 +132,10 @@ func (i ingester) ingestOne(ctx context.Context, app maintainedApp, client *http if cask.URL == "" { return ctxerr.Errorf(ctx, "missing URL for cask %s", app.Identifier) } - parsedURL, err := url.Parse(cask.URL) + _, err = url.Parse(cask.URL) if err != nil { return ctxerr.Wrapf(ctx, err, "parse URL for cask %s", app.Identifier) } - filename := path.Base(parsedURL.Path) installScript := installScriptForApp(app, &cask) uninstallScript := uninstallScriptForApp(app, &cask) @@ -146,7 +147,6 @@ func (i ingester) ingestOne(ctx context.Context, app maintainedApp, client *http // for now, maintained apps are always macOS (darwin) Platform: fleet.MacOSPlatform, InstallerURL: cask.URL, - Filename: filename, SHA256: cask.SHA256, BundleIdentifier: app.BundleIdentifier, InstallScript: installScript, diff --git a/server/service/schedule/schedule.go b/server/service/schedule/schedule.go index 7ca865416ab6..0a322143cc45 100644 --- a/server/service/schedule/schedule.go +++ b/server/service/schedule/schedule.go @@ -30,6 +30,8 @@ type Schedule struct { instanceID string logger log.Logger + defaultPrevRunCreatedAt time.Time // default timestamp of previous run for the schedule if none exists, time.Now if not set + mu sync.Mutex // protects schedInterval and intervalStartedAt schedInterval time.Duration intervalStartedAt time.Time // start time of the most recent run of the scheduled jobs @@ -120,6 +122,17 @@ func WithJob(id string, fn JobFn) Option { } } +// WithDefaultPrevRunCreatedAt sets the default time to use for the previous +// run of the schedule if it never ran yet. If not specified, the current time +// is used. This affects when the schedule starts running after Fleet is +// started, e.g. if the schedule has an interval of 1h and has no previous run +// recorded, by default its first run after Fleet starts will be in 1h. +func WithDefaultPrevRunCreatedAt(tm time.Time) Option { + return func(s *Schedule) { + s.defaultPrevRunCreatedAt = tm + } +} + // New creates and returns a Schedule. // Jobs are added with the WithJob Option. // @@ -168,10 +181,14 @@ func (s *Schedule) Start() { ctxerr.Handle(s.ctx, err) } - // if there is no previous run, set the start time to the current time. + // if there is no previous run, set the start time to the specified default + // time, falling back to current time. startedAt := prevScheduledRun.CreatedAt if startedAt.IsZero() { - startedAt = time.Now() + startedAt = s.defaultPrevRunCreatedAt + if startedAt.IsZero() { + startedAt = time.Now() + } } s.setIntervalStartedAt(startedAt) From 7b3cbdf4d797fbe03757bfb50c6078dba3ac144e Mon Sep 17 00:00:00 2001 From: Jahziel Villasana-Espinoza Date: Tue, 10 Sep 2024 18:21:53 -0400 Subject: [PATCH 004/385] fix: db field name (#21968) > No issue, just a small fix # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Manual QA for all new/changed functionality --- server/fleet/maintained_apps.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/fleet/maintained_apps.go b/server/fleet/maintained_apps.go index 5a34f8e4f2a8..3be14f2d917d 100644 --- a/server/fleet/maintained_apps.go +++ b/server/fleet/maintained_apps.go @@ -5,7 +5,7 @@ package fleet type MaintainedApp struct { ID uint `json:"id" db:"id"` Name string `json:"name" db:"name"` - Token string `json:"-" db:"name"` + Token string `json:"-" db:"token"` Version string `json:"version" db:"version"` Platform AppleDevicePlatform `json:"platform" db:"platform"` InstallerURL string `json:"-" db:"installer_url"` From 2b5631f89160ea87b27bf7aec6fae884785ffe82 Mon Sep 17 00:00:00 2001 From: Gabriel Hernandez Date: Wed, 11 Sep 2024 10:47:35 +0100 Subject: [PATCH 005/385] change add software modal to seperate pages in Fleet UI (#21881) --- .../218090-add-sofware-from-modal-to-pages | 1 + .../SoftwareAddPage/SoftwareAddPage.tsx | 108 ++++++++++++++++++ .../SoftwareAppStore/SoftwareAppStore.tsx | 23 ++++ .../SoftwareAppStore/_styles.scss | 3 + .../SoftwareAddPage/SoftwareAppStore/index.ts | 1 + .../SoftwareFleetMaintained.tsx | 39 +++++++ .../SoftwareFleetMaintained/_styles.scss | 3 + .../SoftwareFleetMaintained/index.ts | 1 + .../SoftwarePackage/SoftwarePackage.tsx | 23 ++++ .../SoftwarePackage/_styles.scss | 3 + .../SoftwareAddPage/SoftwarePackage/index.ts | 1 + .../SoftwarePage/SoftwareAddPage/_styles.scss | 10 ++ .../SoftwarePage/SoftwareAddPage/index.ts | 1 + frontend/pages/SoftwarePage/SoftwarePage.tsx | 23 ++-- .../AddSoftwareModal/AddSoftwareModal.tsx | 57 +-------- frontend/router/index.tsx | 15 +++ frontend/router/paths.ts | 3 + 17 files changed, 251 insertions(+), 64 deletions(-) create mode 100644 changes/218090-add-sofware-from-modal-to-pages create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAddPage.tsx create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/SoftwareAppStore.tsx create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/_styles.scss create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/index.ts create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/SoftwareFleetMaintained.tsx create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/_styles.scss create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/index.ts create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwarePackage/SoftwarePackage.tsx create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwarePackage/_styles.scss create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwarePackage/index.ts create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/_styles.scss create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/index.ts diff --git a/changes/218090-add-sofware-from-modal-to-pages b/changes/218090-add-sofware-from-modal-to-pages new file mode 100644 index 000000000000..e5c6321a7b67 --- /dev/null +++ b/changes/218090-add-sofware-from-modal-to-pages @@ -0,0 +1 @@ +- change add software modal to be seperate pages in Fleet UI diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAddPage.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAddPage.tsx new file mode 100644 index 000000000000..8ccb94c7844a --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAddPage.tsx @@ -0,0 +1,108 @@ +import React, { useCallback } from "react"; +import { Tab, TabList, Tabs } from "react-tabs"; +import { InjectedRouter } from "react-router"; +import { Location } from "history"; + +import PATHS from "router/paths"; +import { buildQueryStringFromParams } from "utilities/url"; + +import MainContent from "components/MainContent"; +import BackLink from "components/BackLink"; +import TabsWrapper from "components/TabsWrapper"; + +const baseClass = "software-add-page"; + +interface IAddSoftwareSubNavItem { + name: string; + pathname: string; +} + +const addSoftwareSubNav: IAddSoftwareSubNavItem[] = [ + { + name: "Fleet-maintained", + pathname: PATHS.SOFTWARE_ADD_FLEET_MAINTAINED, + }, + { + name: "Package", + pathname: PATHS.SOFTWARE_ADD_PACKAGE, + }, + { + name: "App store (VPP)", + pathname: PATHS.SOFTWARE_ADD_APP_STORE, + }, +]; + +const getTabIndex = (path: string): number => { + return addSoftwareSubNav.findIndex((navItem) => { + // tab stays highlighted for paths that start with same pathname + return path.startsWith(navItem.pathname); + }); +}; + +export interface ISoftwareAddPageQueryParams { + team_id?: string; + query?: string; + page?: string; + order_key?: string; + order_direction?: "asc" | "desc"; +} + +interface ISoftwareAddPageProps { + children: JSX.Element; + location: Location; + router: InjectedRouter; +} + +const SoftwareAddPage = ({ + children, + location, + router, +}: ISoftwareAddPageProps) => { + const navigateToNav = useCallback( + (i: number): void => { + // Only query param to persist between tabs is team id + const teamIdParam = buildQueryStringFromParams({ + team_id: location?.query.team_id, + }); + + const navPath = addSoftwareSubNav[i].pathname.concat(`?${teamIdParam}`); + router.replace(navPath); + }, + [location, router] + ); + + return ( + + <> + +

Add Software

+ + + + {addSoftwareSubNav.map((navItem) => { + return ( + + {navItem.name} + + ); + })} + + + + {React.cloneElement(children, { + router, + teamId: location.query.team_id, + })} + +
+ ); +}; + +export default SoftwareAddPage; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/SoftwareAppStore.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/SoftwareAppStore.tsx new file mode 100644 index 000000000000..1d152e87ee3e --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/SoftwareAppStore.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import { InjectedRouter } from "react-router"; +import { Location } from "history"; + +import { ISoftwareAddPageQueryParams } from "../SoftwareAddPage"; + +const baseClass = "software-app-store"; + +interface ISoftwareAppStoreProps { + currentTeamId: number; + router: InjectedRouter; + location: Location; +} + +const SoftwareAppStore = ({ + currentTeamId, + router, + location, +}: ISoftwareAppStoreProps) => { + return
Software App store page
; +}; + +export default SoftwareAppStore; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/_styles.scss b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/_styles.scss new file mode 100644 index 000000000000..24c6a329d27c --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/_styles.scss @@ -0,0 +1,3 @@ +.software-app-store { + +} diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/index.ts b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/index.ts new file mode 100644 index 000000000000..6691b50cd3ca --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/index.ts @@ -0,0 +1 @@ +export { default } from "./SoftwareAppStore"; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/SoftwareFleetMaintained.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/SoftwareFleetMaintained.tsx new file mode 100644 index 000000000000..aaa9627bddbb --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/SoftwareFleetMaintained.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import { InjectedRouter } from "react-router"; +import { Location } from "history"; + +import { DEFAULT_QUERY } from "utilities/constants"; + +import { ISoftwareAddPageQueryParams } from "../SoftwareAddPage"; + +const baseClass = "software-fleet-maintained"; + +interface ISoftwareFleetMaintainedProps { + currentTeamId: number; + router: InjectedRouter; + location: Location; +} + +// default values for query params used on this page if not provided +const DEFAULT_SORT_DIRECTION = "desc"; +const DEFAULT_SORT_HEADER = "hosts_count"; +const DEFAULT_PAGE_SIZE = 20; +const DEFAULT_PAGE = 0; + +const SoftwareFleetMaintained = ({ + currentTeamId, + router, + location, +}: ISoftwareFleetMaintainedProps) => { + const { + order_key = DEFAULT_SORT_HEADER, + order_direction = DEFAULT_SORT_DIRECTION, + query = DEFAULT_QUERY, + page, + } = location.query; + const currentPage = page ? parseInt(page, 10) : DEFAULT_PAGE; + + return
Maintained Page
; +}; + +export default SoftwareFleetMaintained; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/_styles.scss b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/_styles.scss new file mode 100644 index 000000000000..a3e72e4c244f --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/_styles.scss @@ -0,0 +1,3 @@ +.software-fleet-maintained { + +} diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/index.ts b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/index.ts new file mode 100644 index 000000000000..b71ab02cecb6 --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/index.ts @@ -0,0 +1 @@ +export { default } from "./SoftwareFleetMaintained"; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwarePackage/SoftwarePackage.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwarePackage/SoftwarePackage.tsx new file mode 100644 index 000000000000..bb294fe62201 --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwarePackage/SoftwarePackage.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import { InjectedRouter } from "react-router"; +import { Location } from "history"; + +import { ISoftwareAddPageQueryParams } from "../SoftwareAddPage"; + +const baseClass = "software-package"; + +interface ISoftwarePackageProps { + currentTeamId: number; + router: InjectedRouter; + location: Location; +} + +const SoftwarePackage = ({ + currentTeamId, + router, + location, +}: ISoftwarePackageProps) => { + return
Sofware package page
; +}; + +export default SoftwarePackage; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwarePackage/_styles.scss b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwarePackage/_styles.scss new file mode 100644 index 000000000000..e197624a521a --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwarePackage/_styles.scss @@ -0,0 +1,3 @@ +.software-package { + +} diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwarePackage/index.ts b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwarePackage/index.ts new file mode 100644 index 000000000000..c8ed0ce44b96 --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwarePackage/index.ts @@ -0,0 +1 @@ +export { default } from "./SoftwarePackage"; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/_styles.scss b/frontend/pages/SoftwarePage/SoftwareAddPage/_styles.scss new file mode 100644 index 000000000000..62eae961c494 --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/_styles.scss @@ -0,0 +1,10 @@ +.software-add-page { + + &__back-to-software { + margin-bottom: $pad-medium; + } + + h1 { + margin-bottom: $pad-large; + } +} diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/index.ts b/frontend/pages/SoftwarePage/SoftwareAddPage/index.ts new file mode 100644 index 000000000000..75faba6fbba5 --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/index.ts @@ -0,0 +1 @@ +export { default } from "./SoftwareAddPage"; diff --git a/frontend/pages/SoftwarePage/SoftwarePage.tsx b/frontend/pages/SoftwarePage/SoftwarePage.tsx index 9dcf95250fe2..ed3489133dfa 100644 --- a/frontend/pages/SoftwarePage/SoftwarePage.tsx +++ b/frontend/pages/SoftwarePage/SoftwarePage.tsx @@ -13,7 +13,7 @@ import { IZendeskIntegration, IZendeskJiraIntegrations, } from "interfaces/integration"; -import { ITeamConfig } from "interfaces/team"; +import { APP_CONTEXT_ALL_TEAMS_ID, ITeamConfig } from "interfaces/team"; import { IWebhookSoftwareVulnerabilities } from "interfaces/webhook"; import configAPI from "services/entities/config"; import teamsAPI, { ILoadTeamResponse } from "services/entities/teams"; @@ -255,10 +255,6 @@ const SoftwarePage = ({ children, router, location }: ISoftwarePageProps) => { setShowManageAutomationsModal(!showManageAutomationsModal); }, [setShowManageAutomationsModal, showManageAutomationsModal]); - const toggleAddSoftwareModal = useCallback(() => { - setShowAddSoftwareModal(!showAddSoftwareModal); - }, [showAddSoftwareModal]); - const togglePreviewPayloadModal = useCallback(() => { setShowPreviewPayloadModal(!showPreviewPayloadModal); }, [setShowPreviewPayloadModal, showPreviewPayloadModal]); @@ -294,6 +290,16 @@ const SoftwarePage = ({ children, router, location }: ISoftwarePageProps) => { } }; + const onAddSoftware = useCallback(() => { + if (currentTeamId === APP_CONTEXT_ALL_TEAMS_ID) { + setShowAddSoftwareModal(true); + } else { + router.push( + `${PATHS.SOFTWARE_ADD_FLEET_MAINTAINED}?team_id=${currentTeamId}` + ); + } + }, [currentTeamId, router]); + // NOTE: used to reset page number to 0 when modifying filters // NOTE: Solution reused from ManageHostPage.tsx useEffect(() => { @@ -383,7 +389,7 @@ const SoftwarePage = ({ children, router, location }: ISoftwarePageProps) => { )} {canAddSoftware && ( - )} @@ -473,10 +479,7 @@ const SoftwarePage = ({ children, router, location }: ISoftwarePageProps) => { )} {showAddSoftwareModal && ( setShowAddSoftwareModal(false)} isFreeTier={isFreeTier} /> )} diff --git a/frontend/pages/SoftwarePage/components/AddSoftwareModal/AddSoftwareModal.tsx b/frontend/pages/SoftwarePage/components/AddSoftwareModal/AddSoftwareModal.tsx index 4046e090c9fd..6f42d5297be9 100644 --- a/frontend/pages/SoftwarePage/components/AddSoftwareModal/AddSoftwareModal.tsx +++ b/frontend/pages/SoftwarePage/components/AddSoftwareModal/AddSoftwareModal.tsx @@ -1,17 +1,9 @@ import React from "react"; -import { InjectedRouter } from "react-router"; -import { Tab, TabList, TabPanel, Tabs } from "react-tabs"; - -import { APP_CONTEXT_ALL_TEAMS_ID } from "interfaces/team"; import Modal from "components/Modal"; import Button from "components/buttons/Button"; -import TabsWrapper from "components/TabsWrapper"; import PremiumFeatureMessage from "components/PremiumFeatureMessage"; -import AppStoreVpp from "../AppStoreVpp"; -import AddPackage from "../AddPackage"; - const baseClass = "add-software-modal"; interface IAllTeamsMessageProps { @@ -35,20 +27,11 @@ const AllTeamsMessage = ({ onExit }: IAllTeamsMessageProps) => { }; interface IAddSoftwareModalProps { - teamId: number; - router: InjectedRouter; onExit: () => void; - setAddedSoftwareToken: (token: string) => void; isFreeTier?: boolean; } -const AddSoftwareModal = ({ - teamId, - router, - onExit, - setAddedSoftwareToken, - isFreeTier, -}: IAddSoftwareModalProps) => { +const AddSoftwareModal = ({ onExit, isFreeTier }: IAddSoftwareModalProps) => { const renderModalContent = () => { if (isFreeTier) { return ( @@ -63,45 +46,11 @@ const AddSoftwareModal = ({ ); } - if (teamId === APP_CONTEXT_ALL_TEAMS_ID) { - return ; - } - - return ( - - - - Package - App Store (VPP) - - - - - - - - - - ); + return ; }; return ( - + {renderModalContent()} ); diff --git a/frontend/router/index.tsx b/frontend/router/index.tsx index ecdb4aa9fca9..7967069567f2 100644 --- a/frontend/router/index.tsx +++ b/frontend/router/index.tsx @@ -77,6 +77,10 @@ import SoftwareVersionDetailsPage from "pages/SoftwarePage/SoftwareVersionDetail import TeamSettings from "pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamSettings"; import SoftwareOSDetailsPage from "pages/SoftwarePage/SoftwareOSDetailsPage"; import SoftwareVulnerabilityDetailsPage from "pages/SoftwarePage/SoftwareVulnerabilityDetailsPage"; +import SoftwareAddPage from "pages/SoftwarePage/SoftwareAddPage"; +import SoftwareFleetMaintained from "pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained"; +import SoftwarePackage from "pages/SoftwarePage/SoftwareAddPage/SoftwarePackage"; +import SoftwareAppStore from "pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore"; import PATHS from "router/paths"; @@ -271,6 +275,17 @@ const routes = ( + {/* we check the add route first otherwise a route like 'software/add' will be caught + * by the 'software/:id' redirect and be redirected to 'software/versions/add */} + + + + + + diff --git a/frontend/router/paths.ts b/frontend/router/paths.ts index 3c22cd097a01..ede349c3dcbd 100644 --- a/frontend/router/paths.ts +++ b/frontend/router/paths.ts @@ -75,6 +75,9 @@ export default { SOFTWARE_VULNERABILITY_DETAILS: (cve: string): string => { return `${URL_PREFIX}/software/vulnerabilities/${cve}`; }, + SOFTWARE_ADD_FLEET_MAINTAINED: `${URL_PREFIX}/software/add/fleet-maintained`, + SOFTWARE_ADD_PACKAGE: `${URL_PREFIX}/software/add/package`, + SOFTWARE_ADD_APP_STORE: `${URL_PREFIX}/software/add/app-store`, // Label pages LABEL_NEW_DYNAMIC: `${URL_PREFIX}/labels/new/dynamic`, From 58f348a274ee20388f75f73cfe3029ce8f685de1 Mon Sep 17 00:00:00 2001 From: Martin Angers Date: Wed, 11 Sep 2024 10:07:55 -0400 Subject: [PATCH 006/385] Maintained Apps: Add postman to the list of maintained apps (#21974) --- server/mdm/maintainedapps/apps.json | 5 + server/mdm/maintainedapps/ingest.go | 32 ++--- .../testdata/expected_apps.json | 5 + .../mdm/maintainedapps/testdata/postman.json | 117 ++++++++++++++++++ 4 files changed, 145 insertions(+), 14 deletions(-) create mode 100644 server/mdm/maintainedapps/testdata/postman.json diff --git a/server/mdm/maintainedapps/apps.json b/server/mdm/maintainedapps/apps.json index 159d18b05ca7..9af089cf1913 100644 --- a/server/mdm/maintainedapps/apps.json +++ b/server/mdm/maintainedapps/apps.json @@ -69,6 +69,11 @@ "bundle_identifier": "notion.id", "installer_format": "dmg:app" }, + { + "identifier": "postman", + "bundle_identifier": "com.postmanlabs.mac", + "installer_format": "zip:app" + }, { "identifier": "slack", "bundle_identifier": "com.tinyspeck.slackmacgap", diff --git a/server/mdm/maintainedapps/ingest.go b/server/mdm/maintainedapps/ingest.go index f829f33091da..e24e6d60db23 100644 --- a/server/mdm/maintainedapps/ingest.go +++ b/server/mdm/maintainedapps/ingest.go @@ -183,9 +183,13 @@ type brewArtifact struct { // Pkg is a bit like Binary, it is an array with a string and an object as // first two elements. The object has a choices field with an array of // objects. See Microsoft Edge. - Pkg []optjson.StringOr[*brewPkgChoices] `json:"pkg"` - Uninstall []*brewUninstall `json:"uninstall"` - Zap []*brewZap `json:"zap"` + Pkg []optjson.StringOr[*brewPkgChoices] `json:"pkg"` + // Zap and Uninstall have the same format, they support the same stanzas. + // It's just that in homebrew, Zaps are not processed by default (only when + // --zap is provided on uninstall). For our uninstall scripts, we want to + // process the zaps. + Uninstall []*brewUninstall `json:"uninstall"` + Zap []*brewUninstall `json:"zap"` // Binary is an array with a string and an object as first two elements. See // the "docker" and "firefox" casks. Binary []optjson.StringOr[*brewBinaryTarget] `json:"binary"` @@ -206,15 +210,15 @@ type brewUninstall struct { LaunchCtl optjson.StringOr[[]string] `json:"launchctl"` Quit optjson.StringOr[[]string] `json:"quit"` PkgUtil optjson.StringOr[[]string] `json:"pkgutil"` - Script optjson.StringOr[[]string] `json:"script"` - // format: [0]=signal, [1]=process name - Signal optjson.StringOr[[]string] `json:"signal"` - Delete optjson.StringOr[[]string] `json:"delete"` - RmDir optjson.StringOr[[]string] `json:"rmdir"` -} - -// same as brewUninstall, can be []string or string (see Microsoft Teams). -type brewZap struct { - Trash optjson.StringOr[[]string] `json:"trash"` - RmDir optjson.StringOr[[]string] `json:"rmdir"` + // brew docs says string or hash, but our only case has a single string. + Script optjson.StringOr[map[string]any] `json:"script"` + // format: [0]=signal, [1]=process name (although the brew documentation says + // it's an array of arrays, it's not like that in our single case that uses + // it). + Signal optjson.StringOr[[]string] `json:"signal"` + Delete optjson.StringOr[[]string] `json:"delete"` + RmDir optjson.StringOr[[]string] `json:"rmdir"` + Trash optjson.StringOr[[]string] `json:"trash"` + LoginItem optjson.StringOr[[]string] `json:"login_item"` + Kext optjson.StringOr[[]string] `json:"kext"` } diff --git a/server/mdm/maintainedapps/testdata/expected_apps.json b/server/mdm/maintainedapps/testdata/expected_apps.json index 1da9cc4584a0..66c39b96e212 100644 --- a/server/mdm/maintainedapps/testdata/expected_apps.json +++ b/server/mdm/maintainedapps/testdata/expected_apps.json @@ -69,6 +69,11 @@ "version": "3.14.0", "platform": "darwin" }, + { + "name": "Postman", + "version": "11.12.0", + "platform": "darwin" + }, { "name": "Slack", "version": "4.40.126", diff --git a/server/mdm/maintainedapps/testdata/postman.json b/server/mdm/maintainedapps/testdata/postman.json new file mode 100644 index 000000000000..e6ca9722ad81 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/postman.json @@ -0,0 +1,117 @@ +{ + "token": "postman", + "full_token": "postman", + "old_tokens": [], + "tap": "homebrew/cask", + "name": [ + "Postman" + ], + "desc": "Collaboration platform for API development", + "homepage": "https://www.postman.com/", + "url": "https://dl.pstmn.io/download/version/11.12.0/osx_arm64", + "url_specs": { + "verified": "dl.pstmn.io/download/version/" + }, + "version": "11.12.0", + "installed": null, + "installed_time": null, + "bundle_version": null, + "bundle_short_version": null, + "outdated": false, + "sha256": "f08acef5bda961130ba19055ee371183f1505a96ea73a1ee0fd41755de9b05ff", + "artifacts": [ + { + "app": [ + "Postman.app" + ] + }, + { + "zap": [ + { + "trash": [ + "~/Library/Application Support/com.postmanlabs.mac.ShipIt", + "~/Library/Application Support/Postman", + "~/Library/Caches/com.postmanlabs.mac", + "~/Library/Caches/com.postmanlabs.mac.ShipIt", + "~/Library/Caches/Postman", + "~/Library/HTTPStorages/com.postmanlabs.mac", + "~/Library/Preferences/ByHost/com.postmanlabs.mac.ShipIt.*.plist", + "~/Library/Preferences/com.postmanlabs.mac.plist", + "~/Library/Saved Application State/com.postmanlabs.mac.savedState" + ] + } + ] + } + ], + "caveats": null, + "depends_on": { + "macos": { + ">=": [ + "10.13" + ] + } + }, + "conflicts_with": null, + "container": null, + "auto_updates": true, + "deprecated": false, + "deprecation_date": null, + "deprecation_reason": null, + "disabled": false, + "disable_date": null, + "disable_reason": null, + "tap_git_head": "5bd28fbb2da3cf0c3ef1009af5fa4f36c6e74cae", + "languages": [], + "ruby_source_path": "Casks/p/postman.rb", + "ruby_source_checksum": { + "sha256": "92056b4da6ed15435fb1a668ea1d640aa99aa0e80a8a479ae65750e767555fef" + }, + "variations": { + "sequoia": { + "url": "https://dl.pstmn.io/download/version/11.12.0/osx64", + "sha256": "b84fa38db9d345215ad7e5a07ff9651e293bacbd63bad531b827c8a0daece698" + }, + "sonoma": { + "url": "https://dl.pstmn.io/download/version/11.12.0/osx64", + "sha256": "b84fa38db9d345215ad7e5a07ff9651e293bacbd63bad531b827c8a0daece698" + }, + "ventura": { + "url": "https://dl.pstmn.io/download/version/11.12.0/osx64", + "sha256": "b84fa38db9d345215ad7e5a07ff9651e293bacbd63bad531b827c8a0daece698" + }, + "monterey": { + "url": "https://dl.pstmn.io/download/version/11.12.0/osx64", + "sha256": "b84fa38db9d345215ad7e5a07ff9651e293bacbd63bad531b827c8a0daece698" + }, + "big_sur": { + "url": "https://dl.pstmn.io/download/version/11.12.0/osx64", + "sha256": "b84fa38db9d345215ad7e5a07ff9651e293bacbd63bad531b827c8a0daece698" + }, + "catalina": { + "url": "https://dl.pstmn.io/download/version/11.12.0/osx64", + "sha256": "b84fa38db9d345215ad7e5a07ff9651e293bacbd63bad531b827c8a0daece698" + }, + "mojave": { + "url": "https://dl.pstmn.io/download/version/11.12.0/osx64", + "sha256": "b84fa38db9d345215ad7e5a07ff9651e293bacbd63bad531b827c8a0daece698" + }, + "high_sierra": { + "url": "https://dl.pstmn.io/download/version/11.12.0/osx64", + "sha256": "b84fa38db9d345215ad7e5a07ff9651e293bacbd63bad531b827c8a0daece698" + } + }, + "analytics": { + "install": { + "30d": { + "postman": 12107 + }, + "90d": { + "postman": 33679 + }, + "365d": { + "postman": 131069 + } + } + }, + "generated_date": "2024-09-11" +} From 01e3736b1f7d0dd3008b2bb86e21ba28d6892bcc Mon Sep 17 00:00:00 2001 From: Jahziel Villasana-Espinoza Date: Mon, 16 Sep 2024 17:47:00 -0400 Subject: [PATCH 007/385] fix: update migration test with columns added recently (#22137) > No issue, fixes broken test # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Added/updated tests --- .../tables/20240909145426_AddFleetLibraryAppsTable_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/datastore/mysql/migrations/tables/20240909145426_AddFleetLibraryAppsTable_test.go b/server/datastore/mysql/migrations/tables/20240909145426_AddFleetLibraryAppsTable_test.go index 66a80a4ddfae..8dc3ed51c4eb 100644 --- a/server/datastore/mysql/migrations/tables/20240909145426_AddFleetLibraryAppsTable_test.go +++ b/server/datastore/mysql/migrations/tables/20240909145426_AddFleetLibraryAppsTable_test.go @@ -13,9 +13,9 @@ func TestUp_20240909145426(t *testing.T) { execNoErr(t, db, `INSERT INTO script_contents (id, md5_checksum, contents) VALUES (1, 'checksum', 'script content')`) swiID := execNoErrLastID(t, db, ` INSERT INTO software_installers - (filename, version, platform, install_script_content_id, storage_id) + (filename, version, platform, install_script_content_id, storage_id, package_ids, uninstall_script_content_id) VALUES - (?,?,?,?,?)`, "sw1-installer.pkg", "1.2", "darwin", 1, "storage-id1") + (?,?,?,?,?,?,?)`, "sw1-installer.pkg", "1.2", "darwin", 1, "storage-id1", "", 1) // Apply current migration. applyNext(t, db) From 92e0da0c7bb8a5677fd851339bab10160c0475e0 Mon Sep 17 00:00:00 2001 From: Jahziel Villasana-Espinoza Date: Wed, 18 Sep 2024 12:21:53 -0400 Subject: [PATCH 008/385] feat: add fleet-maintained software (#22031) > Related issue: #21776 # 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 --- changes/21776-add-software | 1 + ee/server/service/maintained_apps.go | 103 +++++++++++ server/datastore/mysql/maintained_apps.go | 49 +++++- .../datastore/mysql/maintained_apps_test.go | 25 ++- server/datastore/mysql/software_installers.go | 9 +- server/fleet/datastore.go | 9 +- server/fleet/scripts.go | 2 + server/fleet/service.go | 6 + server/fleet/software_installer.go | 3 + server/mdm/maintainedapps/ingest.go | 2 +- server/mdm/maintainedapps/ingest_test.go | 4 +- server/mdm/maintainedapps/installers.go | 79 +++++++++ server/mock/datastore_mock.go | 16 +- server/service/handler.go | 3 + server/service/integration_enterprise_test.go | 164 ++++++++++++++++++ server/service/maintained_apps.go | 39 +++++ 16 files changed, 501 insertions(+), 13 deletions(-) create mode 100644 changes/21776-add-software create mode 100644 ee/server/service/maintained_apps.go create mode 100644 server/mdm/maintainedapps/installers.go create mode 100644 server/service/maintained_apps.go diff --git a/changes/21776-add-software b/changes/21776-add-software new file mode 100644 index 000000000000..65c32b4d8805 --- /dev/null +++ b/changes/21776-add-software @@ -0,0 +1 @@ +- Adds the `POST /software/fleet_maintained` endpoint for adding Fleet-maintained apps. \ No newline at end of file diff --git a/ee/server/service/maintained_apps.go b/ee/server/service/maintained_apps.go new file mode 100644 index 000000000000..34497b6e9091 --- /dev/null +++ b/ee/server/service/maintained_apps.go @@ -0,0 +1,103 @@ +package service + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/hex" + + "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" + "github.com/fleetdm/fleet/v4/server/contexts/viewer" + "github.com/fleetdm/fleet/v4/server/fleet" + "github.com/fleetdm/fleet/v4/server/mdm/maintainedapps" +) + +func (svc *Service) AddFleetMaintainedApp(ctx context.Context, teamID *uint, appID uint, installScript, preInstallQuery, postInstallScript string, selfService bool) error { + if err := svc.authz.Authorize(ctx, &fleet.SoftwareInstaller{TeamID: teamID}, fleet.ActionWrite); err != nil { + return err + } + + vc, ok := viewer.FromContext(ctx) + if !ok { + return fleet.ErrNoContext + } + + app, err := svc.ds.GetMaintainedAppByID(ctx, appID) + if err != nil { + return ctxerr.Wrap(ctx, err, "getting maintained app by id") + } + + // Download installer from the URL + installerBytes, filename, err := maintainedapps.DownloadInstaller(ctx, app.InstallerURL) + if err != nil { + return ctxerr.Wrap(ctx, err, "downloading app installer") + } + + // Validate the bytes we got are what we expected + h := sha256.New() + _, err = h.Write(installerBytes) + if err != nil { + return ctxerr.Wrap(ctx, err, "generating SHA256 of maintained app installer") + } + gotHash := hex.EncodeToString(h.Sum(nil)) + + if gotHash != app.SHA256 { + return ctxerr.New(ctx, "mismatch in maintained app SHA256 hash") + } + + // Fall back to the filename if we weren't able to extract a filename from the installer response + if filename == "" { + filename = app.Name + } + + installerReader := bytes.NewReader(installerBytes) + payload := &fleet.UploadSoftwareInstallerPayload{ + InstallerFile: installerReader, + Title: app.Name, + UserID: vc.UserID(), + TeamID: teamID, + Version: app.Version, + Filename: filename, + Platform: string(app.Platform), + BundleIdentifier: app.BundleIdentifier, + StorageID: app.SHA256, + FleetLibraryAppID: &app.ID, + PreInstallQuery: preInstallQuery, + PostInstallScript: postInstallScript, + SelfService: selfService, + InstallScript: installScript, + } + + // Create record in software installers table + _, err = svc.ds.MatchOrCreateSoftwareInstaller(ctx, payload) + if err != nil { + return ctxerr.Wrap(ctx, err, "setting downloaded installer") + } + + // Save in S3 + if err := svc.storeSoftware(ctx, payload); err != nil { + return ctxerr.Wrap(ctx, err, "upload maintained app installer to S3") + } + + // Create activity + var teamName *string + if payload.TeamID != nil && *payload.TeamID != 0 { + t, err := svc.ds.Team(ctx, *payload.TeamID) + if err != nil { + return ctxerr.Wrap(ctx, err, "getting team") + } + teamName = &t.Name + } + + if err := svc.NewActivity(ctx, vc.User, fleet.ActivityTypeAddedSoftware{ + SoftwareTitle: payload.Title, + SoftwarePackage: payload.Filename, + TeamName: teamName, + TeamID: payload.TeamID, + SelfService: payload.SelfService, + }); err != nil { + return ctxerr.Wrap(ctx, err, "creating activity for added software") + } + + return nil +} diff --git a/server/datastore/mysql/maintained_apps.go b/server/datastore/mysql/maintained_apps.go index df38f1bcd6f8..45f97ee5a21e 100644 --- a/server/datastore/mysql/maintained_apps.go +++ b/server/datastore/mysql/maintained_apps.go @@ -2,13 +2,15 @@ package mysql import ( "context" + "database/sql" + "errors" "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" "github.com/fleetdm/fleet/v4/server/fleet" "github.com/jmoiron/sqlx" ) -func (ds *Datastore) UpsertMaintainedApp(ctx context.Context, app *fleet.MaintainedApp) error { +func (ds *Datastore) UpsertMaintainedApp(ctx context.Context, app *fleet.MaintainedApp) (*fleet.MaintainedApp, error) { const upsertStmt = ` INSERT INTO fleet_library_apps ( @@ -29,7 +31,8 @@ ON DUPLICATE KEY UPDATE uninstall_script_content_id = VALUES(uninstall_script_content_id) ` - return ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error { + var appID uint + err := ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error { var err error // ensure the install script exists @@ -47,8 +50,48 @@ ON DUPLICATE KEY UPDATE uninstallScriptID, _ := uninstallRes.LastInsertId() // upsert the maintained app - _, err = tx.ExecContext(ctx, upsertStmt, app.Name, app.Token, app.Version, app.Platform, app.InstallerURL, + res, err := tx.ExecContext(ctx, upsertStmt, app.Name, app.Token, app.Version, app.Platform, app.InstallerURL, app.SHA256, app.BundleIdentifier, installScriptID, uninstallScriptID) + id, _ := res.LastInsertId() + appID = uint(id) return ctxerr.Wrap(ctx, err, "upsert maintained app") }) + if err != nil { + return nil, err + } + + app.ID = appID + return app, nil +} + +func (ds *Datastore) GetMaintainedAppByID(ctx context.Context, appID uint) (*fleet.MaintainedApp, error) { + const stmt = ` +SELECT + fla.id, + fla.name, + fla.token, + fla.version, + fla.platform, + fla.installer_url, + fla.sha256, + fla.bundle_identifier, + sc1.contents AS install_script, + sc2.contents AS uninstall_script +FROM fleet_library_apps fla +JOIN script_contents sc1 ON sc1.id = fla.install_script_content_id +JOIN script_contents sc2 ON sc2.id = fla.uninstall_script_content_id +WHERE + fla.id = ? + ` + + var app fleet.MaintainedApp + if err := sqlx.GetContext(ctx, ds.reader(ctx), &app, stmt, appID); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, ctxerr.Wrap(ctx, notFound("MaintainedApp"), "no matching maintained app found") + } + + return nil, ctxerr.Wrap(ctx, err, "getting maintained app by id") + } + + return &app, nil } diff --git a/server/datastore/mysql/maintained_apps_test.go b/server/datastore/mysql/maintained_apps_test.go index b011ad8fa2f2..4e99c1d2c357 100644 --- a/server/datastore/mysql/maintained_apps_test.go +++ b/server/datastore/mysql/maintained_apps_test.go @@ -21,6 +21,7 @@ func TestMaintainedApps(t *testing.T) { }{ {"UpsertMaintainedApps", testUpsertMaintainedApps}, {"IngestWithBrew", testIngestWithBrew}, + {"GetMaintainedAppByID", testGetMaintainedAppByID}, } for _, c := range cases { @@ -50,7 +51,7 @@ func testUpsertMaintainedApps(t *testing.T, ds *Datastore) { require.Equal(t, expectedApps, listSavedApps()) // upsert the figma app, changing the version - err := ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ + _, err := ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ Name: "Figma", Token: "figma", InstallerURL: "https://desktop.figma.com/mac-arm/Figma-999.9.9.zip", @@ -85,3 +86,25 @@ func testIngestWithBrew(t *testing.T, ds *Datastore) { }) require.ElementsMatch(t, expectedTokens, actualTokens) } + +func testGetMaintainedAppByID(t *testing.T, ds *Datastore) { + ctx := context.Background() + + expApp, err := ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ + Name: "foo", + Token: "foo", + Version: "1.0.0", + Platform: "darwin", + InstallerURL: "https://example.com/foo.zip", + SHA256: "abc", + BundleIdentifier: "abc", + InstallScript: "foo", + UninstallScript: "foo", + }) + require.NoError(t, err) + + gotApp, err := ds.GetMaintainedAppByID(ctx, expApp.ID) + require.NoError(t, err) + + require.Equal(t, expApp, gotApp) +} diff --git a/server/datastore/mysql/software_installers.go b/server/datastore/mysql/software_installers.go index 5aa7a2f11d51..972792d473a2 100644 --- a/server/datastore/mysql/software_installers.go +++ b/server/datastore/mysql/software_installers.go @@ -130,8 +130,9 @@ INSERT INTO software_installers ( self_service, user_id, user_name, - user_email -) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, (SELECT name FROM users WHERE id = ?), (SELECT email FROM users WHERE id = ?))` + user_email, + fleet_library_app_id +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, (SELECT name FROM users WHERE id = ?), (SELECT email FROM users WHERE id = ?), ?)` args := []interface{}{ tid, @@ -151,6 +152,7 @@ INSERT INTO software_installers ( payload.UserID, payload.UserID, payload.UserID, + payload.FleetLibraryAppID, } res, err := ds.writer(ctx).ExecContext(ctx, stmt, args...) @@ -333,7 +335,8 @@ SELECT si.uninstall_script_content_id, si.uploaded_at, COALESCE(st.name, '') AS software_title, - si.platform + si.platform, + si.fleet_library_app_id FROM software_installers si LEFT OUTER JOIN software_titles st ON st.id = si.title_id diff --git a/server/fleet/datastore.go b/server/fleet/datastore.go index b77fcf86f362..40f34363d487 100644 --- a/server/fleet/datastore.go +++ b/server/fleet/datastore.go @@ -1726,9 +1726,16 @@ type Datastore interface { GetVPPTokenByLocation(ctx context.Context, loc string) (*VPPTokenDB, error) + /////////////////////////////////////////////////////////////////////////////// + // Fleet-maintained apps + // + + // GetMaintainedAppByID gets a Fleet-maintained app by its ID. + GetMaintainedAppByID(ctx context.Context, appID uint) (*MaintainedApp, error) + // UpsertMaintainedApp inserts or updates a maintained app using the updated // metadata provided via app. - UpsertMaintainedApp(ctx context.Context, app *MaintainedApp) error + UpsertMaintainedApp(ctx context.Context, app *MaintainedApp) (*MaintainedApp, error) } // MDMAppleStore wraps nanomdm's storage and adds methods to deal with diff --git a/server/fleet/scripts.go b/server/fleet/scripts.go index c6aeeaa93406..798d63a87dff 100644 --- a/server/fleet/scripts.go +++ b/server/fleet/scripts.go @@ -375,6 +375,8 @@ type SoftwareInstallerPayload struct { UninstallScript string `json:"uninstall_script"` PostInstallScript string `json:"post_install_script"` SelfService bool `json:"self_service"` + FleetMaintained bool `json:"-"` + Filename string `json:"-"` } type HostLockWipeStatus struct { diff --git a/server/fleet/service.go b/server/fleet/service.go index 8599e464e1b5..134025d5c1a4 100644 --- a/server/fleet/service.go +++ b/server/fleet/service.go @@ -1114,6 +1114,12 @@ type Service interface { teamID *uint) (*DownloadSoftwareInstallerPayload, error) OrbitDownloadSoftwareInstaller(ctx context.Context, installerID uint) (*DownloadSoftwareInstallerPayload, error) + /////////////////////////////////////////////////////////////////////////////// + // Fleet-maintained apps + + // AddFleetMaintainedApp adds a Fleet-maintained app to the given team. + AddFleetMaintainedApp(ctx context.Context, teamID *uint, appID uint, installScript, preInstallQuery, postInstallScript string, selfService bool) error + // ///////////////////////////////////////////////////////////////////////////// // Maintenance windows diff --git a/server/fleet/software_installer.go b/server/fleet/software_installer.go index 09debe069579..c1ea948f533a 100644 --- a/server/fleet/software_installer.go +++ b/server/fleet/software_installer.go @@ -116,6 +116,8 @@ type SoftwareInstaller struct { SelfService bool `json:"self_service" db:"self_service"` // URL is the source URL for this installer (set when uploading via batch/gitops). URL string `json:"url" db:"url"` + // FleetLibraryAppID is the related Fleet-maintained app for this installer (if not nil). + FleetLibraryAppID *uint `json:"-" db:"fleet_library_app_id"` } // SoftwarePackageResponse is the response type used when applying software by batch. @@ -322,6 +324,7 @@ type UploadSoftwareInstallerPayload struct { SelfService bool UserID uint URL string + FleetLibraryAppID *uint PackageIDs []string UninstallScript string Extension string diff --git a/server/mdm/maintainedapps/ingest.go b/server/mdm/maintainedapps/ingest.go index e24e6d60db23..c699c22b8583 100644 --- a/server/mdm/maintainedapps/ingest.go +++ b/server/mdm/maintainedapps/ingest.go @@ -140,7 +140,7 @@ func (i ingester) ingestOne(ctx context.Context, app maintainedApp, client *http installScript := installScriptForApp(app, &cask) uninstallScript := uninstallScriptForApp(app, &cask) - err = i.ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ + _, err = i.ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ Name: cask.Name[0], Token: cask.Token, Version: cask.Version, diff --git a/server/mdm/maintainedapps/ingest_test.go b/server/mdm/maintainedapps/ingest_test.go index 3f1ae68f67ac..28e289490cd5 100644 --- a/server/mdm/maintainedapps/ingest_test.go +++ b/server/mdm/maintainedapps/ingest_test.go @@ -97,8 +97,8 @@ func TestIngestValidations(t *testing.T) { ctx := context.Background() ds := new(mock.Store) - ds.UpsertMaintainedAppFunc = func(ctx context.Context, app *fleet.MaintainedApp) error { - return nil + ds.UpsertMaintainedAppFunc = func(ctx context.Context, app *fleet.MaintainedApp) (*fleet.MaintainedApp, error) { + return nil, nil } cases := []struct { diff --git a/server/mdm/maintainedapps/installers.go b/server/mdm/maintainedapps/installers.go new file mode 100644 index 000000000000..35f5ef4ffc4a --- /dev/null +++ b/server/mdm/maintainedapps/installers.go @@ -0,0 +1,79 @@ +package maintainedapps + +import ( + "context" + "fmt" + "io" + "mime" + "net/http" + "net/url" + "path" + "time" + + "github.com/fleetdm/fleet/v4/pkg/fleethttp" + "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" + "github.com/fleetdm/fleet/v4/server/fleet" +) + +// DownloadInstaller downloads the maintained app installer located at the given URL. +func DownloadInstaller(ctx context.Context, installerURL string) ([]byte, string, error) { + // validate the URL before doing the request + _, err := url.ParseRequestURI(installerURL) + if err != nil { + return nil, "", fleet.NewInvalidArgumentError( + "fleet_maintained_app.url", + fmt.Sprintf("Couldn't download maintained app installer. URL (%q) is invalid", installerURL), + ) + } + + client := fleethttp.NewClient(fleethttp.WithTimeout(30 * time.Second)) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, installerURL, nil) + if err != nil { + return nil, "", ctxerr.Wrapf(ctx, err, "creating request for URL %s", installerURL) + } + + resp, err := client.Do(req) + if err != nil { + return nil, "", ctxerr.Wrapf(ctx, err, "performing request for URL %s", installerURL) + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + return nil, "", fleet.NewInvalidArgumentError( + "fleet_maintained_app.url", + fmt.Sprintf("Couldn't download maintained app installer. URL (%q) doesn't exist. Please make sure that URLs are publicy accessible to the internet.", installerURL), + ) + } + + // Allow all 2xx and 3xx status codes in this pass. + if resp.StatusCode > 400 { + return nil, "", fleet.NewInvalidArgumentError( + "fleet_maintained_app.url", + fmt.Sprintf("Couldn't download maintained app installer. URL (%q) received response status code %d.", installerURL, resp.StatusCode), + ) + } + + var filename string + cdh, ok := resp.Header["Content-Disposition"] + if ok && len(cdh) > 0 { + _, params, err := mime.ParseMediaType(cdh[0]) + if err == nil { + filename = params["filename"] + } + } + + // Fall back on extracting the filename from the URL + // This is OK for the first 20 apps we support, but we should do something more robust once we + // support more apps. + if filename == "" { + filename = path.Base(resp.Request.URL.Path) + } + + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return nil, "", ctxerr.Wrapf(ctx, err, "reading installer %q contents", installerURL) + } + + return bodyBytes, filename, nil +} diff --git a/server/mock/datastore_mock.go b/server/mock/datastore_mock.go index e39761088e67..c36698993e4c 100644 --- a/server/mock/datastore_mock.go +++ b/server/mock/datastore_mock.go @@ -1088,7 +1088,9 @@ type GetPastActivityDataForVPPAppInstallFunc func(ctx context.Context, commandRe type GetVPPTokenByLocationFunc func(ctx context.Context, loc string) (*fleet.VPPTokenDB, error) -type UpsertMaintainedAppFunc func(ctx context.Context, app *fleet.MaintainedApp) error +type GetMaintainedAppByIDFunc func(ctx context.Context, appID uint) (*fleet.MaintainedApp, error) + +type UpsertMaintainedAppFunc func(ctx context.Context, app *fleet.MaintainedApp) (*fleet.MaintainedApp, error) type DataStore struct { HealthCheckFunc HealthCheckFunc @@ -2693,6 +2695,9 @@ type DataStore struct { GetVPPTokenByLocationFunc GetVPPTokenByLocationFunc GetVPPTokenByLocationFuncInvoked bool + GetMaintainedAppByIDFunc GetMaintainedAppByIDFunc + GetMaintainedAppByIDFuncInvoked bool + UpsertMaintainedAppFunc UpsertMaintainedAppFunc UpsertMaintainedAppFuncInvoked bool @@ -6437,7 +6442,14 @@ func (s *DataStore) GetVPPTokenByLocation(ctx context.Context, loc string) (*fle return s.GetVPPTokenByLocationFunc(ctx, loc) } -func (s *DataStore) UpsertMaintainedApp(ctx context.Context, app *fleet.MaintainedApp) error { +func (s *DataStore) GetMaintainedAppByID(ctx context.Context, appID uint) (*fleet.MaintainedApp, error) { + s.mu.Lock() + s.GetMaintainedAppByIDFuncInvoked = true + s.mu.Unlock() + return s.GetMaintainedAppByIDFunc(ctx, appID) +} + +func (s *DataStore) UpsertMaintainedApp(ctx context.Context, app *fleet.MaintainedApp) (*fleet.MaintainedApp, error) { s.mu.Lock() s.UpsertMaintainedAppFuncInvoked = true s.mu.Unlock() diff --git a/server/service/handler.go b/server/service/handler.go index 21bdd2f7ed57..fd8bc0dccf1a 100644 --- a/server/service/handler.go +++ b/server/service/handler.go @@ -387,6 +387,9 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC ue.GET("/api/_version_/fleet/software/app_store_apps", getAppStoreAppsEndpoint, getAppStoreAppsRequest{}) ue.POST("/api/_version_/fleet/software/app_store_apps", addAppStoreAppEndpoint, addAppStoreAppRequest{}) + // Fleet-maintained apps + ue.POST("/api/_version_/fleet/software/fleet_maintained_apps", addFleetMaintainedAppEndpoint, addFleetMaintainedAppRequest{}) + // Vulnerabilities ue.GET("/api/_version_/fleet/vulnerabilities", listVulnerabilitiesEndpoint, listVulnerabilitiesRequest{}) ue.GET("/api/_version_/fleet/vulnerabilities/{cve}", getVulnerabilityEndpoint, getVulnerabilityRequest{}) diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go index 9255e95beff0..393d200fb674 100644 --- a/server/service/integration_enterprise_test.go +++ b/server/service/integration_enterprise_test.go @@ -3,6 +3,7 @@ package service import ( "bytes" "context" + "crypto/sha256" "database/sql" "encoding/base64" "encoding/hex" @@ -37,6 +38,7 @@ import ( "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/live_query/live_query_mock" "github.com/fleetdm/fleet/v4/server/mdm" + "github.com/fleetdm/fleet/v4/server/mdm/maintainedapps" "github.com/fleetdm/fleet/v4/server/ptr" "github.com/fleetdm/fleet/v4/server/pubsub" commonCalendar "github.com/fleetdm/fleet/v4/server/service/calendar" @@ -14225,3 +14227,165 @@ func (s *integrationEnterpriseTestSuite) TestPolicyAutomationsSoftwareInstallers require.NoError(t, err) require.Nil(t, hostVanillaOsquery5Team1LastInstall) } + +func (s *integrationEnterpriseTestSuite) TestMaintainedApps() { + t := s.T() + ctx := context.Background() + + installerBytes := []byte("abc") + // Mock server to serve the "installers" + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/badinstaller": + _, _ = w.Write([]byte("badinstaller")) + default: + _, _ = w.Write(installerBytes) + } + })) + defer srv.Close() + + getSoftwareInstallerIDByMAppID := func(mappID uint) uint { + var id uint + mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error { + return sqlx.GetContext(ctx, q, &id, "SELECT id FROM software_installers WHERE fleet_library_app_id = ?", mappID) + }) + + return id + } + + // Non-existent maintained app + s.Do("POST", "/api/latest/fleet/software/fleet_maintained_apps", &addFleetMaintainedAppRequest{AppID: 1}, http.StatusNotFound) + + // Insert the list of maintained apps + maintainedapps.IngestMaintainedApps(t, s.ds) + + // Edit DB to spoof URLs and SHA256 values so we don't have to actually download the installers + mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error { + h := sha256.New() + _, err := h.Write(installerBytes) + require.NoError(t, err) + spoofedSHA := hex.EncodeToString(h.Sum(nil)) + _, err = q.ExecContext(ctx, "UPDATE fleet_library_apps SET sha256 = ?, installer_url = ?", spoofedSHA, srv.URL+"/installer.zip") + require.NoError(t, err) + _, err = q.ExecContext(ctx, "UPDATE fleet_library_apps SET installer_url = ? WHERE id = 2", srv.URL+"/badinstaller") + return err + }) + + // Create a team + var newTeamResp teamResponse + s.DoJSON("POST", "/api/latest/fleet/teams", &createTeamRequest{TeamPayload: fleet.TeamPayload{Name: ptr.String("Team 1")}}, http.StatusOK, &newTeamResp) + team := newTeamResp.Team + + // Add an ingested app to the team + var addMAResp addFleetMaintainedAppResponse + req := &addFleetMaintainedAppRequest{ + AppID: 1, + TeamID: &team.ID, + SelfService: true, + PreInstallQuery: "SELECT 1", + InstallScript: "echo foo", + PostInstallScript: "echo done", + } + s.DoJSON("POST", "/api/latest/fleet/software/fleet_maintained_apps", req, http.StatusOK, &addMAResp) + require.Nil(t, addMAResp.Err) + + // Validate software installer fields + mapp, err := s.ds.GetMaintainedAppByID(ctx, 1) + require.NoError(t, err) + i, err := s.ds.GetSoftwareInstallerMetadataByID(context.Background(), getSoftwareInstallerIDByMAppID(1)) + require.NoError(t, err) + require.Equal(t, ptr.Uint(1), i.FleetLibraryAppID) + require.Equal(t, mapp.SHA256, i.StorageID) + require.Equal(t, "darwin", i.Platform) + require.NotEmpty(t, i.InstallScriptContentID) + require.Equal(t, req.PreInstallQuery, i.PreInstallQuery) + install, err := s.ds.GetAnyScriptContents(ctx, i.InstallScriptContentID) + require.NoError(t, err) + require.Equal(t, req.InstallScript, string(install)) + require.NotNil(t, i.PostInstallScriptContentID) + postinstall, err := s.ds.GetAnyScriptContents(ctx, *i.PostInstallScriptContentID) + require.NoError(t, err) + require.Equal(t, req.PostInstallScript, string(postinstall)) + + // The maintained app should now be in software titles + var resp listSoftwareTitlesResponse + s.DoJSON( + "GET", "/api/latest/fleet/software/titles", + listSoftwareTitlesRequest{}, + http.StatusOK, &resp, + "per_page", "1", + "order_key", "name", + "order_direction", "desc", + "available_for_install", "true", + "team_id", fmt.Sprintf("%d", team.ID), + ) + + require.Equal(t, 1, resp.Count) + title := resp.SoftwareTitles[0] + require.NotNil(t, title.BundleIdentifier) + require.Equal(t, ptr.String(mapp.BundleIdentifier), title.BundleIdentifier) + require.Equal(t, mapp.Version, title.SoftwarePackage.Version) + require.Equal(t, "installer.zip", title.SoftwarePackage.Name) + require.Equal(t, ptr.Bool(req.SelfService), title.SoftwarePackage.SelfService) + + // Check activity + s.lastActivityOfTypeMatches( + fleet.ActivityTypeAddedSoftware{}.ActivityName(), + fmt.Sprintf(`{"software_title": "%[1]s", "software_package": "installer.zip", "team_name": "%s", "team_id": %d, "self_service": true}`, mapp.Name, team.Name, team.ID), + 0, + ) + + // Should return an error; SHAs don't match up + r := s.Do("POST", "/api/latest/fleet/software/fleet_maintained_apps", &addFleetMaintainedAppRequest{AppID: 2}, http.StatusInternalServerError) + require.Contains(t, extractServerErrorText(r.Body), "mismatch in maintained app SHA256 hash") + + // Add a maintained app to no team + + req = &addFleetMaintainedAppRequest{ + AppID: 3, + SelfService: true, + PreInstallQuery: "SELECT 1", + InstallScript: "echo foo", + PostInstallScript: "echo done", + } + + addMAResp = addFleetMaintainedAppResponse{} + s.DoJSON("POST", "/api/latest/fleet/software/fleet_maintained_apps", req, http.StatusOK, &addMAResp) + require.Nil(t, addMAResp.Err) + + resp = listSoftwareTitlesResponse{} + s.DoJSON( + "GET", "/api/latest/fleet/software/titles", + listSoftwareTitlesRequest{}, + http.StatusOK, &resp, + "per_page", "1", + "order_key", "name", + "order_direction", "desc", + "available_for_install", "true", + "team_id", "0", + ) + + mapp, err = s.ds.GetMaintainedAppByID(ctx, 3) + require.NoError(t, err) + require.Equal(t, 1, resp.Count) + title = resp.SoftwareTitles[0] + require.NotNil(t, title.BundleIdentifier) + require.Equal(t, ptr.String(mapp.BundleIdentifier), title.BundleIdentifier) + require.Equal(t, mapp.Version, title.SoftwarePackage.Version) + require.Equal(t, "installer.zip", title.SoftwarePackage.Name) + + i, err = s.ds.GetSoftwareInstallerMetadataByID(context.Background(), getSoftwareInstallerIDByMAppID(3)) + require.NoError(t, err) + require.Equal(t, ptr.Uint(3), i.FleetLibraryAppID) + require.Equal(t, mapp.SHA256, i.StorageID) + require.Equal(t, "darwin", i.Platform) + require.NotEmpty(t, i.InstallScriptContentID) + require.Equal(t, req.PreInstallQuery, i.PreInstallQuery) + install, err = s.ds.GetAnyScriptContents(ctx, i.InstallScriptContentID) + require.NoError(t, err) + require.Equal(t, req.InstallScript, string(install)) + require.NotNil(t, i.PostInstallScriptContentID) + postinstall, err = s.ds.GetAnyScriptContents(ctx, *i.PostInstallScriptContentID) + require.NoError(t, err) + require.Equal(t, req.PostInstallScript, string(postinstall)) +} diff --git a/server/service/maintained_apps.go b/server/service/maintained_apps.go new file mode 100644 index 000000000000..ef89ca0ac11d --- /dev/null +++ b/server/service/maintained_apps.go @@ -0,0 +1,39 @@ +package service + +import ( + "context" + + "github.com/fleetdm/fleet/v4/server/fleet" +) + +type addFleetMaintainedAppRequest struct { + TeamID *uint `json:"team_id"` + AppID uint `json:"fleet_maintained_app_id"` + InstallScript string `json:"install_script"` + PreInstallQuery string `json:"pre_install_query"` + PostInstallScript string `json:"post_install_script"` + SelfService bool `json:"self_service"` +} + +type addFleetMaintainedAppResponse struct { + Err error `json:"error,omitempty"` +} + +func (r addFleetMaintainedAppResponse) error() error { return r.Err } + +func addFleetMaintainedAppEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) { + req := request.(*addFleetMaintainedAppRequest) + err := svc.AddFleetMaintainedApp(ctx, req.TeamID, req.AppID, req.InstallScript, req.PreInstallQuery, req.PostInstallScript, req.SelfService) + if err != nil { + return &addFleetMaintainedAppResponse{Err: err}, nil + } + return &addFleetMaintainedAppResponse{}, nil +} + +func (svc *Service) AddFleetMaintainedApp(ctx context.Context, teamID *uint, appID uint, installScript, preInstallQuery, postInstallScript string, selfService bool) error { + // skipauth: No authorization check needed due to implementation returning + // only license error. + svc.authz.SkipAuthorization(ctx) + + return fleet.ErrMissingLicense +} From b45c8b02c95d62e2d09a01a46aae97da5a1382f1 Mon Sep 17 00:00:00 2001 From: Dante Catalfamo <43040593+dantecatalfamo@users.noreply.github.com> Date: Thu, 19 Sep 2024 10:43:40 -0400 Subject: [PATCH 009/385] List Available Fleet Managed Apps (#22059) #21777 --- changes/21777-list-fleet-manated-apps | 1 + ee/server/service/software.go | 16 ++ server/datastore/mysql/maintained_apps.go | 48 ++++ .../datastore/mysql/maintained_apps_test.go | 244 +++++++++++++++++- server/fleet/datastore.go | 4 + server/fleet/service.go | 1 + server/mdm/maintainedapps/testing_utils.go | 4 +- server/mock/datastore_mock.go | 12 + server/service/handler.go | 1 + server/service/integration_enterprise_test.go | 46 +++- server/service/software.go | 33 +++ 11 files changed, 402 insertions(+), 8 deletions(-) create mode 100644 changes/21777-list-fleet-manated-apps diff --git a/changes/21777-list-fleet-manated-apps b/changes/21777-list-fleet-manated-apps new file mode 100644 index 000000000000..7da57b618796 --- /dev/null +++ b/changes/21777-list-fleet-manated-apps @@ -0,0 +1 @@ +- Add API endpoint to list team available Fleet-maintained apps diff --git a/ee/server/service/software.go b/ee/server/service/software.go index 3b66ae485a30..4bc973af1ee2 100644 --- a/ee/server/service/software.go +++ b/ee/server/service/software.go @@ -3,6 +3,7 @@ package service import ( "context" + "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" "github.com/fleetdm/fleet/v4/server/fleet" ) @@ -16,3 +17,18 @@ func (svc *Service) SoftwareByID(ctx context.Context, id uint, teamID *uint, _ b // reuse SoftwareByID, but include cve scores in premium version return svc.Service.SoftwareByID(ctx, id, teamID, true) } + +func (svc *Service) ListFleetMaintainedApps(ctx context.Context, teamID uint, opts fleet.ListOptions) ([]fleet.MaintainedApp, *fleet.PaginationMetadata, error) { + if err := svc.authz.Authorize(ctx, &fleet.AuthzSoftwareInventory{ + TeamID: &teamID, + }, fleet.ActionRead); err != nil { + return nil, nil, err + } + + avail, meta, err := svc.ds.ListAvailableFleetMaintainedApps(ctx, teamID, opts) + if err != nil { + return nil, nil, ctxerr.Wrap(ctx, err, "listing available fleet managed apps") + } + + return avail, meta, nil +} diff --git a/server/datastore/mysql/maintained_apps.go b/server/datastore/mysql/maintained_apps.go index 45f97ee5a21e..cb849bf216fd 100644 --- a/server/datastore/mysql/maintained_apps.go +++ b/server/datastore/mysql/maintained_apps.go @@ -95,3 +95,51 @@ WHERE return &app, nil } + +func (ds *Datastore) ListAvailableFleetMaintainedApps(ctx context.Context, teamID uint, opt fleet.ListOptions) ([]fleet.MaintainedApp, *fleet.PaginationMetadata, error) { + stmt := ` +SELECT + fla.id, + fla.name, + fla.version, + fla.platform +FROM + fleet_library_apps fla +WHERE NOT EXISTS ( + SELECT + 1 + FROM + software_titles st + LEFT JOIN + software_installers si + ON si.title_id = st.id + LEFT JOIN + vpp_apps va + ON va.title_id = st.id + LEFT JOIN + vpp_apps_teams vat + ON vat.adam_id = va.adam_id + WHERE + st.bundle_identifier = fla.bundle_identifier + AND ( + (si.platform = fla.platform AND si.team_id = ?) + OR + (va.platform = fla.platform AND vat.team_id = ?) + ) +)` + + stmtPaged, args := appendListOptionsWithCursorToSQL(stmt, []any{teamID, teamID}, &opt) + + var avail []fleet.MaintainedApp + if err := sqlx.SelectContext(ctx, ds.reader(ctx), &avail, stmtPaged, args...); err != nil { + return nil, nil, ctxerr.Wrap(ctx, err, "selecting available fleet managed apps") + } + + meta := &fleet.PaginationMetadata{HasPreviousResults: opt.Page > 0} + if len(avail) > int(opt.PerPage) { + meta.HasNextResults = true + avail = avail[:len(avail)-1] + } + + return avail, meta, nil +} diff --git a/server/datastore/mysql/maintained_apps_test.go b/server/datastore/mysql/maintained_apps_test.go index 4e99c1d2c357..9eb6a6fa0f53 100644 --- a/server/datastore/mysql/maintained_apps_test.go +++ b/server/datastore/mysql/maintained_apps_test.go @@ -7,6 +7,7 @@ import ( "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/mdm/maintainedapps" + "github.com/fleetdm/fleet/v4/server/test" "github.com/go-kit/kit/log" "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" @@ -21,6 +22,7 @@ func TestMaintainedApps(t *testing.T) { }{ {"UpsertMaintainedApps", testUpsertMaintainedApps}, {"IngestWithBrew", testIngestWithBrew}, + {"ListAvailableApps", testListAvailableApps}, {"GetMaintainedAppByID", testGetMaintainedAppByID}, } @@ -35,8 +37,8 @@ func TestMaintainedApps(t *testing.T) { func testUpsertMaintainedApps(t *testing.T, ds *Datastore) { ctx := context.Background() - listSavedApps := func() []*fleet.MaintainedApp { - var apps []*fleet.MaintainedApp + listSavedApps := func() []fleet.MaintainedApp { + var apps []fleet.MaintainedApp ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { return sqlx.SelectContext(ctx, q, &apps, "SELECT name, version, platform FROM fleet_library_apps ORDER BY token") }) @@ -61,9 +63,9 @@ func testUpsertMaintainedApps(t *testing.T, ds *Datastore) { require.NoError(t, err) // change the expected app data for figma - for _, app := range expectedApps { - if app.Name == "Figma" { - app.Version = "999.9.9" + for idx := range expectedApps { + if expectedApps[idx].Name == "Figma" { + expectedApps[idx].Version = "999.9.9" break } } @@ -87,6 +89,238 @@ func testIngestWithBrew(t *testing.T, ds *Datastore) { require.ElementsMatch(t, expectedTokens, actualTokens) } +func testListAvailableApps(t *testing.T, ds *Datastore) { + ctx := context.Background() + + user := test.NewUser(t, ds, "Zaphod Beeblebrox", "zaphod@example.com", true) + team1, err := ds.NewTeam(ctx, &fleet.Team{Name: "Team 1"}) + require.NoError(t, err) + team2, err := ds.NewTeam(ctx, &fleet.Team{Name: "Team 2"}) + require.NoError(t, err) + + maintained1, err := ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ + Name: "Maintained1", + Token: "maintained1", + Version: "1.0.0", + Platform: fleet.MacOSPlatform, + InstallerURL: "http://example.com/main1", + SHA256: "DEADBEEF", + BundleIdentifier: "fleet.maintained1", + InstallScript: "echo installed", + UninstallScript: "echo uninstalled", + }) + require.NoError(t, err) + maintained2, err := ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ + Name: "Maintained2", + Token: "maintained2", + Version: "1.0.0", + Platform: fleet.MacOSPlatform, + InstallerURL: "http://example.com/main1", + SHA256: "DEADBEEF", + BundleIdentifier: "fleet.maintained2", + InstallScript: "echo installed", + UninstallScript: "echo uninstalled", + }) + require.NoError(t, err) + maintained3, err := ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ + Name: "Maintained3", + Token: "maintained3", + Version: "1.0.0", + Platform: fleet.MacOSPlatform, + InstallerURL: "http://example.com/main1", + SHA256: "DEADBEEF", + BundleIdentifier: "fleet.maintained3", + InstallScript: "echo installed", + UninstallScript: "echo uninstalled", + }) + require.NoError(t, err) + + expectedApps := []fleet.MaintainedApp{ + { + ID: maintained1.ID, + Name: maintained1.Name, + Version: maintained1.Version, + Platform: maintained1.Platform, + }, + { + ID: maintained2.ID, + Name: maintained2.Name, + Version: maintained2.Version, + Platform: maintained2.Platform, + }, + { + ID: maintained3.ID, + Name: maintained3.Name, + Version: maintained3.Version, + Platform: maintained3.Platform, + }, + } + + // Testing pagination + apps, meta, err := ds.ListAvailableFleetMaintainedApps(ctx, team1.ID, fleet.ListOptions{IncludeMetadata: true}) + require.NoError(t, err) + require.Len(t, apps, 3) + require.Equal(t, expectedApps, apps) + require.False(t, meta.HasNextResults) + + apps, meta, err = ds.ListAvailableFleetMaintainedApps(ctx, team1.ID, fleet.ListOptions{PerPage: 1, IncludeMetadata: true}) + require.NoError(t, err) + require.Len(t, apps, 1) + require.Equal(t, expectedApps[:1], apps) + require.True(t, meta.HasNextResults) + + apps, meta, err = ds.ListAvailableFleetMaintainedApps(ctx, team1.ID, fleet.ListOptions{PerPage: 1, Page: 1, IncludeMetadata: true}) + require.NoError(t, err) + require.Len(t, apps, 1) + require.Equal(t, expectedApps[1:2], apps) + require.True(t, meta.HasNextResults) + require.True(t, meta.HasPreviousResults) + + apps, meta, err = ds.ListAvailableFleetMaintainedApps(ctx, team1.ID, fleet.ListOptions{PerPage: 1, Page: 2, IncludeMetadata: true}) + require.NoError(t, err) + require.Len(t, apps, 1) + require.Equal(t, expectedApps[2:3], apps) + require.False(t, meta.HasNextResults) + require.True(t, meta.HasPreviousResults) + + // + // Test excluding results for existing apps (installers) + + /// Irrelevant package + _, err = ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{ + Title: "Irrelevant Software", + TeamID: &team1.ID, + InstallScript: "nothing", + Filename: "foo.pkg", + UserID: user.ID, + Platform: string(fleet.MacOSPlatform), + BundleIdentifier: "irrelevant_1", + }) + require.NoError(t, err) + + apps, meta, err = ds.ListAvailableFleetMaintainedApps(ctx, team1.ID, fleet.ListOptions{IncludeMetadata: true}) + require.NoError(t, err) + require.Len(t, apps, 3) + require.Equal(t, expectedApps, apps) + + /// Correct package on a different team + _, err = ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{ + Title: "Maintained1", + TeamID: &team2.ID, + InstallScript: "nothing", + Filename: "foo.pkg", + UserID: user.ID, + Platform: string(fleet.MacOSPlatform), + BundleIdentifier: "fleet.maintained1", + }) + require.NoError(t, err) + + apps, meta, err = ds.ListAvailableFleetMaintainedApps(ctx, team1.ID, fleet.ListOptions{IncludeMetadata: true}) + require.NoError(t, err) + require.Len(t, apps, 3) + require.Equal(t, expectedApps, apps) + + /// Correct package on the right team with the wrong platform + _, err = ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{ + Title: "Maintained1", + TeamID: &team1.ID, + InstallScript: "nothing", + Filename: "foo.pkg", + UserID: user.ID, + Platform: string(fleet.IOSPlatform), + BundleIdentifier: "fleet.maintained1", + }) + require.NoError(t, err) + + apps, meta, err = ds.ListAvailableFleetMaintainedApps(ctx, team1.ID, fleet.ListOptions{IncludeMetadata: true}) + require.NoError(t, err) + require.Len(t, apps, 3) + require.Equal(t, expectedApps, apps) + + /// Correct team and platform + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { + _, err := q.ExecContext(ctx, "UPDATE software_installers SET platform = ? WHERE platform = ?", fleet.MacOSPlatform, fleet.IOSPlatform) + return err + }) + + apps, meta, err = ds.ListAvailableFleetMaintainedApps(ctx, team1.ID, fleet.ListOptions{IncludeMetadata: true}) + require.NoError(t, err) + require.Len(t, apps, 2) + require.Equal(t, expectedApps[1:], apps) + + // + // Test excluding results for existing apps (VPP) + + test.CreateInsertGlobalVPPToken(t, ds) + + // irrelevant vpp app + vppIrrelevant := &fleet.VPPApp{ + Name: "irrelevant_app", + VPPAppTeam: fleet.VPPAppTeam{ + VPPAppID: fleet.VPPAppID{ + AdamID: "1", + Platform: fleet.MacOSPlatform, + }, + }, + BundleIdentifier: "irrelevant_2", + } + _, err = ds.InsertVPPAppWithTeam(ctx, vppIrrelevant, &team1.ID) + require.NoError(t, err) + + apps, meta, err = ds.ListAvailableFleetMaintainedApps(ctx, team1.ID, fleet.ListOptions{IncludeMetadata: true}) + require.NoError(t, err) + require.Len(t, apps, 2) + require.Equal(t, expectedApps[1:], apps) + + // right vpp app, wrong team + vppMaintained2 := &fleet.VPPApp{ + Name: "Maintained 2", + VPPAppTeam: fleet.VPPAppTeam{ + VPPAppID: fleet.VPPAppID{ + AdamID: "2", + Platform: fleet.MacOSPlatform, + }, + }, + BundleIdentifier: "fleet.maintained2", + } + _, err = ds.InsertVPPAppWithTeam(ctx, vppMaintained2, &team2.ID) + require.NoError(t, err) + + apps, meta, err = ds.ListAvailableFleetMaintainedApps(ctx, team1.ID, fleet.ListOptions{IncludeMetadata: true}) + require.NoError(t, err) + require.Len(t, apps, 2) + require.Equal(t, expectedApps[1:], apps) + + // right vpp app, right team + _, err = ds.InsertVPPAppWithTeam(ctx, vppMaintained2, &team1.ID) + require.NoError(t, err) + + apps, meta, err = ds.ListAvailableFleetMaintainedApps(ctx, team1.ID, fleet.ListOptions{IncludeMetadata: true}) + require.NoError(t, err) + require.Len(t, apps, 1) + require.Equal(t, expectedApps[2:], apps) + + // right app, right team, wrong platform + vppMaintained3 := &fleet.VPPApp{ + Name: "Maintained 3", + VPPAppTeam: fleet.VPPAppTeam{ + VPPAppID: fleet.VPPAppID{ + AdamID: "3", + Platform: fleet.IOSPlatform, + }, + }, + BundleIdentifier: "fleet.maintained3", + } + + _, err = ds.InsertVPPAppWithTeam(ctx, vppMaintained3, &team1.ID) + require.NoError(t, err) + + apps, meta, err = ds.ListAvailableFleetMaintainedApps(ctx, team1.ID, fleet.ListOptions{IncludeMetadata: true}) + require.NoError(t, err) + require.Len(t, apps, 1) + require.Equal(t, expectedApps[2:], apps) +} + func testGetMaintainedAppByID(t *testing.T, ds *Datastore) { ctx := context.Background() diff --git a/server/fleet/datastore.go b/server/fleet/datastore.go index 40f34363d487..0500471dcda9 100644 --- a/server/fleet/datastore.go +++ b/server/fleet/datastore.go @@ -602,6 +602,10 @@ type Datastore interface { // the given team. UploadedSoftwareExists(ctx context.Context, bundleIdentifier string, teamID *uint) (bool, error) + /////////////////////////////////////////////////////////////////////////////// + // Fleet Managed Apps + ListAvailableFleetMaintainedApps(ctx context.Context, teamID uint, opt ListOptions) ([]MaintainedApp, *PaginationMetadata, error) + /////////////////////////////////////////////////////////////////////////////// // OperatingSystemsStore diff --git a/server/fleet/service.go b/server/fleet/service.go index 134025d5c1a4..07c50553ac7e 100644 --- a/server/fleet/service.go +++ b/server/fleet/service.go @@ -1119,6 +1119,7 @@ type Service interface { // AddFleetMaintainedApp adds a Fleet-maintained app to the given team. AddFleetMaintainedApp(ctx context.Context, teamID *uint, appID uint, installScript, preInstallQuery, postInstallScript string, selfService bool) error + ListFleetMaintainedApps(ctx context.Context, teamID uint, opts ListOptions) ([]MaintainedApp, *PaginationMetadata, error) // ///////////////////////////////////////////////////////////////////////////// // Maintenance windows diff --git a/server/mdm/maintainedapps/testing_utils.go b/server/mdm/maintainedapps/testing_utils.go index 9c08af04b1a7..3697acfd2891 100644 --- a/server/mdm/maintainedapps/testing_utils.go +++ b/server/mdm/maintainedapps/testing_utils.go @@ -21,7 +21,7 @@ import ( // It returns the expected results of the ingestion as a slice of // fleet.MaintainedApps with only a few fields filled - the result of // unmarshaling the testdata/expected_apps.json file. -func IngestMaintainedApps(t *testing.T, ds fleet.Datastore) []*fleet.MaintainedApp { +func IngestMaintainedApps(t *testing.T, ds fleet.Datastore) []fleet.MaintainedApp { _, filename, _, _ := runtime.Caller(0) base := filepath.Dir(filename) testdataDir := filepath.Join(base, "testdata") @@ -50,7 +50,7 @@ func IngestMaintainedApps(t *testing.T, ds fleet.Datastore) []*fleet.MaintainedA err := Refresh(context.Background(), ds, log.NewNopLogger()) require.NoError(t, err) - var expected []*fleet.MaintainedApp + var expected []fleet.MaintainedApp b, err := os.ReadFile(filepath.Join(testdataDir, "expected_apps.json")) require.NoError(t, err) err = json.Unmarshal(b, &expected) diff --git a/server/mock/datastore_mock.go b/server/mock/datastore_mock.go index c36698993e4c..bf204d35ac05 100644 --- a/server/mock/datastore_mock.go +++ b/server/mock/datastore_mock.go @@ -442,6 +442,8 @@ type SetHostSoftwareInstallResultFunc func(ctx context.Context, result *fleet.Ho type UploadedSoftwareExistsFunc func(ctx context.Context, bundleIdentifier string, teamID *uint) (bool, error) +type ListAvailableFleetMaintainedAppsFunc func(ctx context.Context, teamID uint, opt fleet.ListOptions) ([]fleet.MaintainedApp, *fleet.PaginationMetadata, error) + type GetHostOperatingSystemFunc func(ctx context.Context, hostID uint) (*fleet.OperatingSystem, error) type ListOperatingSystemsFunc func(ctx context.Context) ([]fleet.OperatingSystem, error) @@ -1726,6 +1728,9 @@ type DataStore struct { UploadedSoftwareExistsFunc UploadedSoftwareExistsFunc UploadedSoftwareExistsFuncInvoked bool + ListAvailableFleetMaintainedAppsFunc ListAvailableFleetMaintainedAppsFunc + ListAvailableFleetMaintainedAppsFuncInvoked bool + GetHostOperatingSystemFunc GetHostOperatingSystemFunc GetHostOperatingSystemFuncInvoked bool @@ -4181,6 +4186,13 @@ func (s *DataStore) UploadedSoftwareExists(ctx context.Context, bundleIdentifier return s.UploadedSoftwareExistsFunc(ctx, bundleIdentifier, teamID) } +func (s *DataStore) ListAvailableFleetMaintainedApps(ctx context.Context, teamID uint, opt fleet.ListOptions) ([]fleet.MaintainedApp, *fleet.PaginationMetadata, error) { + s.mu.Lock() + s.ListAvailableFleetMaintainedAppsFuncInvoked = true + s.mu.Unlock() + return s.ListAvailableFleetMaintainedAppsFunc(ctx, teamID, opt) +} + func (s *DataStore) GetHostOperatingSystem(ctx context.Context, hostID uint) (*fleet.OperatingSystem, error) { s.mu.Lock() s.GetHostOperatingSystemFuncInvoked = true diff --git a/server/service/handler.go b/server/service/handler.go index fd8bc0dccf1a..2460f2e77c6f 100644 --- a/server/service/handler.go +++ b/server/service/handler.go @@ -389,6 +389,7 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC // Fleet-maintained apps ue.POST("/api/_version_/fleet/software/fleet_maintained_apps", addFleetMaintainedAppEndpoint, addFleetMaintainedAppRequest{}) + ue.GET("/api/_version_/fleet/software/fleet_maintained_apps", listFleetMaintainedApps, listFleetMaintainedAppsRequest{}) // Vulnerabilities ue.GET("/api/_version_/fleet/vulnerabilities", listVulnerabilitiesEndpoint, listVulnerabilitiesRequest{}) diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go index 393d200fb674..e534117dd5ac 100644 --- a/server/service/integration_enterprise_test.go +++ b/server/service/integration_enterprise_test.go @@ -2,6 +2,7 @@ package service import ( "bytes" + "cmp" "context" "crypto/sha256" "database/sql" @@ -17,6 +18,7 @@ import ( "os" "path/filepath" "reflect" + "slices" "sort" "strconv" "strings" @@ -14257,7 +14259,7 @@ func (s *integrationEnterpriseTestSuite) TestMaintainedApps() { s.Do("POST", "/api/latest/fleet/software/fleet_maintained_apps", &addFleetMaintainedAppRequest{AppID: 1}, http.StatusNotFound) // Insert the list of maintained apps - maintainedapps.IngestMaintainedApps(t, s.ds) + expectedApps := maintainedapps.IngestMaintainedApps(t, s.ds) // Edit DB to spoof URLs and SHA256 values so we don't have to actually download the installers mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error { @@ -14276,6 +14278,43 @@ func (s *integrationEnterpriseTestSuite) TestMaintainedApps() { s.DoJSON("POST", "/api/latest/fleet/teams", &createTeamRequest{TeamPayload: fleet.TeamPayload{Name: ptr.String("Team 1")}}, http.StatusOK, &newTeamResp) team := newTeamResp.Team + // Check apps returned + var listMAResp listFleetMaintainedAppsResponse + s.DoJSON(http.MethodGet, "/api/latest/fleet/software/fleet_maintained_apps", listFleetMaintainedAppsRequest{}, http.StatusOK, &listMAResp, "team_id", strconv.Itoa(int(team.ID))) + require.Nil(t, listMAResp.Err) + require.False(t, listMAResp.Meta.HasPreviousResults) + require.False(t, listMAResp.Meta.HasNextResults) + require.Len(t, listMAResp.FleetMaintainedApps, len(expectedApps)) + var listAppsNoID []fleet.MaintainedApp + for _, app := range listMAResp.FleetMaintainedApps { + app.ID = 0 + listAppsNoID = append(listAppsNoID, app) + } + slices.SortFunc(listAppsNoID, func(a, b fleet.MaintainedApp) int { + return cmp.Compare(a.Name, b.Name) + }) + slices.SortFunc(expectedApps, func(a, b fleet.MaintainedApp) int { + return cmp.Compare(a.Name, b.Name) + }) + require.Equal(t, expectedApps, listAppsNoID) + + var listMAResp2 listFleetMaintainedAppsResponse + s.DoJSON( + http.MethodGet, + "/api/latest/fleet/software/fleet_maintained_apps", + listFleetMaintainedAppsRequest{}, + http.StatusOK, + &listMAResp2, + "team_id", strconv.Itoa(int(team.ID)), + "per_page", "2", + "page", "2", + ) + require.Nil(t, listMAResp2.Err) + require.True(t, listMAResp2.Meta.HasPreviousResults) + require.True(t, listMAResp2.Meta.HasNextResults) + require.Len(t, listMAResp2.FleetMaintainedApps, 2) + require.Equal(t, listMAResp.FleetMaintainedApps[4:6], listMAResp2.FleetMaintainedApps) + // Add an ingested app to the team var addMAResp addFleetMaintainedAppResponse req := &addFleetMaintainedAppRequest{ @@ -14289,6 +14328,11 @@ func (s *integrationEnterpriseTestSuite) TestMaintainedApps() { s.DoJSON("POST", "/api/latest/fleet/software/fleet_maintained_apps", req, http.StatusOK, &addMAResp) require.Nil(t, addMAResp.Err) + s.DoJSON(http.MethodGet, "/api/latest/fleet/software/fleet_maintained_apps", listFleetMaintainedAppsRequest{}, http.StatusOK, &listMAResp, "team_id", strconv.Itoa(int(team.ID))) + require.Nil(t, listMAResp.Err) + require.False(t, listMAResp.Meta.HasPreviousResults) + require.Len(t, listMAResp.FleetMaintainedApps, len(expectedApps)-1) + // Validate software installer fields mapp, err := s.ds.GetMaintainedAppByID(ctx, 1) require.NoError(t, err) diff --git a/server/service/software.go b/server/service/software.go index e589ac7fe679..24a75d40af83 100644 --- a/server/service/software.go +++ b/server/service/software.go @@ -248,3 +248,36 @@ func (svc Service) CountSoftware(ctx context.Context, opt fleet.SoftwareListOpti return svc.ds.CountSoftware(ctx, opt) } + +// Fleet Maintained Apps +type listFleetMaintainedAppsRequest struct { + fleet.ListOptions + TeamID uint `query:"team_id"` +} + +type listFleetMaintainedAppsResponse struct { + FleetMaintainedApps []fleet.MaintainedApp `json:"fleet_maintained_apps"` + Meta *fleet.PaginationMetadata `json:"meta"` + Err error `json:"error,omitempty"` +} + +func (r listFleetMaintainedAppsResponse) error() error { return r.Err } + +func listFleetMaintainedApps(ctx context.Context, request any, svc fleet.Service) (errorer, error) { + req := request.(*listFleetMaintainedAppsRequest) + + req.IncludeMetadata = true + + apps, meta, err := svc.ListFleetMaintainedApps(ctx, req.TeamID, req.ListOptions) + if err != nil { + return listFleetMaintainedAppsResponse{Err: err}, nil + } + + return listFleetMaintainedAppsResponse{FleetMaintainedApps: apps, Meta: meta}, nil +} + +func (svc *Service) ListFleetMaintainedApps(ctx context.Context, teamID uint, opts fleet.ListOptions) ([]fleet.MaintainedApp, *fleet.PaginationMetadata, error) { + svc.authz.SkipAuthorization(ctx) + + return nil, nil, fleet.ErrMissingLicense +} From 7f39281937689a1e5f9063fac26d120d68386337 Mon Sep 17 00:00:00 2001 From: Jahziel Villasana-Espinoza Date: Thu, 19 Sep 2024 15:42:17 -0400 Subject: [PATCH 010/385] feat: use a 15 minute timeout for adding a maintained app (#22247) > Related issue: #22239 # 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 --- ee/server/service/maintained_apps.go | 11 +++++++++- server/mdm/maintainedapps/installers.go | 8 ++++---- server/service/integration_enterprise_test.go | 20 +++++++++++++++---- server/service/maintained_apps.go | 8 ++++++++ 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/ee/server/service/maintained_apps.go b/ee/server/service/maintained_apps.go index 34497b6e9091..c8749999882d 100644 --- a/ee/server/service/maintained_apps.go +++ b/ee/server/service/maintained_apps.go @@ -5,7 +5,10 @@ import ( "context" "crypto/sha256" "encoding/hex" + "os" + "time" + "github.com/fleetdm/fleet/v4/pkg/fleethttp" "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" "github.com/fleetdm/fleet/v4/server/contexts/viewer" "github.com/fleetdm/fleet/v4/server/fleet" @@ -28,7 +31,13 @@ func (svc *Service) AddFleetMaintainedApp(ctx context.Context, teamID *uint, app } // Download installer from the URL - installerBytes, filename, err := maintainedapps.DownloadInstaller(ctx, app.InstallerURL) + timeout := maintainedapps.InstallerTimeout + if v := os.Getenv("FLEET_DEV_MAINTAINED_APPS_INSTALLER_TIMEOUT"); v != "" { + timeout, _ = time.ParseDuration(v) + } + + client := fleethttp.NewClient(fleethttp.WithTimeout(timeout)) + installerBytes, filename, err := maintainedapps.DownloadInstaller(ctx, app.InstallerURL, client) if err != nil { return ctxerr.Wrap(ctx, err, "downloading app installer") } diff --git a/server/mdm/maintainedapps/installers.go b/server/mdm/maintainedapps/installers.go index 35f5ef4ffc4a..e9b6e145ce04 100644 --- a/server/mdm/maintainedapps/installers.go +++ b/server/mdm/maintainedapps/installers.go @@ -10,13 +10,15 @@ import ( "path" "time" - "github.com/fleetdm/fleet/v4/pkg/fleethttp" "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" "github.com/fleetdm/fleet/v4/server/fleet" ) +// InstallerTimeout is the timeout duration for downloading and adding a maintained app. +const InstallerTimeout = 15 * time.Minute + // DownloadInstaller downloads the maintained app installer located at the given URL. -func DownloadInstaller(ctx context.Context, installerURL string) ([]byte, string, error) { +func DownloadInstaller(ctx context.Context, installerURL string, client *http.Client) ([]byte, string, error) { // validate the URL before doing the request _, err := url.ParseRequestURI(installerURL) if err != nil { @@ -26,8 +28,6 @@ func DownloadInstaller(ctx context.Context, installerURL string) ([]byte, string ) } - client := fleethttp.NewClient(fleethttp.WithTimeout(30 * time.Second)) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, installerURL, nil) if err != nil { return nil, "", ctxerr.Wrapf(ctx, err, "creating request for URL %s", installerURL) diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go index e534117dd5ac..6e4f32e89877 100644 --- a/server/service/integration_enterprise_test.go +++ b/server/service/integration_enterprise_test.go @@ -14235,11 +14235,15 @@ func (s *integrationEnterpriseTestSuite) TestMaintainedApps() { ctx := context.Background() installerBytes := []byte("abc") + // Mock server to serve the "installers" srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/badinstaller": _, _ = w.Write([]byte("badinstaller")) + case "/timeout": + time.Sleep(3 * time.Second) + _, _ = w.Write([]byte("timeout")) default: _, _ = w.Write(installerBytes) } @@ -14270,6 +14274,8 @@ func (s *integrationEnterpriseTestSuite) TestMaintainedApps() { _, err = q.ExecContext(ctx, "UPDATE fleet_library_apps SET sha256 = ?, installer_url = ?", spoofedSHA, srv.URL+"/installer.zip") require.NoError(t, err) _, err = q.ExecContext(ctx, "UPDATE fleet_library_apps SET installer_url = ? WHERE id = 2", srv.URL+"/badinstaller") + require.NoError(t, err) + _, err = q.ExecContext(ctx, "UPDATE fleet_library_apps SET installer_url = ? WHERE id = 3", srv.URL+"/timeout") return err }) @@ -14383,10 +14389,16 @@ func (s *integrationEnterpriseTestSuite) TestMaintainedApps() { r := s.Do("POST", "/api/latest/fleet/software/fleet_maintained_apps", &addFleetMaintainedAppRequest{AppID: 2}, http.StatusInternalServerError) require.Contains(t, extractServerErrorText(r.Body), "mismatch in maintained app SHA256 hash") + // Should timeout + os.Setenv("FLEET_DEV_MAINTAINED_APPS_INSTALLER_TIMEOUT", "1s") + r = s.Do("POST", "/api/latest/fleet/software/fleet_maintained_apps", &addFleetMaintainedAppRequest{AppID: 3}, http.StatusGatewayTimeout) + os.Unsetenv("FLEET_DEV_MAINTAINED_APPS_INSTALLER_TIMEOUT") + require.Contains(t, extractServerErrorText(r.Body), "Couldn't upload. Request timeout. Please make sure your server and load balancer timeout is long enough.") + // Add a maintained app to no team req = &addFleetMaintainedAppRequest{ - AppID: 3, + AppID: 4, SelfService: true, PreInstallQuery: "SELECT 1", InstallScript: "echo foo", @@ -14409,7 +14421,7 @@ func (s *integrationEnterpriseTestSuite) TestMaintainedApps() { "team_id", "0", ) - mapp, err = s.ds.GetMaintainedAppByID(ctx, 3) + mapp, err = s.ds.GetMaintainedAppByID(ctx, 4) require.NoError(t, err) require.Equal(t, 1, resp.Count) title = resp.SoftwareTitles[0] @@ -14418,9 +14430,9 @@ func (s *integrationEnterpriseTestSuite) TestMaintainedApps() { require.Equal(t, mapp.Version, title.SoftwarePackage.Version) require.Equal(t, "installer.zip", title.SoftwarePackage.Name) - i, err = s.ds.GetSoftwareInstallerMetadataByID(context.Background(), getSoftwareInstallerIDByMAppID(3)) + i, err = s.ds.GetSoftwareInstallerMetadataByID(context.Background(), getSoftwareInstallerIDByMAppID(4)) require.NoError(t, err) - require.Equal(t, ptr.Uint(3), i.FleetLibraryAppID) + require.Equal(t, ptr.Uint(4), i.FleetLibraryAppID) require.Equal(t, mapp.SHA256, i.StorageID) require.Equal(t, "darwin", i.Platform) require.NotEmpty(t, i.InstallScriptContentID) diff --git a/server/service/maintained_apps.go b/server/service/maintained_apps.go index ef89ca0ac11d..ed785033bc2d 100644 --- a/server/service/maintained_apps.go +++ b/server/service/maintained_apps.go @@ -2,8 +2,10 @@ package service import ( "context" + "errors" "github.com/fleetdm/fleet/v4/server/fleet" + "github.com/fleetdm/fleet/v4/server/mdm/maintainedapps" ) type addFleetMaintainedAppRequest struct { @@ -23,8 +25,14 @@ func (r addFleetMaintainedAppResponse) error() error { return r.Err } func addFleetMaintainedAppEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) { req := request.(*addFleetMaintainedAppRequest) + ctx, cancel := context.WithTimeout(ctx, maintainedapps.InstallerTimeout) + defer cancel() err := svc.AddFleetMaintainedApp(ctx, req.TeamID, req.AppID, req.InstallScript, req.PreInstallQuery, req.PostInstallScript, req.SelfService) if err != nil { + if errors.Is(err, context.DeadlineExceeded) { + err = fleet.NewGatewayTimeoutError("Couldn't upload. Request timeout. Please make sure your server and load balancer timeout is long enough.", err) + } + return &addFleetMaintainedAppResponse{Err: err}, nil } return &addFleetMaintainedAppResponse{}, nil From 1a02ff5cdef1dd9b9f3e6f291ea19030aff35fc1 Mon Sep 17 00:00:00 2001 From: Dante Catalfamo <43040593+dantecatalfamo@users.noreply.github.com> Date: Fri, 20 Sep 2024 10:42:43 -0400 Subject: [PATCH 011/385] Fleet maintained apps get app (#22241) #22234 --- changes/22234-fleet-maintained-apps-get-api | 1 + ee/server/service/maintained_apps.go | 30 +++++++++ ee/server/service/software.go | 16 ----- server/datastore/mysql/maintained_apps.go | 16 ++--- .../datastore/mysql/maintained_apps_test.go | 10 +-- server/fleet/datastore.go | 8 +-- server/fleet/service.go | 3 + server/service/handler.go | 1 + server/service/integration_enterprise_test.go | 9 +++ server/service/maintained_apps.go | 64 +++++++++++++++++++ server/service/software.go | 33 ---------- 11 files changed, 125 insertions(+), 66 deletions(-) create mode 100644 changes/22234-fleet-maintained-apps-get-api diff --git a/changes/22234-fleet-maintained-apps-get-api b/changes/22234-fleet-maintained-apps-get-api new file mode 100644 index 000000000000..837dde425f1e --- /dev/null +++ b/changes/22234-fleet-maintained-apps-get-api @@ -0,0 +1 @@ +- Add endpoint to retrieve details on fleet maintained app diff --git a/ee/server/service/maintained_apps.go b/ee/server/service/maintained_apps.go index c8749999882d..95e5e46ee395 100644 --- a/ee/server/service/maintained_apps.go +++ b/ee/server/service/maintained_apps.go @@ -110,3 +110,33 @@ func (svc *Service) AddFleetMaintainedApp(ctx context.Context, teamID *uint, app return nil } + +func (svc *Service) ListFleetMaintainedApps(ctx context.Context, teamID uint, opts fleet.ListOptions) ([]fleet.MaintainedApp, *fleet.PaginationMetadata, error) { + if err := svc.authz.Authorize(ctx, &fleet.SoftwareInstaller{ + TeamID: &teamID, + }, fleet.ActionRead); err != nil { + return nil, nil, err + } + + avail, meta, err := svc.ds.ListAvailableFleetMaintainedApps(ctx, teamID, opts) + if err != nil { + return nil, nil, ctxerr.Wrap(ctx, err, "listing available fleet managed apps") + } + + return avail, meta, nil +} + +func (svc *Service) GetFleetMaintainedApp(ctx context.Context, appID uint) (*fleet.MaintainedApp, error) { + if err := svc.authz.Authorize(ctx, &fleet.SoftwareInstaller{ + TeamID: nil, + }, fleet.ActionRead); err != nil { + return nil, err + } + + app, err := svc.ds.GetMaintainedAppByID(ctx, appID) + if err != nil { + return nil, ctxerr.Wrap(ctx, err, "get fleet maintained app") + } + + return app, nil +} diff --git a/ee/server/service/software.go b/ee/server/service/software.go index 4bc973af1ee2..3b66ae485a30 100644 --- a/ee/server/service/software.go +++ b/ee/server/service/software.go @@ -3,7 +3,6 @@ package service import ( "context" - "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" "github.com/fleetdm/fleet/v4/server/fleet" ) @@ -17,18 +16,3 @@ func (svc *Service) SoftwareByID(ctx context.Context, id uint, teamID *uint, _ b // reuse SoftwareByID, but include cve scores in premium version return svc.Service.SoftwareByID(ctx, id, teamID, true) } - -func (svc *Service) ListFleetMaintainedApps(ctx context.Context, teamID uint, opts fleet.ListOptions) ([]fleet.MaintainedApp, *fleet.PaginationMetadata, error) { - if err := svc.authz.Authorize(ctx, &fleet.AuthzSoftwareInventory{ - TeamID: &teamID, - }, fleet.ActionRead); err != nil { - return nil, nil, err - } - - avail, meta, err := svc.ds.ListAvailableFleetMaintainedApps(ctx, teamID, opts) - if err != nil { - return nil, nil, ctxerr.Wrap(ctx, err, "listing available fleet managed apps") - } - - return avail, meta, nil -} diff --git a/server/datastore/mysql/maintained_apps.go b/server/datastore/mysql/maintained_apps.go index cb849bf216fd..9668b7069866 100644 --- a/server/datastore/mysql/maintained_apps.go +++ b/server/datastore/mysql/maintained_apps.go @@ -68,14 +68,14 @@ func (ds *Datastore) GetMaintainedAppByID(ctx context.Context, appID uint) (*fle const stmt = ` SELECT fla.id, - fla.name, - fla.token, - fla.version, - fla.platform, - fla.installer_url, - fla.sha256, - fla.bundle_identifier, - sc1.contents AS install_script, + fla.name, + fla.token, + fla.version, + fla.platform, + fla.installer_url, + fla.sha256, + fla.bundle_identifier, + sc1.contents AS install_script, sc2.contents AS uninstall_script FROM fleet_library_apps fla JOIN script_contents sc1 ON sc1.id = fla.install_script_content_id diff --git a/server/datastore/mysql/maintained_apps_test.go b/server/datastore/mysql/maintained_apps_test.go index 9eb6a6fa0f53..5aa30c435c43 100644 --- a/server/datastore/mysql/maintained_apps_test.go +++ b/server/datastore/mysql/maintained_apps_test.go @@ -326,14 +326,14 @@ func testGetMaintainedAppByID(t *testing.T, ds *Datastore) { expApp, err := ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ Name: "foo", - Token: "foo", + Token: "token", Version: "1.0.0", Platform: "darwin", InstallerURL: "https://example.com/foo.zip", - SHA256: "abc", - BundleIdentifier: "abc", - InstallScript: "foo", - UninstallScript: "foo", + SHA256: "sha", + BundleIdentifier: "bundle", + InstallScript: "install", + UninstallScript: "uninstall", }) require.NoError(t, err) diff --git a/server/fleet/datastore.go b/server/fleet/datastore.go index 0500471dcda9..2bd14a17a46c 100644 --- a/server/fleet/datastore.go +++ b/server/fleet/datastore.go @@ -602,10 +602,6 @@ type Datastore interface { // the given team. UploadedSoftwareExists(ctx context.Context, bundleIdentifier string, teamID *uint) (bool, error) - /////////////////////////////////////////////////////////////////////////////// - // Fleet Managed Apps - ListAvailableFleetMaintainedApps(ctx context.Context, teamID uint, opt ListOptions) ([]MaintainedApp, *PaginationMetadata, error) - /////////////////////////////////////////////////////////////////////////////// // OperatingSystemsStore @@ -1734,6 +1730,10 @@ type Datastore interface { // Fleet-maintained apps // + // ListAvailableFleetMaintainedApps returns a list of + // Fleet-maintained apps available to a specific team + ListAvailableFleetMaintainedApps(ctx context.Context, teamID uint, opt ListOptions) ([]MaintainedApp, *PaginationMetadata, error) + // GetMaintainedAppByID gets a Fleet-maintained app by its ID. GetMaintainedAppByID(ctx context.Context, appID uint) (*MaintainedApp, error) diff --git a/server/fleet/service.go b/server/fleet/service.go index 07c50553ac7e..925f60b5b34c 100644 --- a/server/fleet/service.go +++ b/server/fleet/service.go @@ -1119,7 +1119,10 @@ type Service interface { // AddFleetMaintainedApp adds a Fleet-maintained app to the given team. AddFleetMaintainedApp(ctx context.Context, teamID *uint, appID uint, installScript, preInstallQuery, postInstallScript string, selfService bool) error + // ListFleetMaintainedApps lists Fleet-maintained apps available to a specific team ListFleetMaintainedApps(ctx context.Context, teamID uint, opts ListOptions) ([]MaintainedApp, *PaginationMetadata, error) + // GetFleetMaintainedApp returns a Fleet-maintained app by ID + GetFleetMaintainedApp(ctx context.Context, appID uint) (*MaintainedApp, error) // ///////////////////////////////////////////////////////////////////////////// // Maintenance windows diff --git a/server/service/handler.go b/server/service/handler.go index 2460f2e77c6f..a4132ff123f5 100644 --- a/server/service/handler.go +++ b/server/service/handler.go @@ -390,6 +390,7 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC // Fleet-maintained apps ue.POST("/api/_version_/fleet/software/fleet_maintained_apps", addFleetMaintainedAppEndpoint, addFleetMaintainedAppRequest{}) ue.GET("/api/_version_/fleet/software/fleet_maintained_apps", listFleetMaintainedApps, listFleetMaintainedAppsRequest{}) + ue.GET("/api/_version_/fleet/software/fleet_maintained_apps/{app_id}", getFleetMaintainedApp, getFleetMaintainedAppRequest{}) // Vulnerabilities ue.GET("/api/_version_/fleet/vulnerabilities", listVulnerabilitiesEndpoint, listVulnerabilitiesRequest{}) diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go index 6e4f32e89877..97ac5c021f7b 100644 --- a/server/service/integration_enterprise_test.go +++ b/server/service/integration_enterprise_test.go @@ -14321,6 +14321,15 @@ func (s *integrationEnterpriseTestSuite) TestMaintainedApps() { require.Len(t, listMAResp2.FleetMaintainedApps, 2) require.Equal(t, listMAResp.FleetMaintainedApps[4:6], listMAResp2.FleetMaintainedApps) + // Check individual app fetch + var getMAResp getFleetMaintainedAppResponse + s.DoJSON(http.MethodGet, fmt.Sprintf("/api/latest/fleet/software/fleet_maintained_apps/%d", listMAResp.FleetMaintainedApps[0].ID), getFleetMaintainedAppRequest{}, http.StatusOK, &getMAResp) + // TODO this will change when actual install scripts are created. + actualApp := listMAResp.FleetMaintainedApps[0] + actualApp.InstallScript = "install" + actualApp.UninstallScript = "uninstall" + require.Equal(t, actualApp, *getMAResp.FleetMaintainedApp) + // Add an ingested app to the team var addMAResp addFleetMaintainedAppResponse req := &addFleetMaintainedAppRequest{ diff --git a/server/service/maintained_apps.go b/server/service/maintained_apps.go index ed785033bc2d..e4d0309318e4 100644 --- a/server/service/maintained_apps.go +++ b/server/service/maintained_apps.go @@ -45,3 +45,67 @@ func (svc *Service) AddFleetMaintainedApp(ctx context.Context, teamID *uint, app return fleet.ErrMissingLicense } + +type listFleetMaintainedAppsRequest struct { + fleet.ListOptions + TeamID uint `query:"team_id"` +} + +type listFleetMaintainedAppsResponse struct { + FleetMaintainedApps []fleet.MaintainedApp `json:"fleet_maintained_apps"` + Meta *fleet.PaginationMetadata `json:"meta"` + Err error `json:"error,omitempty"` +} + +func (r listFleetMaintainedAppsResponse) error() error { return r.Err } + +func listFleetMaintainedApps(ctx context.Context, request any, svc fleet.Service) (errorer, error) { + req := request.(*listFleetMaintainedAppsRequest) + + req.IncludeMetadata = true + + apps, meta, err := svc.ListFleetMaintainedApps(ctx, req.TeamID, req.ListOptions) + if err != nil { + return listFleetMaintainedAppsResponse{Err: err}, nil + } + + return listFleetMaintainedAppsResponse{FleetMaintainedApps: apps, Meta: meta}, nil +} + +func (svc *Service) ListFleetMaintainedApps(ctx context.Context, teamID uint, opts fleet.ListOptions) ([]fleet.MaintainedApp, *fleet.PaginationMetadata, error) { + // skipauth: No authorization check needed due to implementation returning + // only license error. + svc.authz.SkipAuthorization(ctx) + + return nil, nil, fleet.ErrMissingLicense +} + +type getFleetMaintainedAppRequest struct { + AppID uint `url:"app_id"` +} + +type getFleetMaintainedAppResponse struct { + FleetMaintainedApp *fleet.MaintainedApp `json:"fleet_maintained_app"` + Err error `json:"error,omitempty"` +} + +func (r getFleetMaintainedAppResponse) error() error { return r.Err } + +func getFleetMaintainedApp(ctx context.Context, request any, svc fleet.Service) (errorer, error) { + req := request.(*getFleetMaintainedAppRequest) + + app, err := svc.GetFleetMaintainedApp(ctx, req.AppID) + if err != nil { + return getFleetMaintainedAppResponse{Err: err}, nil + } + + return getFleetMaintainedAppResponse{FleetMaintainedApp: app}, nil +} + +func (svc *Service) GetFleetMaintainedApp(ctx context.Context, appID uint) (*fleet.MaintainedApp, error) { + // skipauth: No authorization check needed due to implementation returning + // only license error. + svc.authz.SkipAuthorization(ctx) + + return nil, fleet.ErrMissingLicense +} diff --git a/server/service/software.go b/server/service/software.go index 24a75d40af83..e589ac7fe679 100644 --- a/server/service/software.go +++ b/server/service/software.go @@ -248,36 +248,3 @@ func (svc Service) CountSoftware(ctx context.Context, opt fleet.SoftwareListOpti return svc.ds.CountSoftware(ctx, opt) } - -// Fleet Maintained Apps -type listFleetMaintainedAppsRequest struct { - fleet.ListOptions - TeamID uint `query:"team_id"` -} - -type listFleetMaintainedAppsResponse struct { - FleetMaintainedApps []fleet.MaintainedApp `json:"fleet_maintained_apps"` - Meta *fleet.PaginationMetadata `json:"meta"` - Err error `json:"error,omitempty"` -} - -func (r listFleetMaintainedAppsResponse) error() error { return r.Err } - -func listFleetMaintainedApps(ctx context.Context, request any, svc fleet.Service) (errorer, error) { - req := request.(*listFleetMaintainedAppsRequest) - - req.IncludeMetadata = true - - apps, meta, err := svc.ListFleetMaintainedApps(ctx, req.TeamID, req.ListOptions) - if err != nil { - return listFleetMaintainedAppsResponse{Err: err}, nil - } - - return listFleetMaintainedAppsResponse{FleetMaintainedApps: apps, Meta: meta}, nil -} - -func (svc *Service) ListFleetMaintainedApps(ctx context.Context, teamID uint, opts fleet.ListOptions) ([]fleet.MaintainedApp, *fleet.PaginationMetadata, error) { - svc.authz.SkipAuthorization(ctx) - - return nil, nil, fleet.ErrMissingLicense -} From 314219a65a970d017a0225b8932af576d86fa653 Mon Sep 17 00:00:00 2001 From: Gabriel Hernandez Date: Fri, 20 Sep 2024 15:47:01 +0100 Subject: [PATCH 012/385] Add UI for adding fleet maintained apps (#22204) relates to #21775 > NOTE: there still needs to be integrated with the API when this work is done. Adds the UI for adding Fleet maintained applications. This includes: **the view to see all the fleet maintained apps** ![image](https://github.com/user-attachments/assets/f49983d1-df6d-4721-b50d-a4fa78b2c85e) **The fleet maintained app details Page:** ![image](https://github.com/user-attachments/assets/974c4a83-211e-45de-b0cc-2c5f6e055896) - [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. - [ ] Added/updated tests - [ ] Manual QA for all new/changed functionality --- changes/21775-ui-fleet-maintained-apps | 1 + frontend/__mocks__/softwareMock.ts | 36 +++ frontend/components/Modal/Modal.tsx | 28 ++- .../DataTable/HeaderCell/HeaderCell.tsx | 6 +- .../SoftwareNameCell/SoftwareNameCell.tsx | 10 +- frontend/interfaces/software.ts | 18 ++ .../SoftwareAddPage/SoftwareAddPage.tsx | 2 +- .../AddFleetAppSoftwareModal.tsx | 28 +++ .../AddFleetAppSoftwareModal/_styles.scss | 8 + .../AddFleetAppSoftwareModal/index.ts | 1 + .../FleetAppDetailsForm.tsx | 157 ++++++++++++ .../FleetAppDetailsForm/_styles.scss | 19 ++ .../FleetAppDetailsForm/helpers.ts | 76 ++++++ .../FleetAppDetailsForm/index.ts | 1 + .../FleetMaintainedAppDetailsPage.tsx | 223 ++++++++++++++++++ .../_styles.scss | 31 +++ .../FleetMaintainedAppDetailsPage/index.ts | 1 + .../FleetMaintainedAppsTable.tsx | 164 +++++++++++++ .../FleetMaintainedAppsTableConfig.tsx | 81 +++++++ .../FleetMaintainedAppsTable/_styles.scss | 3 + .../FleetMaintainedAppsTable/index.ts | 1 + .../SoftwareFleetMaintained.tsx | 46 +++- .../SoftwareFleetMaintained/_styles.scss | 4 +- .../AdvancedOptionsFields.tsx | 112 +++++++++ .../AdvancedOptionsFields/_styles.scss | 5 + .../components/AdvancedOptionsFields/index.ts | 1 + .../PackageAdvancedOptions.tsx | 79 ++----- .../PackageAdvancedOptions/_styles.scss | 3 - .../SoftwarePage/components/icons/Box.tsx | 16 ++ .../SoftwarePage/components/icons/Brave.tsx | 61 +++++ .../components/icons/Cloudflare.tsx | 18 ++ .../SoftwarePage/components/icons/Docker.tsx | 14 ++ .../SoftwarePage/components/icons/Edge.tsx | 116 +++++++++ .../SoftwarePage/components/icons/Figma.tsx | 41 ++++ .../SoftwarePage/components/icons/Notion.tsx | 20 ++ .../components/icons/TeamViewer.tsx | 46 ++++ .../components/icons/TeamViewerHost.tsx | 14 ++ .../components/icons/WhatsApp.tsx | 18 ++ .../components/icons/WindowsDefender.tsx | 14 ++ .../SoftwarePage/components/icons/index.ts | 20 ++ frontend/router/index.tsx | 5 + frontend/router/paths.ts | 2 + frontend/services/entities/software.ts | 88 +++++++ frontend/utilities/endpoints.ts | 3 + 44 files changed, 1559 insertions(+), 82 deletions(-) create mode 100644 changes/21775-ui-fleet-maintained-apps create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/AddFleetAppSoftwareModal/AddFleetAppSoftwareModal.tsx create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/AddFleetAppSoftwareModal/_styles.scss create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/AddFleetAppSoftwareModal/index.ts create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetAppDetailsForm/FleetAppDetailsForm.tsx create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetAppDetailsForm/_styles.scss create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetAppDetailsForm/helpers.ts create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetAppDetailsForm/index.ts create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetMaintainedAppDetailsPage.tsx create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/_styles.scss create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/index.ts create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/FleetMaintainedAppsTable.tsx create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/FleetMaintainedAppsTableConfig.tsx create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/_styles.scss create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/index.ts create mode 100644 frontend/pages/SoftwarePage/components/AdvancedOptionsFields/AdvancedOptionsFields.tsx create mode 100644 frontend/pages/SoftwarePage/components/AdvancedOptionsFields/_styles.scss create mode 100644 frontend/pages/SoftwarePage/components/AdvancedOptionsFields/index.ts create mode 100644 frontend/pages/SoftwarePage/components/icons/Box.tsx create mode 100644 frontend/pages/SoftwarePage/components/icons/Brave.tsx create mode 100644 frontend/pages/SoftwarePage/components/icons/Cloudflare.tsx create mode 100644 frontend/pages/SoftwarePage/components/icons/Docker.tsx create mode 100644 frontend/pages/SoftwarePage/components/icons/Edge.tsx create mode 100644 frontend/pages/SoftwarePage/components/icons/Figma.tsx create mode 100644 frontend/pages/SoftwarePage/components/icons/Notion.tsx create mode 100644 frontend/pages/SoftwarePage/components/icons/TeamViewer.tsx create mode 100644 frontend/pages/SoftwarePage/components/icons/TeamViewerHost.tsx create mode 100644 frontend/pages/SoftwarePage/components/icons/WhatsApp.tsx create mode 100644 frontend/pages/SoftwarePage/components/icons/WindowsDefender.tsx diff --git a/changes/21775-ui-fleet-maintained-apps b/changes/21775-ui-fleet-maintained-apps new file mode 100644 index 000000000000..3cb4883f9b84 --- /dev/null +++ b/changes/21775-ui-fleet-maintained-apps @@ -0,0 +1 @@ +- add UI for adding fleet maintained apps diff --git a/frontend/__mocks__/softwareMock.ts b/frontend/__mocks__/softwareMock.ts index fb2fc313ba78..a40589a099ff 100644 --- a/frontend/__mocks__/softwareMock.ts +++ b/frontend/__mocks__/softwareMock.ts @@ -7,6 +7,8 @@ import { ISoftwareTitle, ISoftwareTitleDetails, IAppStoreApp, + IFleetMaintainedApp, + IFleetMaintainedAppDetails, } from "interfaces/software"; import { ISoftwareTitlesResponse, @@ -253,3 +255,37 @@ export const createMockSoftwareTitlesResponse = ( ): ISoftwareTitlesResponse => { return { ...DEFAULT_SOFTWARE_TITLES_RESPONSE_MOCK, ...overrides }; }; + +const DEFAULT_FLEET_MAINTAINED_APPS_MOCK: IFleetMaintainedApp = { + id: 1, + name: "test app", + version: "1.2.3", + platform: "darwin", +}; + +export const createMockFleetMaintainedApp = ( + overrides?: Partial +): IFleetMaintainedApp => { + return { + ...DEFAULT_FLEET_MAINTAINED_APPS_MOCK, + ...overrides, + }; +}; + +const DEFAULT_FLEET_MAINTAINED_APP_DETAILS_MOCK: IFleetMaintainedAppDetails = { + id: 1, + name: "Test app", + version: "1.2.3", + platform: "darwin", + pre_install_script: "SELECT * FROM osquery_info WHERE start_time > 1", + install_script: '#!/bin/sh\n\ninstaller -pkg "$INSTALLER" -target /', + post_install_script: 'echo "Installed"', + uninstall_script: + "#!/bin/sh\n\n# Fleet extracts and saves package IDs\npkg_ids=$PACKAGE_ID", +}; + +export const createMockFleetMaintainedAppDetails = ( + overrides?: Partial +) => { + return { ...DEFAULT_FLEET_MAINTAINED_APP_DETAILS_MOCK, ...overrides }; +}; diff --git a/frontend/components/Modal/Modal.tsx b/frontend/components/Modal/Modal.tsx index d69c10c481f5..deb2d10c724b 100644 --- a/frontend/components/Modal/Modal.tsx +++ b/frontend/components/Modal/Modal.tsx @@ -31,6 +31,11 @@ export interface IModalProps { * @default false */ isContentDisabled?: boolean; + /** `disableClosingModal` can be set to disable the users ability to manually + * close the modal. + * @default false + * */ + disableClosingModal?: boolean; className?: string; } @@ -43,6 +48,7 @@ const Modal = ({ isHidden = false, isLoading = false, isContentDisabled = false, + disableClosingModal = false, className, }: IModalProps): JSX.Element => { useEffect(() => { @@ -52,12 +58,16 @@ const Modal = ({ } }; - document.addEventListener("keydown", closeWithEscapeKey); + if (!disableClosingModal) { + document.addEventListener("keydown", closeWithEscapeKey); + } return () => { - document.removeEventListener("keydown", closeWithEscapeKey); + if (!disableClosingModal) { + document.removeEventListener("keydown", closeWithEscapeKey); + } }; - }, []); + }, [disableClosingModal, onExit]); useEffect(() => { if (onEnter) { @@ -101,11 +111,13 @@ const Modal = ({
{title} -
- -
+ {!disableClosingModal && ( +
+ +
+ )}
diff --git a/frontend/components/TableContainer/DataTable/HeaderCell/HeaderCell.tsx b/frontend/components/TableContainer/DataTable/HeaderCell/HeaderCell.tsx index 608ccd91fd2e..5176b0986fd8 100644 --- a/frontend/components/TableContainer/DataTable/HeaderCell/HeaderCell.tsx +++ b/frontend/components/TableContainer/DataTable/HeaderCell/HeaderCell.tsx @@ -1,17 +1,19 @@ -import React from "react"; +import React, { ReactNode } from "react"; import classnames from "classnames"; interface IHeaderCellProps { - value: string | JSX.Element; // either a string or a TooltipWrapper + value: ReactNode; isSortedDesc?: boolean; disableSortBy?: boolean; + tootip?: ReactNode; } const HeaderCell = ({ value, isSortedDesc, disableSortBy, + tootip, }: IHeaderCellProps): JSX.Element => { let sortArrowClass = ""; if (isSortedDesc === undefined) { diff --git a/frontend/components/TableContainer/DataTable/SoftwareNameCell/SoftwareNameCell.tsx b/frontend/components/TableContainer/DataTable/SoftwareNameCell/SoftwareNameCell.tsx index d8301b9b8b11..987054b2aabb 100644 --- a/frontend/components/TableContainer/DataTable/SoftwareNameCell/SoftwareNameCell.tsx +++ b/frontend/components/TableContainer/DataTable/SoftwareNameCell/SoftwareNameCell.tsx @@ -46,7 +46,10 @@ const InstallIconWithTooltip = ({ . ) : ( - "Software can be installed on Host details page." + <> + Install manually on Host details page or automatically with + policy automations. + )} @@ -55,8 +58,9 @@ const InstallIconWithTooltip = ({ }; interface ISoftwareNameCellProps { - name: string; - source: string; + name?: string; + source?: string; + /** pass in a `path` that this cell will link to */ path?: string; router?: InjectedRouter; hasPackage?: boolean; diff --git a/frontend/interfaces/software.ts b/frontend/interfaces/software.ts index a3e633a8bb6b..77b4c91767cb 100644 --- a/frontend/interfaces/software.ts +++ b/frontend/interfaces/software.ts @@ -389,3 +389,21 @@ export const hasHostSoftwareAppLastInstall = ( export const isIpadOrIphoneSoftwareSource = (source: string) => ["ios_apps", "ipados_apps"].includes(source); + +export interface IFleetMaintainedApp { + id: number; + name: string; + version: string; + platform: string; +} + +export interface IFleetMaintainedAppDetails { + id: number; + name: string; + version: string; + platform: string; + pre_install_script: string; // TODO: is this needed? + install_script: string; + post_install_script: string; // TODO: is this needed? + uninstall_script: string; +} diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAddPage.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAddPage.tsx index 8ccb94c7844a..5e6de90d62df 100644 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAddPage.tsx +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAddPage.tsx @@ -98,7 +98,7 @@ const SoftwareAddPage = ({ {React.cloneElement(children, { router, - teamId: location.query.team_id, + currentTeamId: location.query.team_id, })} diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/AddFleetAppSoftwareModal/AddFleetAppSoftwareModal.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/AddFleetAppSoftwareModal/AddFleetAppSoftwareModal.tsx new file mode 100644 index 000000000000..1c15a1b1b9bf --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/AddFleetAppSoftwareModal/AddFleetAppSoftwareModal.tsx @@ -0,0 +1,28 @@ +import Modal from "components/Modal"; +import Spinner from "components/Spinner"; +import { noop } from "lodash"; +import React from "react"; + +const baseClass = "add-fleet-app-software-modal"; + +const AddFleetAppSoftwareModal = () => { + return ( + + <> + +

+ Uploading software so that it's available for install. This may + take few minutes. +

+ +
+ ); +}; + +export default AddFleetAppSoftwareModal; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/AddFleetAppSoftwareModal/_styles.scss b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/AddFleetAppSoftwareModal/_styles.scss new file mode 100644 index 000000000000..2a4a528377f4 --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/AddFleetAppSoftwareModal/_styles.scss @@ -0,0 +1,8 @@ +.add-fleet-app-software-modal { + margin-top: 215px; + text-align: center; + + &__spinner { + margin: 0 auto; + } +} diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/AddFleetAppSoftwareModal/index.ts b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/AddFleetAppSoftwareModal/index.ts new file mode 100644 index 000000000000..80f6e83ceccc --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/AddFleetAppSoftwareModal/index.ts @@ -0,0 +1 @@ +export { default } from "./AddFleetAppSoftwareModal"; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetAppDetailsForm/FleetAppDetailsForm.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetAppDetailsForm/FleetAppDetailsForm.tsx new file mode 100644 index 000000000000..0ebd284397f3 --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetAppDetailsForm/FleetAppDetailsForm.tsx @@ -0,0 +1,157 @@ +import React, { useState } from "react"; + +import Checkbox from "components/forms/fields/Checkbox"; +import TooltipWrapper from "components/TooltipWrapper"; +import RevealButton from "components/buttons/RevealButton"; +import Button from "components/buttons/Button"; + +import AdvancedOptionsFields from "pages/SoftwarePage/components/AdvancedOptionsFields"; + +import { generateFormValidation } from "./helpers"; + +const baseClass = "fleet-app-details-form"; + +export interface IFleetMaintainedAppFormData { + selfService: boolean; + installScript: string; + preInstallQuery?: string; + postInstallScript?: string; + uninstallScript?: string; +} + +export interface IFormValidation { + isValid: boolean; + preInstallQuery?: { isValid: boolean; message?: string }; +} + +interface IFleetAppDetailsFormProps { + defaultInstallScript: string; + defaultPostInstallScript: string; + defaultUninstallScript: string; + showSchemaButton: boolean; + onClickShowSchema: () => void; + onCancel: () => void; + onSubmit: (formData: IFleetMaintainedAppFormData) => void; +} + +const FleetAppDetailsForm = ({ + defaultInstallScript, + defaultPostInstallScript, + defaultUninstallScript, + showSchemaButton, + onClickShowSchema, + onCancel, + onSubmit, +}: IFleetAppDetailsFormProps) => { + const [showAdvancedOptions, setShowAdvancedOptions] = useState(false); + + const [formData, setFormData] = useState({ + selfService: false, + preInstallQuery: undefined, + installScript: defaultInstallScript, + postInstallScript: defaultPostInstallScript, + uninstallScript: defaultUninstallScript, + }); + const [formValidation, setFormValidation] = useState({ + isValid: true, + preInstallQuery: { isValid: false }, + }); + + const onChangePreInstallQuery = (value?: string) => { + const newData = { ...formData, preInstallQuery: value }; + setFormData(newData); + setFormValidation(generateFormValidation(newData)); + }; + + const onChangeInstallScript = (value: string) => { + const newData = { ...formData, installScript: value }; + setFormData(newData); + setFormValidation(generateFormValidation(newData)); + }; + + const onChangePostInstallScript = (value?: string) => { + const newData = { ...formData, postInstallScript: value }; + setFormData(newData); + setFormValidation(generateFormValidation(newData)); + }; + + const onChangeUninstallScript = (value?: string) => { + const newData = { ...formData, uninstallScript: value }; + setFormData(newData); + setFormValidation(generateFormValidation(newData)); + }; + + const onToggleSelfServiceCheckbox = (value: boolean) => { + const newData = { ...formData, selfService: value }; + setFormData(newData); + setFormValidation(generateFormValidation(newData)); + }; + + const onSubmitForm = (evt: React.FormEvent) => { + evt.preventDefault(); + onSubmit(formData); + }; + + const isSubmitDisabled = !formValidation.isValid; + + return ( +
+ + + End users can install from Fleet Desktop {">"} Self-service + . + + } + > + Self-service + + +
+ setShowAdvancedOptions(!showAdvancedOptions)} + /> + {showAdvancedOptions && ( + + )} +
+
+ + +
+
+ ); +}; + +export default FleetAppDetailsForm; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetAppDetailsForm/_styles.scss b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetAppDetailsForm/_styles.scss new file mode 100644 index 000000000000..436e3678e8ed --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetAppDetailsForm/_styles.scss @@ -0,0 +1,19 @@ +.fleet-app-details-form { + + &__advanced-options-section { + display: flex; + flex-direction: column; + gap: $pad-large; + align-items: flex-start; + } + + &__advanced-options-fields { + width: 100%; + } + + &__form-buttons { + display: flex; + flex-direction: row; + gap: $pad-large; + } +} diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetAppDetailsForm/helpers.ts b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetAppDetailsForm/helpers.ts new file mode 100644 index 000000000000..12ee2c5f9a94 --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetAppDetailsForm/helpers.ts @@ -0,0 +1,76 @@ +// @ts-ignore +import validateQuery from "components/forms/validators/validate_query"; + +import { + IFleetMaintainedAppFormData, + IFormValidation, +} from "./FleetAppDetailsForm"; + +type IMessageFunc = (formData: IFleetMaintainedAppFormData) => string; +type IValidationMessage = string | IMessageFunc; + +interface IValidation { + name: string; + isValid: (formData: IFleetMaintainedAppFormData) => boolean; + message?: IValidationMessage; +} + +const FORM_VALIDATION_CONFIG: Record< + "preInstallQuery", + { validations: IValidation[] } +> = { + preInstallQuery: { + validations: [ + { + name: "invalidQuery", + isValid: (formData) => { + const query = formData.preInstallQuery; + return ( + query === undefined || query === "" || validateQuery(query).valid + ); + }, + message: (formData) => validateQuery(formData.preInstallQuery).error, + }, + ], + }, +}; + +const getErrorMessage = ( + formData: IFleetMaintainedAppFormData, + message?: IValidationMessage +) => { + if (message === undefined || typeof message === "string") { + return message; + } + return message(formData); +}; + +// eslint-disable-next-line import/prefer-default-export +export const generateFormValidation = ( + formData: IFleetMaintainedAppFormData +) => { + const formValidation: IFormValidation = { + isValid: true, + }; + + Object.keys(FORM_VALIDATION_CONFIG).forEach((key) => { + const objKey = key as keyof typeof FORM_VALIDATION_CONFIG; + const failedValidation = FORM_VALIDATION_CONFIG[objKey].validations.find( + (validation) => !validation.isValid(formData) + ); + + if (!failedValidation) { + formValidation[objKey] = { + isValid: true, + }; + } else { + formValidation.isValid = false; + formValidation[objKey] = { + isValid: false, + message: getErrorMessage(formData, failedValidation.message), + }; + } + }); + + return formValidation; +}; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetAppDetailsForm/index.ts b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetAppDetailsForm/index.ts new file mode 100644 index 000000000000..cdeb43cd2f68 --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetAppDetailsForm/index.ts @@ -0,0 +1 @@ +export { default } from "./FleetAppDetailsForm"; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetMaintainedAppDetailsPage.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetMaintainedAppDetailsPage.tsx new file mode 100644 index 000000000000..a97b7a1fc112 --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetMaintainedAppDetailsPage.tsx @@ -0,0 +1,223 @@ +import React, { useContext, useState } from "react"; +import { Location } from "history"; +import { useQuery } from "react-query"; +import { InjectedRouter } from "react-router"; + +import PATHS from "router/paths"; +import { buildQueryStringFromParams } from "utilities/url"; +import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants"; +import softwareAPI from "services/entities/software"; +import { QueryContext } from "context/query"; +import { AppContext } from "context/app"; +import { NotificationContext } from "context/notification"; +import { getErrorReason } from "interfaces/errors"; +import { Platform, PLATFORM_DISPLAY_NAMES } from "interfaces/platform"; +import useToggleSidePanel from "hooks/useToggleSidePanel"; + +import BackLink from "components/BackLink"; +import MainContent from "components/MainContent"; +import Spinner from "components/Spinner"; +import DataError from "components/DataError"; +import SidePanelContent from "components/SidePanelContent"; +import QuerySidePanel from "components/side_panels/QuerySidePanel"; +import PremiumFeatureMessage from "components/PremiumFeatureMessage"; +import Card from "components/Card"; + +import SoftwareIcon from "pages/SoftwarePage/components/icons/SoftwareIcon"; + +import FleetAppDetailsForm from "./FleetAppDetailsForm"; +import { IFleetMaintainedAppFormData } from "./FleetAppDetailsForm/FleetAppDetailsForm"; +import AddFleetAppSoftwareModal from "./AddFleetAppSoftwareModal"; + +const baseClass = "fleet-maintained-app-details-page"; + +interface ISoftwareSummaryProps { + name: string; + platform: string; + version: string; +} + +const FleetAppSummary = ({ + name, + platform, + version, +}: ISoftwareSummaryProps) => { + return ( + + +
+
{name}
+
+
+ {PLATFORM_DISPLAY_NAMES[platform as Platform]} +
+ • +
+ {version} +
+
+
+
+ ); +}; + +export interface IFleetMaintainedAppDetailsQueryParams { + team_id?: string; +} + +interface IFleetMaintainedAppDetailsRouteParams { + id: string; +} + +interface IFleetMaintainedAppDetailsPageProps { + location: Location; + router: InjectedRouter; + routeParams: IFleetMaintainedAppDetailsRouteParams; +} + +/** This type includes the editable form data as well as the fleet maintained + * app id */ +export type IAddFleetMaintainedData = IFleetMaintainedAppFormData & { + appId: number; +}; + +const FleetMaintainedAppDetailsPage = ({ + location, + router, + routeParams, +}: IFleetMaintainedAppDetailsPageProps) => { + const teamId = location.query.team_id; + const appId = parseInt(routeParams.id, 10); + + const { renderFlash } = useContext(NotificationContext); + const { isPremiumTier } = useContext(AppContext); + const { selectedOsqueryTable, setSelectedOsqueryTable } = useContext( + QueryContext + ); + const { isSidePanelOpen, setSidePanelOpen } = useToggleSidePanel(false); + const [ + showAddFleetAppSoftwareModal, + setShowAddFleetAppSoftwareModal, + ] = useState(false); + + const { data, isLoading, isError } = useQuery( + ["fleet-maintained-app", appId], + () => softwareAPI.getFleetMainainedApp(appId), + { + ...DEFAULT_USE_QUERY_OPTIONS, + enabled: isPremiumTier, + select: (res) => res.fleet_maintained_app, + } + ); + + const onOsqueryTableSelect = (tableName: string) => { + setSelectedOsqueryTable(tableName); + }; + + const backToAddSoftwareUrl = `${ + PATHS.SOFTWARE_ADD_FLEET_MAINTAINED + }?${buildQueryStringFromParams({ team_id: teamId })}`; + + const onCancel = () => { + router.push(backToAddSoftwareUrl); + }; + + const onSubmit = async (formData: IFleetMaintainedAppFormData) => { + // this should not happen but we need to handle the type correctly + if (!teamId) return; + + setShowAddFleetAppSoftwareModal(true); + + try { + await softwareAPI.addFleetMaintainedApp(parseInt(teamId, 10), { + ...formData, + appId, + }); + renderFlash( + "success", + <> + {data?.name} successfully added. + + ); + router.push( + `${PATHS.SOFTWARE_TITLES}?${buildQueryStringFromParams({ + team_id: teamId, + available_for_install: true, + })}` + ); + } catch (error) { + renderFlash("error", getErrorReason(error)); // TODO: handle error messages + } + + setShowAddFleetAppSoftwareModal(false); + }; + + const renderContent = () => { + if (!isPremiumTier) { + return ; + } + + if (isLoading) { + return ; + } + + if (isError) { + return ; + } + + if (data) { + return ( + <> + +

{data.name}

+
+ + setSidePanelOpen(true)} + onCancel={onCancel} + onSubmit={onSubmit} + /> +
+ + ); + } + + return null; + }; + + return ( + <> + + <>{renderContent()} + + {isPremiumTier && data && isSidePanelOpen && ( + + setSidePanelOpen(false)} + /> + + )} + {showAddFleetAppSoftwareModal && } + + ); +}; + +export default FleetMaintainedAppDetailsPage; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/_styles.scss b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/_styles.scss new file mode 100644 index 000000000000..4995d172832f --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/_styles.scss @@ -0,0 +1,31 @@ +.fleet-maintained-app-details-page { + &__back-to-add-software { + margin-bottom: $pad-medium; + } + + h1 { + margin-bottom: $pad-large; + } + + &__page-content { + display: flex; + flex-direction: column; + gap: $pad-large; + } + + &__fleet-app-summary { + display: flex; + gap: $pad-medium; + } + + &__fleet-app-summary--title { + font-weight: $bold; + font-size: $small; + } + + &__fleet-app-summary--info { + font-size: $x-small; + display: flex; + gap: $pad-xsmall; + } +} diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/index.ts b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/index.ts new file mode 100644 index 000000000000..3f81c5728816 --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/index.ts @@ -0,0 +1 @@ +export { default } from "./FleetMaintainedAppDetailsPage"; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/FleetMaintainedAppsTable.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/FleetMaintainedAppsTable.tsx new file mode 100644 index 000000000000..4cdc17270404 --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/FleetMaintainedAppsTable.tsx @@ -0,0 +1,164 @@ +import React, { useCallback, useMemo } from "react"; +import { InjectedRouter } from "react-router"; + +import PATHS from "router/paths"; +import { ISoftwareFleetMaintainedAppsResponse } from "services/entities/software"; +import { getNextLocationPath } from "utilities/helpers"; +import { buildQueryStringFromParams } from "utilities/url"; + +import TableContainer from "components/TableContainer"; +import TableCount from "components/TableContainer/TableCount"; +import LastUpdatedText from "components/LastUpdatedText"; +import { ITableQueryData } from "components/TableContainer/TableContainer"; + +import { generateTableConfig } from "./FleetMaintainedAppsTableConfig"; + +const baseClass = "fleet-maintained-apps-table"; + +interface IFleetMaintainedAppsTableProps { + teamId: number; + isLoading: boolean; + query: string; + perPage: number; + orderDirection: "asc" | "desc"; + orderKey: string; + currentPage: number; + router: InjectedRouter; + data?: ISoftwareFleetMaintainedAppsResponse; +} + +const FleetMaintainedAppsTable = ({ + teamId, + isLoading, + data, + router, + query, + perPage, + orderDirection, + orderKey, + currentPage, +}: IFleetMaintainedAppsTableProps) => { + const determineQueryParamChange = useCallback( + (newTableQuery: ITableQueryData) => { + const changedEntry = Object.entries(newTableQuery).find(([key, val]) => { + switch (key) { + case "searchQuery": + return val !== query; + case "sortDirection": + return val !== orderDirection; + case "sortHeader": + return val !== orderKey; + case "pageIndex": + return val !== currentPage; + default: + return false; + } + }); + return changedEntry?.[0] ?? ""; + }, + [currentPage, orderDirection, orderKey, query] + ); + + const generateNewQueryParams = useCallback( + (newTableQuery: ITableQueryData, changedParam: string) => { + const newQueryParam: Record = { + query: newTableQuery.searchQuery, + team_id: teamId, + order_direction: newTableQuery.sortDirection, + order_key: newTableQuery.sortHeader, + page: changedParam === "pageIndex" ? newTableQuery.pageIndex : 0, + }; + + return newQueryParam; + }, + [teamId] + ); + + // NOTE: this is called once on initial render and every time the query changes + const onQueryChange = useCallback( + (newTableQuery: ITableQueryData) => { + // we want to determine which query param has changed in order to + // reset the page index to 0 if any other param has changed. + const changedParam = determineQueryParamChange(newTableQuery); + + // if nothing has changed, don't update the route. this can happen when + // this handler is called on the inital render. Can also happen when + // the filter dropdown is changed. That is handled on the onChange handler + // for the dropdown. + if (changedParam === "") return; + + const newRoute = getNextLocationPath({ + pathPrefix: PATHS.SOFTWARE_ADD_FLEET_MAINTAINED, + routeTemplate: "", + queryParams: generateNewQueryParams(newTableQuery, changedParam), + }); + + router.replace(newRoute); + }, + [determineQueryParamChange, generateNewQueryParams, router] + ); + + const handleRowClick = () => { + // TODO: change to correct path + const path = `${PATHS.MANAGE_HOSTS}?${buildQueryStringFromParams({ + team_id: teamId, + })}`; + + router.push(path); + }; + + const tableHeadersConfig = useMemo(() => { + if (!data) return []; + return generateTableConfig(router, teamId); + }, [data, router, teamId]); + + const renderCount = () => { + if (!data) return null; + + return ( + <> + + {data?.counts_updated_at && ( + + The last time Fleet-maintained
+ package library data was
+ updated. + + } + /> + )} + + ); + }; + + return ( + + className={baseClass} + columnConfigs={tableHeadersConfig} + data={data?.fleet_maintained_apps ?? []} + isLoading={isLoading} + resultsTitle="items" + emptyComponent={() => <>EMPTY STATE TODO} + defaultSortHeader={orderKey} + defaultSortDirection={orderDirection} + defaultPageIndex={currentPage} + defaultSearchQuery={query} + manualSortBy + pageSize={perPage} + showMarkAllPages={false} + isAllPagesSelected={false} + disableNextPage={!data?.meta.has_next_results} + searchable + inputPlaceHolder="Search by name" + onQueryChange={onQueryChange} + renderCount={renderCount} + disableMultiRowSelect + onClickRow={handleRowClick} + /> + ); +}; + +export default FleetMaintainedAppsTable; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/FleetMaintainedAppsTableConfig.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/FleetMaintainedAppsTableConfig.tsx new file mode 100644 index 000000000000..42f2012a8790 --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/FleetMaintainedAppsTableConfig.tsx @@ -0,0 +1,81 @@ +import React from "react"; +import { Column } from "react-table"; +import { InjectedRouter } from "react-router"; + +import PATHS from "router/paths"; +import { IHeaderProps, IStringCellProps } from "interfaces/datatable_config"; +import { APPLE_PLATFORM_DISPLAY_NAMES } from "interfaces/platform"; +import { IFleetMaintainedApp } from "interfaces/software"; +import { buildQueryStringFromParams } from "utilities/url"; + +import TextCell from "components/TableContainer/DataTable/TextCell"; +import HeaderCell from "components/TableContainer/DataTable/HeaderCell"; +import SoftwareNameCell from "components/TableContainer/DataTable/SoftwareNameCell"; +import TooltipWrapper from "components/TooltipWrapper"; + +type IFleetMaintainedAppsTableConfig = Column; +type ITableStringCellProps = IStringCellProps; +type ITableHeaderProps = IHeaderProps; + +// eslint-disable-next-line import/prefer-default-export +export const generateTableConfig = ( + router: InjectedRouter, + teamId: number +): IFleetMaintainedAppsTableConfig[] => { + return [ + { + Header: (cellProps: ITableHeaderProps) => ( + + ), + accessor: "name", + Cell: (cellProps: ITableStringCellProps) => { + const { name, id } = cellProps.row.original; + + const path = `${PATHS.SOFTWARE_FLEET_MAINTAINED_DETAILS( + id + )}?${buildQueryStringFromParams({ + team_id: teamId, + })}`; + + return ; + }, + sortType: "caseInsensitive", + }, + { + Header: "Version", + accessor: "version", + Cell: ({ cell }: ITableStringCellProps) => ( + + ), + disableSortBy: true, + }, + { + Header: () => { + const titleWithToolTip = ( + + Currently, only macOS apps are
+ supported. + + } + > + Platform +
+ ); + return ; + }, + accessor: "platform", + Cell: ({ cell }: ITableStringCellProps) => ( + + ), + disableSortBy: true, + }, + ]; +}; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/_styles.scss b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/_styles.scss new file mode 100644 index 000000000000..223887888044 --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/_styles.scss @@ -0,0 +1,3 @@ +.fleet-maintained-apps-table { + +} diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/index.ts b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/index.ts new file mode 100644 index 000000000000..a23cb1209090 --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/index.ts @@ -0,0 +1 @@ +export { default } from "./FleetMaintainedAppsTable"; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/SoftwareFleetMaintained.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/SoftwareFleetMaintained.tsx index aaa9627bddbb..dcb87bd3b6d8 100644 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/SoftwareFleetMaintained.tsx +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/SoftwareFleetMaintained.tsx @@ -1,9 +1,15 @@ import React from "react"; import { InjectedRouter } from "react-router"; import { Location } from "history"; +import { useQuery } from "react-query"; -import { DEFAULT_QUERY } from "utilities/constants"; +import softwareAPI from "services/entities/software"; +import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants"; +import Spinner from "components/Spinner"; +import DataError from "components/DataError"; + +import FleetMaintainedAppsTable from "./FleetMaintainedAppsTable"; import { ISoftwareAddPageQueryParams } from "../SoftwareAddPage"; const baseClass = "software-fleet-maintained"; @@ -15,8 +21,8 @@ interface ISoftwareFleetMaintainedProps { } // default values for query params used on this page if not provided -const DEFAULT_SORT_DIRECTION = "desc"; -const DEFAULT_SORT_HEADER = "hosts_count"; +const DEFAULT_SORT_DIRECTION = "asc"; +const DEFAULT_SORT_HEADER = "name"; const DEFAULT_PAGE_SIZE = 20; const DEFAULT_PAGE = 0; @@ -28,12 +34,42 @@ const SoftwareFleetMaintained = ({ const { order_key = DEFAULT_SORT_HEADER, order_direction = DEFAULT_SORT_DIRECTION, - query = DEFAULT_QUERY, + query = "", page, } = location.query; const currentPage = page ? parseInt(page, 10) : DEFAULT_PAGE; - return
Maintained Page
; + const { data, isLoading, isError } = useQuery( + ["fleet-maintained", currentTeamId], + () => softwareAPI.getFleetMaintainedApps(currentTeamId), + { + ...DEFAULT_USE_QUERY_OPTIONS, + } + ); + + if (isLoading) { + return ; + } + + if (isError) { + return ; + } + + return ( +
+ +
+ ); }; export default SoftwareFleetMaintained; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/_styles.scss b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/_styles.scss index a3e72e4c244f..11fd431079ce 100644 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/_styles.scss +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/_styles.scss @@ -1,3 +1,5 @@ .software-fleet-maintained { - + &__table-error { + margin-top: $pad-xxxlarge; + } } diff --git a/frontend/pages/SoftwarePage/components/AdvancedOptionsFields/AdvancedOptionsFields.tsx b/frontend/pages/SoftwarePage/components/AdvancedOptionsFields/AdvancedOptionsFields.tsx new file mode 100644 index 000000000000..95bf94be5e22 --- /dev/null +++ b/frontend/pages/SoftwarePage/components/AdvancedOptionsFields/AdvancedOptionsFields.tsx @@ -0,0 +1,112 @@ +import React, { ReactNode } from "react"; +import classnames from "classnames"; + +import Editor from "components/Editor"; +import FleetAce from "components/FleetAce"; +import Button from "components/buttons/Button"; +import Icon from "components/Icon"; + +const baseClass = "advanced-options-fields"; + +interface IAdvancedOptionsFieldsProps { + showSchemaButton: boolean; + installScriptHelpText: ReactNode; + postInstallScriptHelpText: ReactNode; + uninstallScriptHelpText: ReactNode; + errors: { preInstallQuery?: string; postInstallScript?: string }; + preInstallQuery?: string; + installScript: string; + postInstallScript?: string; + uninstallScript?: string; + className?: string; + onClickShowSchema: () => void; + onChangePreInstallQuery: (value?: string) => void; + onChangeInstallScript: (value: string) => void; + onChangePostInstallScript: (value?: string) => void; + onChangeUninstallScript: (value?: string) => void; +} + +const AdvancedOptionsFields = ({ + showSchemaButton, + installScriptHelpText, + postInstallScriptHelpText, + uninstallScriptHelpText, + errors, + preInstallQuery, + installScript, + postInstallScript, + uninstallScript, + className, + onClickShowSchema, + onChangePreInstallQuery, + onChangeInstallScript, + onChangePostInstallScript, + onChangeUninstallScript, +}: IAdvancedOptionsFieldsProps) => { + const classNames = classnames(baseClass, className); + + const renderLabelComponent = (): JSX.Element | null => { + if (showSchemaButton) { + return null; + } + + return ( + + ); + }; + + return ( +
+ + + + +
+ ); +}; + +export default AdvancedOptionsFields; diff --git a/frontend/pages/SoftwarePage/components/AdvancedOptionsFields/_styles.scss b/frontend/pages/SoftwarePage/components/AdvancedOptionsFields/_styles.scss new file mode 100644 index 000000000000..cdac97da17dd --- /dev/null +++ b/frontend/pages/SoftwarePage/components/AdvancedOptionsFields/_styles.scss @@ -0,0 +1,5 @@ +.advanced-options-fields { + display: flex; + flex-direction: column; + gap: $pad-medium; +} diff --git a/frontend/pages/SoftwarePage/components/AdvancedOptionsFields/index.ts b/frontend/pages/SoftwarePage/components/AdvancedOptionsFields/index.ts new file mode 100644 index 000000000000..666497c59ba8 --- /dev/null +++ b/frontend/pages/SoftwarePage/components/AdvancedOptionsFields/index.ts @@ -0,0 +1 @@ +export { default } from "./AdvancedOptionsFields"; diff --git a/frontend/pages/SoftwarePage/components/PackageAdvancedOptions/PackageAdvancedOptions.tsx b/frontend/pages/SoftwarePage/components/PackageAdvancedOptions/PackageAdvancedOptions.tsx index ad06e516ec0a..5758b67b0320 100644 --- a/frontend/pages/SoftwarePage/components/PackageAdvancedOptions/PackageAdvancedOptions.tsx +++ b/frontend/pages/SoftwarePage/components/PackageAdvancedOptions/PackageAdvancedOptions.tsx @@ -1,4 +1,5 @@ import React, { useState } from "react"; +import { noop } from "lodash"; import { LEARN_MORE_ABOUT_BASE_LINK } from "utilities/constants"; @@ -8,11 +9,11 @@ import { PackageType, } from "interfaces/package_type"; -import Editor from "components/Editor"; import CustomLink from "components/CustomLink"; -import FleetAce from "components/FleetAce"; import RevealButton from "components/buttons/RevealButton"; + import { IPackageFormData } from "../PackageForm/PackageForm"; +import AdvancedOptionsFields from "../AdvancedOptionsFields"; const getSupportedScriptTypeText = (pkgType: PackageType) => { return `Currently, ${ @@ -95,63 +96,23 @@ const PackageAdvancedOptions = ({ return null; } return ( -
- - Software will be installed only if the{" "} - - - } - /> - - - -
+ ); }; diff --git a/frontend/pages/SoftwarePage/components/PackageAdvancedOptions/_styles.scss b/frontend/pages/SoftwarePage/components/PackageAdvancedOptions/_styles.scss index 99167137dae5..c31242eaf8ac 100644 --- a/frontend/pages/SoftwarePage/components/PackageAdvancedOptions/_styles.scss +++ b/frontend/pages/SoftwarePage/components/PackageAdvancedOptions/_styles.scss @@ -6,9 +6,6 @@ &__input-fields { width: 100%; - display: flex; - flex-direction: column; - gap: $pad-medium; } &__table-link { diff --git a/frontend/pages/SoftwarePage/components/icons/Box.tsx b/frontend/pages/SoftwarePage/components/icons/Box.tsx new file mode 100644 index 000000000000..005f14263050 --- /dev/null +++ b/frontend/pages/SoftwarePage/components/icons/Box.tsx @@ -0,0 +1,16 @@ +import React from "react"; + +import type { SVGProps } from "react"; + +const Box = (props: SVGProps) => ( + + + + +); +export default Box; diff --git a/frontend/pages/SoftwarePage/components/icons/Brave.tsx b/frontend/pages/SoftwarePage/components/icons/Brave.tsx new file mode 100644 index 000000000000..a75ddc94bf17 --- /dev/null +++ b/frontend/pages/SoftwarePage/components/icons/Brave.tsx @@ -0,0 +1,61 @@ +import React from "react"; + +import type { SVGProps } from "react"; + +const Brave = (props: SVGProps) => ( + + + + + + + + + + + + + + + + + + + + + + + +); +export default Brave; diff --git a/frontend/pages/SoftwarePage/components/icons/Cloudflare.tsx b/frontend/pages/SoftwarePage/components/icons/Cloudflare.tsx new file mode 100644 index 000000000000..1cf10d2deee8 --- /dev/null +++ b/frontend/pages/SoftwarePage/components/icons/Cloudflare.tsx @@ -0,0 +1,18 @@ +import React from "react"; + +import type { SVGProps } from "react"; + +const Cloudflare = (props: SVGProps) => ( + + + + + +); +export default Cloudflare; diff --git a/frontend/pages/SoftwarePage/components/icons/Docker.tsx b/frontend/pages/SoftwarePage/components/icons/Docker.tsx new file mode 100644 index 000000000000..051038511042 --- /dev/null +++ b/frontend/pages/SoftwarePage/components/icons/Docker.tsx @@ -0,0 +1,14 @@ +import React from "react"; + +import type { SVGProps } from "react"; + +const Docker = (props: SVGProps) => ( + + + + +); +export default Docker; diff --git a/frontend/pages/SoftwarePage/components/icons/Edge.tsx b/frontend/pages/SoftwarePage/components/icons/Edge.tsx new file mode 100644 index 000000000000..726bb1a70ec0 --- /dev/null +++ b/frontend/pages/SoftwarePage/components/icons/Edge.tsx @@ -0,0 +1,116 @@ +import React from "react"; + +import type { SVGProps } from "react"; + +const Edge = (props: SVGProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); +export default Edge; diff --git a/frontend/pages/SoftwarePage/components/icons/Figma.tsx b/frontend/pages/SoftwarePage/components/icons/Figma.tsx new file mode 100644 index 000000000000..91f721bde929 --- /dev/null +++ b/frontend/pages/SoftwarePage/components/icons/Figma.tsx @@ -0,0 +1,41 @@ +import React from "react"; + +import type { SVGProps } from "react"; + +const Figma = (props: SVGProps) => ( + + + + + + + + + + + + + + + +); +export default Figma; diff --git a/frontend/pages/SoftwarePage/components/icons/Notion.tsx b/frontend/pages/SoftwarePage/components/icons/Notion.tsx new file mode 100644 index 000000000000..bae121084afa --- /dev/null +++ b/frontend/pages/SoftwarePage/components/icons/Notion.tsx @@ -0,0 +1,20 @@ +import React from "react"; + +import type { SVGProps } from "react"; + +const Notion = (props: SVGProps) => ( + + + + + +); +export default Notion; diff --git a/frontend/pages/SoftwarePage/components/icons/TeamViewer.tsx b/frontend/pages/SoftwarePage/components/icons/TeamViewer.tsx new file mode 100644 index 000000000000..4c50e8046910 --- /dev/null +++ b/frontend/pages/SoftwarePage/components/icons/TeamViewer.tsx @@ -0,0 +1,46 @@ +import React from "react"; + +import type { SVGProps } from "react"; + +const TeamViewer = (props: SVGProps) => ( + + + + + + + + + + + + + + + + + + + +); +export default TeamViewer; diff --git a/frontend/pages/SoftwarePage/components/icons/TeamViewerHost.tsx b/frontend/pages/SoftwarePage/components/icons/TeamViewerHost.tsx new file mode 100644 index 000000000000..42632e584af8 --- /dev/null +++ b/frontend/pages/SoftwarePage/components/icons/TeamViewerHost.tsx @@ -0,0 +1,14 @@ +import React from "react"; + +import type { SVGProps } from "react"; + +const AppStore = (props: SVGProps) => ( + + + + +); +export default AppStore; diff --git a/frontend/pages/SoftwarePage/components/icons/WhatsApp.tsx b/frontend/pages/SoftwarePage/components/icons/WhatsApp.tsx new file mode 100644 index 000000000000..a839380f19f8 --- /dev/null +++ b/frontend/pages/SoftwarePage/components/icons/WhatsApp.tsx @@ -0,0 +1,18 @@ +import React from "react"; + +import type { SVGProps } from "react"; + +const WhatsApp = (props: SVGProps) => ( + + + + + +); +export default WhatsApp; diff --git a/frontend/pages/SoftwarePage/components/icons/WindowsDefender.tsx b/frontend/pages/SoftwarePage/components/icons/WindowsDefender.tsx new file mode 100644 index 000000000000..1dbab8f73ca4 --- /dev/null +++ b/frontend/pages/SoftwarePage/components/icons/WindowsDefender.tsx @@ -0,0 +1,14 @@ +import React from "react"; + +import type { SVGProps } from "react"; + +const WindowsDefender = (props: SVGProps) => ( + + + + +); +export default WindowsDefender; diff --git a/frontend/pages/SoftwarePage/components/icons/index.ts b/frontend/pages/SoftwarePage/components/icons/index.ts index 2c8d355f7e98..df129805392e 100644 --- a/frontend/pages/SoftwarePage/components/icons/index.ts +++ b/frontend/pages/SoftwarePage/components/icons/index.ts @@ -23,6 +23,16 @@ import Falcon from "./Falcon"; import AppStore from "./AppStore"; import iOS from "./iOS"; import iPadOS from "./iPadOS"; +import TeamViewer from "./TeamViewer"; +import Box from "./Box"; +import Brave from "./Brave"; +import Cloudflare from "./Cloudflare"; +import Docker from "./Docker"; +import Edge from "./Edge"; +import Figma from "./Figma"; +import Notion from "./Notion"; +import WindowsDefender from "./WindowsDefender"; +import WhatsApp from "./WhatsApp"; // Maps all known Linux platforms to the LinuxOS icon const LINUX_OS_NAME_TO_ICON_MAP = HOST_LINUX_PLATFORMS.reduce( @@ -51,6 +61,16 @@ const SOFTWARE_NAME_TO_ICON_MAP = { chrome: ChromeOS, ios: iOS, ipados: iPadOS, + whatsapp: WhatsApp, + notion: Notion, + figma: Figma, + edge: Edge, + docker: Docker, + cloudflare: Cloudflare, + brave: Brave, + box: Box, + "team viewer": TeamViewer, + "windows defender": WindowsDefender, ...LINUX_OS_NAME_TO_ICON_MAP, } as const; diff --git a/frontend/router/index.tsx b/frontend/router/index.tsx index 7967069567f2..69970397036e 100644 --- a/frontend/router/index.tsx +++ b/frontend/router/index.tsx @@ -81,6 +81,7 @@ import SoftwareAddPage from "pages/SoftwarePage/SoftwareAddPage"; import SoftwareFleetMaintained from "pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained"; import SoftwarePackage from "pages/SoftwarePage/SoftwareAddPage/SoftwarePackage"; import SoftwareAppStore from "pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore"; +import FleetMaintainedAppDetailsPage from "pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage"; import PATHS from "router/paths"; @@ -286,6 +287,10 @@ const routes = ( + diff --git a/frontend/router/paths.ts b/frontend/router/paths.ts index 3d76ced95b55..6da5a9ca2b33 100644 --- a/frontend/router/paths.ts +++ b/frontend/router/paths.ts @@ -76,6 +76,8 @@ export default { return `${URL_PREFIX}/software/vulnerabilities/${cve}`; }, SOFTWARE_ADD_FLEET_MAINTAINED: `${URL_PREFIX}/software/add/fleet-maintained`, + SOFTWARE_FLEET_MAINTAINED_DETAILS: (id: number) => + `${URL_PREFIX}/software/add/fleet-maintained/${id}`, SOFTWARE_ADD_PACKAGE: `${URL_PREFIX}/software/add/package`, SOFTWARE_ADD_APP_STORE: `${URL_PREFIX}/software/add/app-store`, diff --git a/frontend/services/entities/software.ts b/frontend/services/entities/software.ts index b244f6de4d41..47e3076d5589 100644 --- a/frontend/services/entities/software.ts +++ b/frontend/services/entities/software.ts @@ -6,12 +6,19 @@ import { ISoftwareVersion, ISoftwareTitle, ISoftwareTitleDetails, + IFleetMaintainedApp, + IFleetMaintainedAppDetails, } from "interfaces/software"; import { buildQueryStringFromParams, convertParamsToSnakeCase, } from "utilities/url"; import { IPackageFormData } from "pages/SoftwarePage/components/PackageForm/PackageForm"; +import { + createMockFleetMaintainedApp, + createMockFleetMaintainedAppDetails, +} from "__mocks__/softwareMock"; +import { IAddFleetMaintainedData } from "pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetMaintainedAppDetailsPage"; export interface ISoftwareApiParams { page?: number; @@ -102,6 +109,29 @@ export interface ISoftwareInstallTokenResponse { token: string; } +export interface ISoftwareFleetMaintainedAppsResponse { + fleet_maintained_apps: IFleetMaintainedApp[]; + count: number; + counts_updated_at: string | null; + meta: { + has_next_results: boolean; + has_previous_results: boolean; + }; +} + +export interface IFleetMaintainedAppResponse { + fleet_maintained_app: IFleetMaintainedAppDetails; +} + +interface IAddFleetMaintainedAppPostBody { + team_id: number; + fleet_maintained_app_id: number; + pre_install_query?: string; + install_script?: string; + post_install_script?: string; + self_service?: boolean; +} + const ORDER_KEY = "name"; const ORDER_DIRECTION = "asc"; @@ -286,4 +316,62 @@ export default { const path = SOFTWARE_INSTALL_RESULTS(installUuid); return sendRequest("GET", path); }, + + getFleetMaintainedApps: ( + teamId: number + ): Promise => { + const { SOFTWARE_FLEET_MAINTAINED_APPS } = endpoints; + const path = `${SOFTWARE_FLEET_MAINTAINED_APPS}?team_id=${teamId}`; + + return new Promise((resolve) => { + resolve({ + fleet_maintained_apps: [ + createMockFleetMaintainedApp({ + name: "edge", + }), + ], + count: 1, + counts_updated_at: "2021-09-01T00:00:00Z", + meta: { + has_next_results: false, + has_previous_results: false, + }, + }); + }); + + // return sendRequest("GET", path); + }, + + getFleetMainainedApp: (id: number): Promise => { + const { SOFTWARE_FLEET_MAINTAINED_APP } = endpoints; + const path = `${SOFTWARE_FLEET_MAINTAINED_APP(id)}`; + + return new Promise((resolve) => { + resolve({ + fleet_maintained_app: createMockFleetMaintainedAppDetails({ + name: "box", + }), + }); + }); + + // return sendRequest("GET", path); + }, + + addFleetMaintainedApp: ( + teamId: number, + formData: IAddFleetMaintainedData + ) => { + const { SOFTWARE_FLEET_MAINTAINED_APPS } = endpoints; + + const body: IAddFleetMaintainedAppPostBody = { + team_id: teamId, + fleet_maintained_app_id: formData.appId, + pre_install_query: formData.preInstallQuery, + install_script: formData.installScript, + post_install_script: formData.postInstallScript, + self_service: formData.selfService, + }; + + return sendRequest("POST", SOFTWARE_FLEET_MAINTAINED_APPS, body); + }, }; diff --git a/frontend/utilities/endpoints.ts b/frontend/utilities/endpoints.ts index 29524b4c3155..5833a486539c 100644 --- a/frontend/utilities/endpoints.ts +++ b/frontend/utilities/endpoints.ts @@ -174,6 +174,9 @@ export default { `/${API_VERSION}/fleet/software/packages/${id}`, SOFTWARE_AVAILABLE_FOR_INSTALL: (id: number) => `/${API_VERSION}/fleet/software/titles/${id}/available_for_install`, + SOFTWARE_FLEET_MAINTAINED_APPS: `/${API_VERSION}/fleet/software/fleet_maintained_apps`, + SOFTWARE_FLEET_MAINTAINED_APP: (id: number) => + `/${API_VERSION}/fleet/software/fleet_maintained_apps/${id}`, // AI endpoints AUTOFILL_POLICY: `/${API_VERSION}/fleet/autofill/policy`, From 03592906625d4c8901ea4aeb9c2469b766c2259f Mon Sep 17 00:00:00 2001 From: Gabriel Hernandez Date: Fri, 20 Sep 2024 16:15:10 +0100 Subject: [PATCH 013/385] implement add Vpp software UI to new add software pages (#22264) relates to #21811 This adds the UI for the new add vpp software flow. This moves the add app store vpp flow under the App Store (Vpp) Tab panel. This includes: **add vpp software form** ![image](https://github.com/user-attachments/assets/0b8cea88-baea-4da7-88f7-6c392a8af4a6) **has now vpp token setup UI** ![image](https://github.com/user-attachments/assets/40142599-f5d9-4602-a731-786b69a6b8b9) **has no vpp apps UI** ![image](https://github.com/user-attachments/assets/5aa29772-037c-456a-894b-cf99f5f35cb4) - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. - [ ] Added/updated tests - [x] Manual QA for all new/changed functionality --- ...811-add-vpp-flow-to-new-add-software-pages | 0 frontend/components/Card/Card.tsx | 8 +- frontend/components/Card/_styles.scss | 4 + .../SoftwareAddPage/SoftwareAddPage.tsx | 31 +++- .../SoftwareAppStore/SoftwareAppStore.tsx | 23 --- .../SoftwareAppStore/_styles.scss | 3 - .../SoftwareAddPage/SoftwareAppStore/index.ts | 1 - .../AddSoftwareVppForm.tsx} | 162 ++++++++---------- .../AddSoftwareVppForm}/_styles.scss | 108 +++++++----- .../AddSoftwareVppForm}/helpers.tsx | 44 ----- .../AddSoftwareVppForm/index.ts | 1 + .../SoftwareAppStoreVpp.tsx | 83 +++++++++ .../SoftwareAppStoreVpp/_styles.scss | 12 ++ .../SoftwareAppStoreVpp/helpers.ts | 26 +++ .../SoftwareAppStoreVpp/index.ts | 1 + .../components/AppStoreVpp/index.ts | 1 - frontend/router/index.tsx | 2 +- 17 files changed, 292 insertions(+), 218 deletions(-) create mode 100644 changes/21811-add-vpp-flow-to-new-add-software-pages delete mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/SoftwareAppStore.tsx delete mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/_styles.scss delete mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/index.ts rename frontend/pages/SoftwarePage/{components/AppStoreVpp/AppStoreVpp.tsx => SoftwareAddPage/SoftwareAppStoreVpp/AddSoftwareVppForm/AddSoftwareVppForm.tsx} (63%) rename frontend/pages/SoftwarePage/{components/AppStoreVpp => SoftwareAddPage/SoftwareAppStoreVpp/AddSoftwareVppForm}/_styles.scss (53%) rename frontend/pages/SoftwarePage/{components/AppStoreVpp => SoftwareAddPage/SoftwareAppStoreVpp/AddSoftwareVppForm}/helpers.tsx (58%) create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/AddSoftwareVppForm/index.ts create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/SoftwareAppStoreVpp.tsx create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/_styles.scss create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/helpers.ts create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/index.ts delete mode 100644 frontend/pages/SoftwarePage/components/AppStoreVpp/index.ts diff --git a/changes/21811-add-vpp-flow-to-new-add-software-pages b/changes/21811-add-vpp-flow-to-new-add-software-pages new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/frontend/components/Card/Card.tsx b/frontend/components/Card/Card.tsx index 7e42f188a60f..452f57308d2a 100644 --- a/frontend/components/Card/Card.tsx +++ b/frontend/components/Card/Card.tsx @@ -22,7 +22,13 @@ interface ICardProps { * * These correspond to the padding sizes in the design system. Look at * `padding.scss` for values */ - paddingSize?: "small" | "medium" | "large" | "xlarge" | "xxlarge"; + paddingSize?: + | "small" + | "medium" + | "large" + | "xlarge" + | "xxlarge" + | "xxxlarge"; /** * @deprecated Use `paddingSize` prop instead. * diff --git a/frontend/components/Card/_styles.scss b/frontend/components/Card/_styles.scss index 16bb5ad1e43b..dcfa49ca2827 100644 --- a/frontend/components/Card/_styles.scss +++ b/frontend/components/Card/_styles.scss @@ -51,6 +51,10 @@ padding: $pad-xxlarge; } + &__padding-xxxlarge { + padding: $pad-xxxlarge; + } + // 40px padding // TODO: remove when we've replaced all instances of largePadding with // paddingSize prop diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAddPage.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAddPage.tsx index 5e6de90d62df..dbb8bded4c07 100644 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAddPage.tsx +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAddPage.tsx @@ -9,6 +9,7 @@ import { buildQueryStringFromParams } from "utilities/url"; import MainContent from "components/MainContent"; import BackLink from "components/BackLink"; import TabsWrapper from "components/TabsWrapper"; +import { APP_CONTEXT_NO_TEAM_ID } from "interfaces/team"; const baseClass = "software-add-page"; @@ -22,14 +23,14 @@ const addSoftwareSubNav: IAddSoftwareSubNavItem[] = [ name: "Fleet-maintained", pathname: PATHS.SOFTWARE_ADD_FLEET_MAINTAINED, }, - { - name: "Package", - pathname: PATHS.SOFTWARE_ADD_PACKAGE, - }, { name: "App store (VPP)", pathname: PATHS.SOFTWARE_ADD_APP_STORE, }, + { + name: "Custom Package", + pathname: PATHS.SOFTWARE_ADD_PACKAGE, + }, ]; const getTabIndex = (path: string): number => { @@ -62,7 +63,7 @@ const SoftwareAddPage = ({ (i: number): void => { // Only query param to persist between tabs is team id const teamIdParam = buildQueryStringFromParams({ - team_id: location?.query.team_id, + team_id: location.query.team_id, }); const navPath = addSoftwareSubNav[i].pathname.concat(`?${teamIdParam}`); @@ -71,12 +72,28 @@ const SoftwareAddPage = ({ [location, router] ); + // Quick exit if no team_id param. This page must have a team id to function + // correctly. We redirect to the same page with the "No team" context if it + // is not provieded. + if (!location.query.team_id) { + router.replace( + `${location.pathname}?${buildQueryStringFromParams({ + team_id: APP_CONTEXT_NO_TEAM_ID, + })}` + ); + return null; + } + + const backUrl = `${PATHS.SOFTWARE_TITLES}?${buildQueryStringFromParams({ + team_id: location.query.team_id, + })}`; + return ( <>

Add Software

@@ -98,7 +115,7 @@ const SoftwareAddPage = ({ {React.cloneElement(children, { router, - currentTeamId: location.query.team_id, + currentTeamId: parseInt(location.query.team_id, 10), })}
diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/SoftwareAppStore.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/SoftwareAppStore.tsx deleted file mode 100644 index 1d152e87ee3e..000000000000 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/SoftwareAppStore.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from "react"; -import { InjectedRouter } from "react-router"; -import { Location } from "history"; - -import { ISoftwareAddPageQueryParams } from "../SoftwareAddPage"; - -const baseClass = "software-app-store"; - -interface ISoftwareAppStoreProps { - currentTeamId: number; - router: InjectedRouter; - location: Location; -} - -const SoftwareAppStore = ({ - currentTeamId, - router, - location, -}: ISoftwareAppStoreProps) => { - return
Software App store page
; -}; - -export default SoftwareAppStore; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/_styles.scss b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/_styles.scss deleted file mode 100644 index 24c6a329d27c..000000000000 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/_styles.scss +++ /dev/null @@ -1,3 +0,0 @@ -.software-app-store { - -} diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/index.ts b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/index.ts deleted file mode 100644 index 6691b50cd3ca..000000000000 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./SoftwareAppStore"; diff --git a/frontend/pages/SoftwarePage/components/AppStoreVpp/AppStoreVpp.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/AddSoftwareVppForm/AddSoftwareVppForm.tsx similarity index 63% rename from frontend/pages/SoftwarePage/components/AppStoreVpp/AppStoreVpp.tsx rename to frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/AddSoftwareVppForm/AddSoftwareVppForm.tsx index ffde9220a271..12b5db6226f1 100644 --- a/frontend/pages/SoftwarePage/components/AppStoreVpp/AppStoreVpp.tsx +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/AddSoftwareVppForm/AddSoftwareVppForm.tsx @@ -1,44 +1,33 @@ import React, { useContext, useState } from "react"; -import { useQuery } from "react-query"; -import { InjectedRouter } from "react-router"; -import { AxiosError } from "axios"; +import classnames from "classnames"; import PATHS from "router/paths"; -import mdmAppleAPI, { - IGetVppTokensResponse, - IVppApp, -} from "services/entities/mdm_apple"; -import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants"; import { PLATFORM_DISPLAY_NAMES } from "interfaces/platform"; +import mdmAppleAPI, { IVppApp } from "services/entities/mdm_apple"; import { NotificationContext } from "context/notification"; import Card from "components/Card"; import CustomLink from "components/CustomLink"; -import Spinner from "components/Spinner"; -import Button from "components/buttons/Button"; -import DataError from "components/DataError"; import Radio from "components/forms/fields/Radio"; -import Checkbox from "components/forms/fields/Checkbox"; +import Button from "components/buttons/Button"; -import SoftwareIcon from "../icons/SoftwareIcon"; -import { - generateRedirectQueryParams, - getErrorMessage, - getUniqueAppId, - teamHasVPPToken, -} from "./helpers"; +import SoftwareIcon from "pages/SoftwarePage/components/icons/SoftwareIcon"; +import Checkbox from "components/forms/fields/Checkbox"; +import { InjectedRouter } from "react-router"; +import { buildQueryStringFromParams } from "utilities/url"; +import { getErrorMessage, getUniqueAppId } from "./helpers"; -const baseClass = "app-store-vpp"; +const baseClass = "add-software-vpp-form"; const EnableVppCard = () => { return ( - -
+ +

- No Volume Purchasing Program (VPP) token assigned + Volume Purchasing Program (VPP) isn't enabled

- To add App Store apps, assign a VPP token to this team. + To add App Store apps, first enable VPP.

{ }; const NoVppAppsCard = () => ( - -
+ +

You don't have any App Store apps

@@ -129,61 +118,47 @@ const VppAppList = ({ apps, selectedApp, onSelect }: IVppAppListProps) => { ); }; -interface IAppStoreVppProps { +interface IAddSoftwareVppFormProps { teamId: number; + hasVppToken: boolean; router: InjectedRouter; - onExit: () => void; - setAddedSoftwareToken: (token: string) => void; + vppApps?: IVppApp[]; } -const AppStoreVpp = ({ +const AddSoftwareVppForm = ({ teamId, + hasVppToken, router, - onExit, - setAddedSoftwareToken, -}: IAppStoreVppProps) => { + vppApps, +}: IAddSoftwareVppFormProps) => { const { renderFlash } = useContext(NotificationContext); const [isSubmitDisabled, setIsSubmitDisabled] = useState(true); const [selectedApp, setSelectedApp] = useState(null); + const [isUploading, setIsUploading] = useState(false); const [isSelfService, setIsSelfService] = useState(false); - const { - data: vppInfo, - isLoading: isLoadingVppInfo, - error: errorVppInfo, - } = useQuery( - ["vppInfo"], - () => mdmAppleAPI.getVppTokens(), - { - ...DEFAULT_USE_QUERY_OPTIONS, - staleTime: 30000, - retry: (tries, error) => error.status !== 404 && tries <= 3, - } - ); - - const hasVPPToken = teamHasVPPToken(teamId, vppInfo?.vpp_tokens); - - const { - data: vppApps, - isLoading: isLoadingVppApps, - error: errorVppApps, - } = useQuery(["vppSoftware", teamId], () => mdmAppleAPI.getVppApps(teamId), { - ...DEFAULT_USE_QUERY_OPTIONS, - enabled: hasVPPToken, - staleTime: 30000, - select: (res) => res.app_store_apps, - }); + const goBackToSoftwareTitles = (availableInstall?: boolean) => { + router.push( + `${PATHS.SOFTWARE_TITLES}?${buildQueryStringFromParams({ + team_id: teamId, + available_for_install: availableInstall, + })}` + ); + }; const onSelectApp = (app: IVppApp) => { setIsSubmitDisabled(false); setSelectedApp(app); }; - const onAddSoftware = async () => { + const onAddSoftware = async (evt: React.FormEvent) => { + evt.preventDefault(); if (!selectedApp) { return; } + setIsUploading(true); + try { await mdmAppleAPI.addVppApp( teamId, @@ -194,40 +169,30 @@ const AppStoreVpp = ({ renderFlash( "success", <> - {selectedApp.name} successfully added. Go to Host details page - to install software. + {selectedApp.name} successfully added. ); - const queryParams = generateRedirectQueryParams(teamId, isSelfService); - // any unique string - triggers SW refetch - setAddedSoftwareToken(`${Date.now()}`); - router.push(`${PATHS.SOFTWARE}?${queryParams}`); + goBackToSoftwareTitles(true); } catch (e) { renderFlash("error", getErrorMessage(e)); } - onExit(); + + setIsUploading(false); }; const renderContent = () => { - if (isLoadingVppInfo || isLoadingVppApps) { - return ; - } - - if (!hasVPPToken) { + if (!hasVppToken) { return ; } - if (errorVppInfo || errorVppApps) { - return ; - } - if (vppApps) { if (vppApps.length === 0) { return ; } + return ( -
+
); } + return null; }; + const contentWrapperClasses = classnames(`${baseClass}__content-wrapper`, { + [`${baseClass}__content-disabled`]: isUploading, + }); + return ( -
- {renderContent()} -
- - +
+ {isUploading &&
} +
+

Apple App Store apps purchased via Apple Business Manager:

+
+ <>{renderContent()} +
+ + +
+
-
+
); }; -export default AppStoreVpp; +export default AddSoftwareVppForm; diff --git a/frontend/pages/SoftwarePage/components/AppStoreVpp/_styles.scss b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/AddSoftwareVppForm/_styles.scss similarity index 53% rename from frontend/pages/SoftwarePage/components/AppStoreVpp/_styles.scss rename to frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/AddSoftwareVppForm/_styles.scss index 604750a25808..90535061ad8b 100644 --- a/frontend/pages/SoftwarePage/components/AppStoreVpp/_styles.scss +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/AddSoftwareVppForm/_styles.scss @@ -1,88 +1,108 @@ -.app-store-vpp { - margin-top: $pad-large; +.add-software-vpp-form { + position: relative; - &__list-container { - border: 1px solid $ui-fleet-black-10; - border-radius: $border-radius-medium; + &__overlay { + position: absolute; + height: 100%; + width: 100%; + z-index: 1000; } - &__list { - max-height: 315px; - overflow-y: auto; - list-style: none; - margin: 0; - padding: 0; - } - - &__list-item { + &__content-wrapper { display: flex; + flex-direction: column; gap: $pad-medium; - align-items: center; - padding: $pad-small $pad-medium; - border-bottom: 1px solid $ui-fleet-black-10; + } - .app-platform { - color: $ui-fleet-black-50; - } + &__content-disabled { + transition: opacity 150ms ease-in-out; + opacity: 0.5; // this adds a disabled effect to the modal content + } - &:last-child { - border-bottom: none; - } + &__form-content { + display: flex; + flex-direction: column; + gap: $pad-xxlarge; } - &__app-info { + &__form-fields { display: flex; - align-items: center; - gap: $pad-small; + flex-direction: column; + gap: $pad-medium; + } + + + + // enable and no software message styles + &__enable-vpp-card, &__no-software-card { + padding: $pad-xxxlarge auto; } - &__no-software { + &__enable-vpp-message, &__no-software-message { display: flex; flex-direction: column; gap: $pad-small; align-items: center; - padding: $pad-xxlarge 48px; - font-size: $x-small; } - &__no-software-title { + &__enable-vpp-title, &__no-software-title { font-weight: $bold; margin: 0; } - &__no-software-description { + &__enable-vpp-description, &__no-software-description { margin: 0; color: $ui-fleet-black-75; - text-align: center; } - &__error { - margin: $pad-xxlarge 0; + // list styles + &__list-container { + border: 1px solid $ui-fleet-black-10; + border-radius: $border-radius-medium; } - &__modal-body { + &__list { + max-height: 280px; + overflow-y: auto; + list-style: none; + margin: 0; + padding: 0; + } + + &__list-item { display: flex; - flex-direction: column; gap: $pad-medium; + align-items: center; + padding: $pad-small $pad-medium; + border-bottom: 1px solid $ui-fleet-black-10; + + .app-platform { + font-size: $x-small; + color: $ui-fleet-black-50; + } + + &:last-child { + border-bottom: none; + } } - &__enable-vpp { + &__app-info { display: flex; - flex-direction: column; - min-height: 149px; align-items: center; - justify-content: center; gap: $pad-small; - - p { - margin: 0; - } } &__help-text { @include help-text; + .custom-link { font-size: $xx-small; } } + + // button styles + &__action-buttons { + display: flex; + gap: $pad-medium; + } } diff --git a/frontend/pages/SoftwarePage/components/AppStoreVpp/helpers.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/AddSoftwareVppForm/helpers.tsx similarity index 58% rename from frontend/pages/SoftwarePage/components/AppStoreVpp/helpers.tsx rename to frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/AddSoftwareVppForm/helpers.tsx index 76c131769fa1..a0130de802a4 100644 --- a/frontend/pages/SoftwarePage/components/AppStoreVpp/helpers.tsx +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/AddSoftwareVppForm/helpers.tsx @@ -1,8 +1,6 @@ import React from "react"; import { getErrorReason } from "interfaces/errors"; -import { IMdmVppToken } from "interfaces/mdm"; import { IVppApp } from "services/entities/mdm_apple"; -import { buildQueryStringFromParams } from "utilities/url"; const ADD_SOFTWARE_ERROR_PREFIX = "Couldn’t add software."; const DEFAULT_ERROR_MESSAGE = `${ADD_SOFTWARE_ERROR_PREFIX} Please try again.`; @@ -46,45 +44,3 @@ export const getErrorMessage = (e: unknown) => { export const getUniqueAppId = (app: IVppApp) => `${app.app_store_id}_${app.platform}`; - -/** - * Generates the query params for the redirect to the software page. This - * will either generate query params to filter by available for install or - * self service. - */ -export const generateRedirectQueryParams = ( - teamId: number, - isSelfService: boolean -) => { - let queryParams = buildQueryStringFromParams({ team_id: teamId }); - if (isSelfService) { - queryParams = `${queryParams}&self_service=true`; - } else { - queryParams = `${queryParams}&available_for_install=true`; - } - return queryParams; -}; - -/** - * Checks if the given team has an available VPP token (either a token - * that's associated with the team, or a token that's available to "All - * teams") - */ -export const teamHasVPPToken = ( - currentTeamId: number, - tokens?: IMdmVppToken[] -) => { - if (!tokens || tokens.length === 0) { - return false; - } - - return tokens.some((token) => { - // if we've got a non-null, empty array it means the token is available for - // "All teams" - if (token.teams?.length === 0) { - return true; - } - - return token.teams?.some((team) => team.team_id === currentTeamId); - }); -}; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/AddSoftwareVppForm/index.ts b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/AddSoftwareVppForm/index.ts new file mode 100644 index 000000000000..ac71794580d1 --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/AddSoftwareVppForm/index.ts @@ -0,0 +1 @@ +export { default } from "./AddSoftwareVppForm"; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/SoftwareAppStoreVpp.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/SoftwareAppStoreVpp.tsx new file mode 100644 index 000000000000..af4b1ca36889 --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/SoftwareAppStoreVpp.tsx @@ -0,0 +1,83 @@ +import React from "react"; +import { InjectedRouter } from "react-router"; +import { useQuery } from "react-query"; +import { AxiosError } from "axios"; + +import mdmAppleAPI, { + IGetVppTokensResponse, +} from "services/entities/mdm_apple"; +import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants"; + +import DataError from "components/DataError"; +import Spinner from "components/Spinner"; + +import AddSoftwareVppForm from "./AddSoftwareVppForm"; +import { teamHasVPPToken } from "./helpers"; + +const baseClass = "software-app-store-vpp"; + +interface ISoftwareAppStoreProps { + currentTeamId: number; + router: InjectedRouter; +} + +const SoftwareAppStoreVpp = ({ + currentTeamId, + router, +}: ISoftwareAppStoreProps) => { + const { + data: vppInfo, + isLoading: isLoadingVppInfo, + error: errorVppInfo, + } = useQuery( + ["vppInfo", currentTeamId], + () => mdmAppleAPI.getVppTokens(), + { + ...DEFAULT_USE_QUERY_OPTIONS, + staleTime: 30000, + retry: (tries, error) => error.status !== 404 && tries <= 3, + } + ); + + const hasVppToken = teamHasVPPToken(currentTeamId, vppInfo?.vpp_tokens); + + const { + data: vppApps, + isLoading: isLoadingVppApps, + error: errorVppApps, + } = useQuery( + ["vppSoftware", currentTeamId], + () => mdmAppleAPI.getVppApps(currentTeamId), + { + ...DEFAULT_USE_QUERY_OPTIONS, + enabled: hasVppToken, + staleTime: 30000, + select: (res) => res.app_store_apps, + } + ); + + const renderContent = () => { + if (isLoadingVppInfo || isLoadingVppApps) { + return ; + } + + if (errorVppInfo || errorVppApps) { + return ; + } + + return ( +
+ +
+ ); + }; + + return
{renderContent()}
; +}; + +export default SoftwareAppStoreVpp; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/_styles.scss b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/_styles.scss new file mode 100644 index 000000000000..374edf692f44 --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/_styles.scss @@ -0,0 +1,12 @@ +.software-app-store-vpp { + + &__error { + margin-top: $pad-xxlarge; + } + + &__content { + margin-top: $pad-xxlarge; + display: flex; + flex-direction: column; + } +} diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/helpers.ts b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/helpers.ts new file mode 100644 index 000000000000..3bfebace69cc --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/helpers.ts @@ -0,0 +1,26 @@ +import { IMdmVppToken } from "interfaces/mdm"; + +/** + * Checks if the given team has an available VPP token (either a token + * that's associated with the team, or a token that's available to "All + * teams") + */ +// eslint-disable-next-line import/prefer-default-export +export const teamHasVPPToken = ( + currentTeamId: number, + tokens?: IMdmVppToken[] +) => { + if (!tokens || tokens.length === 0) { + return false; + } + + return tokens.some((token) => { + // if we've got a non-null, empty array it means the token is available for + // "All teams" + if (token.teams?.length === 0) { + return true; + } + + return token.teams?.some((team) => team.team_id === currentTeamId); + }); +}; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/index.ts b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/index.ts new file mode 100644 index 000000000000..97e0d258154a --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp/index.ts @@ -0,0 +1 @@ +export { default } from "./SoftwareAppStoreVpp"; diff --git a/frontend/pages/SoftwarePage/components/AppStoreVpp/index.ts b/frontend/pages/SoftwarePage/components/AppStoreVpp/index.ts deleted file mode 100644 index b0dbd383b223..000000000000 --- a/frontend/pages/SoftwarePage/components/AppStoreVpp/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./AppStoreVpp"; diff --git a/frontend/router/index.tsx b/frontend/router/index.tsx index 69970397036e..68e7994f8048 100644 --- a/frontend/router/index.tsx +++ b/frontend/router/index.tsx @@ -80,7 +80,7 @@ import SoftwareVulnerabilityDetailsPage from "pages/SoftwarePage/SoftwareVulnera import SoftwareAddPage from "pages/SoftwarePage/SoftwareAddPage"; import SoftwareFleetMaintained from "pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained"; import SoftwarePackage from "pages/SoftwarePage/SoftwareAddPage/SoftwarePackage"; -import SoftwareAppStore from "pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore"; +import SoftwareAppStore from "pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp"; import FleetMaintainedAppDetailsPage from "pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage"; import PATHS from "router/paths"; From 21c91a2c140776706a254e77364880c9de074120 Mon Sep 17 00:00:00 2001 From: Gabriel Hernandez Date: Mon, 30 Sep 2024 12:34:43 +0100 Subject: [PATCH 014/385] Chore UI fleet maintained api integration (#22467) relates to #22456 integrate the new fleet maintained API with the UI --- .../SoftwareFleetMaintained.tsx | 34 +++++++++++-- .../SoftwareTable/SoftwareTable.tsx | 14 +++--- .../SoftwarePage/components/icons/Postman.tsx | 32 +++++++++++++ .../components/icons/TeamViewer.tsx | 33 +++++++------ .../SoftwarePage/components/icons/index.ts | 8 +++- frontend/services/entities/software.ts | 48 ++++++------------- frontend/utilities/url/index.ts | 4 +- 7 files changed, 110 insertions(+), 63 deletions(-) create mode 100644 frontend/pages/SoftwarePage/components/icons/Postman.tsx diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/SoftwareFleetMaintained.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/SoftwareFleetMaintained.tsx index dcb87bd3b6d8..1c5916416627 100644 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/SoftwareFleetMaintained.tsx +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/SoftwareFleetMaintained.tsx @@ -2,8 +2,12 @@ import React from "react"; import { InjectedRouter } from "react-router"; import { Location } from "history"; import { useQuery } from "react-query"; +import { AxiosError } from "axios"; -import softwareAPI from "services/entities/software"; +import softwareAPI, { + ISoftwareFleetMaintainedAppsQueryParams, + ISoftwareFleetMaintainedAppsResponse, +} from "services/entities/software"; import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants"; import Spinner from "components/Spinner"; @@ -14,6 +18,10 @@ import { ISoftwareAddPageQueryParams } from "../SoftwareAddPage"; const baseClass = "software-fleet-maintained"; +interface IQueryKey extends ISoftwareFleetMaintainedAppsQueryParams { + scope?: "fleet-maintained-apps"; +} + interface ISoftwareFleetMaintainedProps { currentTeamId: number; router: InjectedRouter; @@ -39,9 +47,27 @@ const SoftwareFleetMaintained = ({ } = location.query; const currentPage = page ? parseInt(page, 10) : DEFAULT_PAGE; - const { data, isLoading, isError } = useQuery( - ["fleet-maintained", currentTeamId], - () => softwareAPI.getFleetMaintainedApps(currentTeamId), + const { data, isLoading, isError } = useQuery< + ISoftwareFleetMaintainedAppsResponse, + AxiosError, + ISoftwareFleetMaintainedAppsResponse, + [IQueryKey] + >( + [ + { + scope: "fleet-maintained-apps", + page: currentPage, + per_page: DEFAULT_PAGE_SIZE, + query, + order_direction, + order_key, + team_id: currentTeamId, + }, + ], + (options) => { + delete options.queryKey[0].scope; + return softwareAPI.getFleetMaintainedApps(options.queryKey[0]); + }, { ...DEFAULT_USE_QUERY_OPTIONS, } diff --git a/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareTable.tsx b/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareTable.tsx index 24dd8a95e077..aa4b3e28f697 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareTable.tsx +++ b/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareTable.tsx @@ -251,19 +251,17 @@ const SoftwareTable = ({ }; const handleRowSelect = (row: IRowProps) => { - const hostsBySoftwareParams = showVersions - ? { + const queryParams = showVersions + ? buildQueryStringFromParams({ software_version_id: row.original.id, team_id: teamId, - } - : { + }) + : buildQueryStringFromParams({ software_title_id: row.original.id, team_id: teamId, - }; + }); - const path = `${PATHS.MANAGE_HOSTS}?${buildQueryStringFromParams( - hostsBySoftwareParams - )}`; + const path = `${PATHS.MANAGE_HOSTS}?${queryParams}`; router.push(path); }; diff --git a/frontend/pages/SoftwarePage/components/icons/Postman.tsx b/frontend/pages/SoftwarePage/components/icons/Postman.tsx new file mode 100644 index 000000000000..b261bfe9cd5d --- /dev/null +++ b/frontend/pages/SoftwarePage/components/icons/Postman.tsx @@ -0,0 +1,32 @@ +import React from "react"; + +import type { SVGProps } from "react"; + +const Postman = (props: SVGProps) => ( + + + + + + + + +); +export default Postman; diff --git a/frontend/pages/SoftwarePage/components/icons/TeamViewer.tsx b/frontend/pages/SoftwarePage/components/icons/TeamViewer.tsx index 4c50e8046910..a378357ce9d3 100644 --- a/frontend/pages/SoftwarePage/components/icons/TeamViewer.tsx +++ b/frontend/pages/SoftwarePage/components/icons/TeamViewer.tsx @@ -4,41 +4,46 @@ import type { SVGProps } from "react"; const TeamViewer = (props: SVGProps) => ( - + - - + + diff --git a/frontend/pages/SoftwarePage/components/icons/index.ts b/frontend/pages/SoftwarePage/components/icons/index.ts index df129805392e..1b8b222562b8 100644 --- a/frontend/pages/SoftwarePage/components/icons/index.ts +++ b/frontend/pages/SoftwarePage/components/icons/index.ts @@ -33,6 +33,7 @@ import Figma from "./Figma"; import Notion from "./Notion"; import WindowsDefender from "./WindowsDefender"; import WhatsApp from "./WhatsApp"; +import Postman from "./Postman"; // Maps all known Linux platforms to the LinuxOS icon const LINUX_OS_NAME_TO_ICON_MAP = HOST_LINUX_PLATFORMS.reduce( @@ -49,10 +50,12 @@ const SOFTWARE_NAME_TO_ICON_MAP = { "microsoft excel": Excel, falcon: Falcon, firefox: Firefox, + "mozilla firefox": Firefox, package: Package, safari: Safari, slack: Slack, "microsoft teams": Teams, + "microsoft visual studio code": VisualStudioCode, "visual studio code": VisualStudioCode, "microsoft word": Word, "google chrome": ChromeApp, @@ -64,13 +67,14 @@ const SOFTWARE_NAME_TO_ICON_MAP = { whatsapp: WhatsApp, notion: Notion, figma: Figma, - edge: Edge, + "microsoft edge": Edge, docker: Docker, cloudflare: Cloudflare, brave: Brave, box: Box, - "team viewer": TeamViewer, + teamviewer: TeamViewer, "windows defender": WindowsDefender, + postman: Postman, ...LINUX_OS_NAME_TO_ICON_MAP, } as const; diff --git a/frontend/services/entities/software.ts b/frontend/services/entities/software.ts index 47e3076d5589..ed9323925ae4 100644 --- a/frontend/services/entities/software.ts +++ b/frontend/services/entities/software.ts @@ -14,10 +14,6 @@ import { convertParamsToSnakeCase, } from "utilities/url"; import { IPackageFormData } from "pages/SoftwarePage/components/PackageForm/PackageForm"; -import { - createMockFleetMaintainedApp, - createMockFleetMaintainedAppDetails, -} from "__mocks__/softwareMock"; import { IAddFleetMaintainedData } from "pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetMaintainedAppDetailsPage"; export interface ISoftwareApiParams { @@ -109,6 +105,15 @@ export interface ISoftwareInstallTokenResponse { token: string; } +export interface ISoftwareFleetMaintainedAppsQueryParams { + team_id: number; + query?: string; + order_key?: string; + order_direction?: "asc" | "desc"; + page?: number; + per_page?: number; +} + export interface ISoftwareFleetMaintainedAppsResponse { fleet_maintained_apps: IFleetMaintainedApp[]; count: number; @@ -318,43 +323,18 @@ export default { }, getFleetMaintainedApps: ( - teamId: number + params: ISoftwareFleetMaintainedAppsQueryParams ): Promise => { const { SOFTWARE_FLEET_MAINTAINED_APPS } = endpoints; - const path = `${SOFTWARE_FLEET_MAINTAINED_APPS}?team_id=${teamId}`; - - return new Promise((resolve) => { - resolve({ - fleet_maintained_apps: [ - createMockFleetMaintainedApp({ - name: "edge", - }), - ], - count: 1, - counts_updated_at: "2021-09-01T00:00:00Z", - meta: { - has_next_results: false, - has_previous_results: false, - }, - }); - }); - - // return sendRequest("GET", path); + const queryStr = buildQueryStringFromParams(params); + const path = `${SOFTWARE_FLEET_MAINTAINED_APPS}?${queryStr}`; + return sendRequest("GET", path); }, getFleetMainainedApp: (id: number): Promise => { const { SOFTWARE_FLEET_MAINTAINED_APP } = endpoints; const path = `${SOFTWARE_FLEET_MAINTAINED_APP(id)}`; - - return new Promise((resolve) => { - resolve({ - fleet_maintained_app: createMockFleetMaintainedAppDetails({ - name: "box", - }), - }); - }); - - // return sendRequest("GET", path); + return sendRequest("GET", path); }, addFleetMaintainedApp: ( diff --git a/frontend/utilities/url/index.ts b/frontend/utilities/url/index.ts index cf827047b7d1..c15a910a98df 100644 --- a/frontend/utilities/url/index.ts +++ b/frontend/utilities/url/index.ts @@ -14,6 +14,8 @@ import { API_ALL_TEAMS_ID } from "interfaces/team"; export type QueryValues = string | number | boolean | undefined | null; export type QueryParams = Record; +/** updated value for query params. TODO: update using this value across the codebase */ +type QueryParams2 = { [s in keyof T]: QueryValues }; type FilteredQueryValues = string | number | boolean; type FilteredQueryParams = Record; @@ -91,7 +93,7 @@ const filterEmptyParams = (queryParams: QueryParams) => { * or an empty string on the queryParams object, that key-value pair will be * excluded from the query string. */ -export const buildQueryStringFromParams = (queryParams: QueryParams) => { +export const buildQueryStringFromParams = (queryParams: QueryParams2) => { const filteredParams = filterEmptyParams(queryParams); let queryString = ""; From 1cac6ffbc4a64d24d213fba3453809c6a409247e Mon Sep 17 00:00:00 2001 From: Dante Catalfamo <43040593+dantecatalfamo@users.noreply.github.com> Date: Mon, 30 Sep 2024 11:08:50 -0400 Subject: [PATCH 015/385] Setup Experience Migration (#22405) #22375 --- changes/22375-setup-experience-migration | 1 + ...dInstallDuringSetupToSoftwareInstallers.go | 28 ++++++ ...25112748_AddSetupExperienceResultsTable.go | 91 +++++++++++++++++++ server/datastore/mysql/schema.sql | 47 +++++++++- 4 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 changes/22375-setup-experience-migration create mode 100644 server/datastore/mysql/migrations/tables/20240925111236_AddInstallDuringSetupToSoftwareInstallers.go create mode 100644 server/datastore/mysql/migrations/tables/20240925112748_AddSetupExperienceResultsTable.go diff --git a/changes/22375-setup-experience-migration b/changes/22375-setup-experience-migration new file mode 100644 index 000000000000..7158083881e1 --- /dev/null +++ b/changes/22375-setup-experience-migration @@ -0,0 +1 @@ +- Add database migrations to support Setup Experience diff --git a/server/datastore/mysql/migrations/tables/20240925111236_AddInstallDuringSetupToSoftwareInstallers.go b/server/datastore/mysql/migrations/tables/20240925111236_AddInstallDuringSetupToSoftwareInstallers.go new file mode 100644 index 000000000000..1a3eb3280b9d --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20240925111236_AddInstallDuringSetupToSoftwareInstallers.go @@ -0,0 +1,28 @@ +package tables + +import ( + "database/sql" + "fmt" +) + +func init() { + MigrationClient.AddMigration(Up_20240925111236, Down_20240925111236) +} + +func Up_20240925111236(tx *sql.Tx) error { + _, err := tx.Exec(`ALTER TABLE software_installers ADD COLUMN install_during_setup BOOL NOT NULL DEFAULT false`) + if err != nil { + return fmt.Errorf("failed to add install_during_setup to software_installers: %w", err) + } + + _, err = tx.Exec(`ALTER TABLE vpp_apps_teams ADD COLUMN install_during_setup BOOL NOT NULL DEFAULT false`) + if err != nil { + return fmt.Errorf("failed to add install_during_setup to vpp_apps_teams: %w", err) + } + + return nil +} + +func Down_20240925111236(tx *sql.Tx) error { + return nil +} diff --git a/server/datastore/mysql/migrations/tables/20240925112748_AddSetupExperienceResultsTable.go b/server/datastore/mysql/migrations/tables/20240925112748_AddSetupExperienceResultsTable.go new file mode 100644 index 000000000000..25333056e1cb --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20240925112748_AddSetupExperienceResultsTable.go @@ -0,0 +1,91 @@ +package tables + +import ( + "database/sql" + "fmt" +) + +func init() { + MigrationClient.AddMigration(Up_20240925112748, Down_20240925112748) +} + +func Up_20240925112748(tx *sql.Tx) error { + _, err := tx.Exec(` +CREATE TABLE setup_experience_scripts ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT, + team_id INT UNSIGNED DEFAULT NULL, + global_or_team_id INT UNSIGNED NOT NULL DEFAULT '0', + name VARCHAR(255) COLLATE utf8mb4_unicode_ci NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + script_content_id INT UNSIGNED DEFAULT NULL, + + PRIMARY KEY (id), + + UNIQUE KEY idx_setup_experience_scripts_global_or_team_id_name (global_or_team_id,name), + UNIQUE KEY idx_setup_experience_scripts_team_name (team_id,name), + + KEY idx_script_content_id (script_content_id), + + CONSTRAINT fk_setup_experience_scripts_ibfk_1 FOREIGN KEY (team_id) REFERENCES teams (id) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT fk_setup_experience_scripts_ibfk_2 FOREIGN KEY (script_content_id) REFERENCES script_contents (id) ON DELETE CASCADE +); + +`) + if err != nil { + return fmt.Errorf("failed to create setup_experience_scripts table: %w", err) + } + + _, err = tx.Exec(`ALTER TABLE host_script_results ADD setup_experience_script_id INT UNSIGNED DEFAULT NULL`) + if err != nil { + return fmt.Errorf("failed to add setup_experience_scripts_id key to host_script_results: %w", err) + } + + _, err = tx.Exec(` +ALTER TABLE host_script_results + ADD CONSTRAINT fk_host_script_results_setup_experience_id + FOREIGN KEY (setup_experience_script_id) + REFERENCES setup_experience_scripts (id) ON DELETE SET NULL`) + if err != nil { + return fmt.Errorf("failed to add foreign key constraint for host_script_resutls setup_experience column: %w", err) + } + + _, err = tx.Exec(` +CREATE TABLE setup_experience_status_results ( + id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + host_uuid VARCHAR(255) COLLATE utf8mb4_unicode_ci NOT NULL, + type ENUM('bootstrap-package', 'software-install', 'post-install-script') NOT NULL, + name VARCHAR(255) COLLATE utf8mb4_unicode_ci NOT NULL, + status ENUM('pending', 'running', 'success', 'failure') NOT NULL, + -- Software installs reference + host_software_installs_id INT(10) UNSIGNED, + -- VPP app install reference + nano_command_uuid VARCHAR(255) COLLATE utf8mb4_unicode_ci, + -- Script execution reference + script_execution_id VARCHAR(255) COLLATE utf8mb4_unicode_ci, + error VARCHAR(255) COLLATE utf8mb4_unicode_ci, + + PRIMARY KEY (id), + + KEY idx_setup_experience_scripts_host_uuid (host_uuid), + KEY idx_setup_experience_scripts_hsi_id (host_software_installs_id), + KEY idx_setup_experience_scripts_nano_command_uuid (nano_command_uuid), + KEY idx_setup_experience_scripts_script_execution_id (script_execution_id), + + CONSTRAINT fk_setup_experience_status_results_hsi_id FOREIGN KEY (host_software_installs_id) REFERENCES host_software_installs(id) ON DELETE CASCADE +) +`) + // Service layer state machine like SetupExperienceNestStep()? + // Called from each of the three endpoints (software install, vpp + // mdm, scripts) involved in the setup when an eligible installer + // writes its results + if err != nil { + return fmt.Errorf("failed to create setup_experience_status_results table: %w", err) + } + + return nil +} + +func Down_20240925112748(tx *sql.Tx) error { + return nil +} diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql index 8fb66675fb14..e50197c1e515 100644 --- a/server/datastore/mysql/schema.sql +++ b/server/datastore/mysql/schema.sql @@ -498,6 +498,7 @@ CREATE TABLE `host_script_results` ( `script_content_id` int unsigned DEFAULT NULL, `host_deleted_at` timestamp NULL DEFAULT NULL, `timeout` int DEFAULT NULL, + `setup_experience_script_id` int unsigned DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `idx_host_script_results_execution_id` (`execution_id`), KEY `idx_host_script_results_host_exit_created` (`host_id`,`exit_code`,`created_at`), @@ -505,7 +506,9 @@ CREATE TABLE `host_script_results` ( KEY `idx_host_script_created_at` (`host_id`,`script_id`,`created_at`), KEY `fk_host_script_results_user_id` (`user_id`), KEY `script_content_id` (`script_content_id`), + KEY `fk_host_script_results_setup_experience_id` (`setup_experience_script_id`), CONSTRAINT `fk_host_script_results_script_id` FOREIGN KEY (`script_id`) REFERENCES `scripts` (`id`) ON DELETE SET NULL, + CONSTRAINT `fk_host_script_results_setup_experience_id` FOREIGN KEY (`setup_experience_script_id`) REFERENCES `setup_experience_scripts` (`id`) ON DELETE SET NULL, CONSTRAINT `fk_host_script_results_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL, CONSTRAINT `host_script_results_ibfk_1` FOREIGN KEY (`script_content_id`) REFERENCES `script_contents` (`id`) ON DELETE CASCADE ) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; @@ -1038,9 +1041,9 @@ CREATE TABLE `migration_status_tables` ( `tstamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `id` (`id`) -) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=313 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=315 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; -INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'); +INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20240925111236,1,'2020-01-01 01:01:01'),(314,20240925112748,1,'2020-01-01 01:01:01'); /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `mobile_device_management_solutions` ( @@ -1597,6 +1600,44 @@ CREATE TABLE `sessions` ( /*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `setup_experience_scripts` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `team_id` int unsigned DEFAULT NULL, + `global_or_team_id` int unsigned NOT NULL DEFAULT '0', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `script_content_id` int unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `idx_setup_experience_scripts_global_or_team_id_name` (`global_or_team_id`,`name`), + UNIQUE KEY `idx_setup_experience_scripts_team_name` (`team_id`,`name`), + KEY `idx_script_content_id` (`script_content_id`), + CONSTRAINT `fk_setup_experience_scripts_ibfk_1` FOREIGN KEY (`team_id`) REFERENCES `teams` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_setup_experience_scripts_ibfk_2` FOREIGN KEY (`script_content_id`) REFERENCES `script_contents` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `setup_experience_status_results` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `host_uuid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `type` enum('bootstrap-package','software-install','post-install-script') COLLATE utf8mb4_unicode_ci NOT NULL, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `status` enum('pending','running','success','failure') COLLATE utf8mb4_unicode_ci NOT NULL, + `host_software_installs_id` int unsigned DEFAULT NULL, + `nano_command_uuid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `script_execution_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `error` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_setup_experience_scripts_host_uuid` (`host_uuid`), + KEY `idx_setup_experience_scripts_hsi_id` (`host_software_installs_id`), + KEY `idx_setup_experience_scripts_nano_command_uuid` (`nano_command_uuid`), + KEY `idx_setup_experience_scripts_script_execution_id` (`script_execution_id`), + CONSTRAINT `fk_setup_experience_status_results_hsi_id` FOREIGN KEY (`host_software_installs_id`) REFERENCES `host_software_installs` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `software` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, @@ -1686,6 +1727,7 @@ CREATE TABLE `software_installers` ( `extension` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', `uninstall_script_content_id` int unsigned NOT NULL, `updated_at` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + `install_during_setup` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `idx_software_installers_team_id_title_id` (`global_or_team_id`,`title_id`), KEY `fk_software_installers_title` (`title_id`), @@ -1815,6 +1857,7 @@ CREATE TABLE `vpp_apps_teams` ( `platform` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, `self_service` tinyint(1) NOT NULL DEFAULT '0', `vpp_token_id` int unsigned NOT NULL, + `install_during_setup` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `idx_global_or_team_id_adam_id` (`global_or_team_id`,`adam_id`,`platform`), KEY `team_id` (`team_id`), From a7718b8866f4329a2de1fff5cd0672f7c6896694 Mon Sep 17 00:00:00 2001 From: Gabriel Hernandez Date: Mon, 30 Sep 2024 17:28:53 +0100 Subject: [PATCH 016/385] UI polish for the fleet maintained feature. (#22509) Carious small fixes to give the fleet maintained UI a bit of polish. --- .../FleetMaintainedAppsTable.tsx | 27 +++++++++++++++---- .../FleetMaintainedAppsTable/_styles.scss | 2 +- .../SoftwareFleetMaintained.tsx | 17 ++++++++---- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/FleetMaintainedAppsTable.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/FleetMaintainedAppsTable.tsx index 4cdc17270404..525b153d0bcd 100644 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/FleetMaintainedAppsTable.tsx +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/FleetMaintainedAppsTable.tsx @@ -5,16 +5,32 @@ import PATHS from "router/paths"; import { ISoftwareFleetMaintainedAppsResponse } from "services/entities/software"; import { getNextLocationPath } from "utilities/helpers"; import { buildQueryStringFromParams } from "utilities/url"; +import { IFleetMaintainedApp } from "interfaces/software"; import TableContainer from "components/TableContainer"; import TableCount from "components/TableContainer/TableCount"; import LastUpdatedText from "components/LastUpdatedText"; import { ITableQueryData } from "components/TableContainer/TableContainer"; +import EmptyTable from "components/EmptyTable"; +import CustomLink from "components/CustomLink"; import { generateTableConfig } from "./FleetMaintainedAppsTableConfig"; const baseClass = "fleet-maintained-apps-table"; +const EmptyFleetAppsTable = () => ( + + Can' find app?{" "} + + + } + /> +); + interface IFleetMaintainedAppsTableProps { teamId: number; isLoading: boolean; @@ -98,9 +114,10 @@ const FleetMaintainedAppsTable = ({ [determineQueryParamChange, generateNewQueryParams, router] ); - const handleRowClick = () => { - // TODO: change to correct path - const path = `${PATHS.MANAGE_HOSTS}?${buildQueryStringFromParams({ + const handleRowClick = (row: IFleetMaintainedApp) => { + const path = `${PATHS.SOFTWARE_FLEET_MAINTAINED_DETAILS( + row.id + )}?${buildQueryStringFromParams({ team_id: teamId, })}`; @@ -135,13 +152,13 @@ const FleetMaintainedAppsTable = ({ }; return ( - + className={baseClass} columnConfigs={tableHeadersConfig} data={data?.fleet_maintained_apps ?? []} isLoading={isLoading} resultsTitle="items" - emptyComponent={() => <>EMPTY STATE TODO} + emptyComponent={EmptyFleetAppsTable} defaultSortHeader={orderKey} defaultSortDirection={orderDirection} defaultPageIndex={currentPage} diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/_styles.scss b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/_styles.scss index 223887888044..63548e51d91f 100644 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/_styles.scss +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/_styles.scss @@ -1,3 +1,3 @@ .fleet-maintained-apps-table { - + margin-top: $pad-xxlarge; } diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/SoftwareFleetMaintained.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/SoftwareFleetMaintained.tsx index 1c5916416627..7a2f12f6aa94 100644 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/SoftwareFleetMaintained.tsx +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/SoftwareFleetMaintained.tsx @@ -3,6 +3,7 @@ import { InjectedRouter } from "react-router"; import { Location } from "history"; import { useQuery } from "react-query"; import { AxiosError } from "axios"; +import { omit } from "lodash"; import softwareAPI, { ISoftwareFleetMaintainedAppsQueryParams, @@ -18,6 +19,12 @@ import { ISoftwareAddPageQueryParams } from "../SoftwareAddPage"; const baseClass = "software-fleet-maintained"; +const DATA_STALE_TIME = 30000; +const QUERY_OPTIONS = { + keepPreviousData: true, + staleTime: DATA_STALE_TIME, +}; + interface IQueryKey extends ISoftwareFleetMaintainedAppsQueryParams { scope?: "fleet-maintained-apps"; } @@ -47,7 +54,7 @@ const SoftwareFleetMaintained = ({ } = location.query; const currentPage = page ? parseInt(page, 10) : DEFAULT_PAGE; - const { data, isLoading, isError } = useQuery< + const { data, isLoading, isFetching, isError } = useQuery< ISoftwareFleetMaintainedAppsResponse, AxiosError, ISoftwareFleetMaintainedAppsResponse, @@ -64,12 +71,12 @@ const SoftwareFleetMaintained = ({ team_id: currentTeamId, }, ], - (options) => { - delete options.queryKey[0].scope; - return softwareAPI.getFleetMaintainedApps(options.queryKey[0]); + ({ queryKey: [queryKey] }) => { + return softwareAPI.getFleetMaintainedApps(omit(queryKey, "scope")); }, { ...DEFAULT_USE_QUERY_OPTIONS, + ...QUERY_OPTIONS, } ); @@ -85,7 +92,7 @@ const SoftwareFleetMaintained = ({
Date: Mon, 30 Sep 2024 17:54:14 +0100 Subject: [PATCH 017/385] Handle `query` query param for the fleet maintained apps list endpoint (#22501) support the `query` query param for the `GET /fleet_maintained_apps` endpoint --- server/datastore/mysql/maintained_apps.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/server/datastore/mysql/maintained_apps.go b/server/datastore/mysql/maintained_apps.go index 9668b7069866..6be77a458362 100644 --- a/server/datastore/mysql/maintained_apps.go +++ b/server/datastore/mysql/maintained_apps.go @@ -128,7 +128,15 @@ WHERE NOT EXISTS ( ) )` - stmtPaged, args := appendListOptionsWithCursorToSQL(stmt, []any{teamID, teamID}, &opt) + args := []any{teamID, teamID} + + if match := opt.MatchQuery; match != "" { + match = likePattern(match) + stmt += ` AND (fla.name LIKE ?)` + args = append(args, match) + } + + stmtPaged, args := appendListOptionsWithCursorToSQL(stmt, args, &opt) var avail []fleet.MaintainedApp if err := sqlx.SelectContext(ctx, ds.reader(ctx), &avail, stmtPaged, args...); err != nil { From 825a36b0ba1939ff646ab495f84325bc8aedf532 Mon Sep 17 00:00:00 2001 From: Sam Pfluger <108141731+Sampfluger88@users.noreply.github.com> Date: Mon, 30 Sep 2024 15:59:00 -0500 Subject: [PATCH 018/385] Update digital-experience.rituals.yml (#22521) --- handbook/demand/README.md | 14 -------------- handbook/digital-experience/README.md | 14 ++++++++++++++ .../digital-experience.rituals.yml | 10 ++++++++++ 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/handbook/demand/README.md b/handbook/demand/README.md index 69dd7a2d843f..9d3f6eb5d561 100644 --- a/handbook/demand/README.md +++ b/handbook/demand/README.md @@ -31,20 +31,6 @@ Fleet's public relations firm is directly responsible for the accuracy of event 2. Update the workbook with the latest location, dates, and CFP deadlines from the website. -### Respond to a "Contact us" submission - -1. Check the [_from-prospective-customers](https://fleetdm.slack.com/archives/C01HE9GQW6B) Slack channel for "Contact us" submissions. -2. Mark submission as seen with the "👀" emoji. -3. Within 4 business hours, use the [best practices template (private Google doc)](https://docs.google.com/document/d/1D02k0tc5v-sEJ4uahAouuqnvZ6phxA_gP-IqmkBdMTE/edit) to respond to general asks. -4. Answer any technical questions to the best of your ability. If you are unable to answer a technical/product question, ask a Solutions Consultant in `#help-solutions-consulting`. If an SC is unavailable, post in `#g-mdm`or `#g-endpoint-ops`and notify @on-call. -5. log in to [Salesforce](https://fleetdm.lightning.force.com/lightning/o/Lead/list?filterName=00B4x00000DtaRDEAZ) and search the lead list by first name and match the corresponding email to find the right lead. -6. Enrich each lead with company information and buying situation. -7. If a lead is completed or out of ICP, update the lead status in Salesforce to "Closed" or "Disqualified". If within ICP at-mention the [Head of Digital Experience](https://fleetdm.com/handbook/digital-experience#team) in the [#g-digital-experience](https://fleetdm.slack.com/archives/C058S8PFSK0) Slack channel and move lead to their name in SFDC. -8. Mark the Slack message as complete with the "✅" emoji. - -> For any support-related questions, forward the submission to [Fleet's support team](https://docs.google.com/document/d/1tE-NpNfw1icmU2MjYuBRib0VWBPVAdmq4NiCrpuI0F0/edit#heading=h.wqalwz1je6rq). - - ### Begin or modify an advertising campaign Any new ads or changes to current running ads are approved in ["🦢🗣 Design review (#g-digital-experience)"](https://app.zenhub.com/workspaces/-g-digital-experience-6451748b4eb15200131d4bab/board?sprints=none). diff --git a/handbook/digital-experience/README.md b/handbook/digital-experience/README.md index 34f77f2b9fe3..b634a973ec1f 100644 --- a/handbook/digital-experience/README.md +++ b/handbook/digital-experience/README.md @@ -765,6 +765,20 @@ Fleet has several brand fronts that need to be updated from time to time. Check - The current [brand imagery](https://www.figma.com/design/1J2yxqH8Q7u8V7YTtA1iej/Social-media-(logos%2C-covers%2C-banners)?node-id=3962-65895). Check this [Loom video](https://www.loom.com/share/4432646cc9614046aaa4a74da1c0adb5?sid=2f84779f-f0bd-4055-be69-282c5a16f5c5) for more info. +### Respond to a "Contact us" submission + +1. Check the [_from-prospective-customers](https://fleetdm.slack.com/archives/C01HE9GQW6B) Slack channel for "Contact us" submissions. +2. Mark submission as seen with the "👀" emoji. +3. Within 4 business hours, use the [best practices template (private Google doc)](https://docs.google.com/document/d/1D02k0tc5v-sEJ4uahAouuqnvZ6phxA_gP-IqmkBdMTE/edit) to respond to general asks. +4. Answer any technical questions to the best of your ability. If you are unable to answer a technical/product question, ask a Solutions Consultant in `#help-solutions-consulting`. If an SC is unavailable, post in `#g-mdm`or `#g-endpoint-ops`and notify @on-call. +5. log in to [Salesforce](https://fleetdm.lightning.force.com/lightning/o/Lead/list?filterName=00B4x00000DtaRDEAZ) and search the lead list by first name and match the corresponding email to find the right lead. +6. Enrich each lead with company information and buying situation. +7. If a lead is completed or out of ICP, update the lead status in Salesforce to "Closed" or "Disqualified". If within ICP at-mention the [Head of Digital Experience](https://fleetdm.com/handbook/digital-experience#team) in the [#g-digital-experience](https://fleetdm.slack.com/archives/C058S8PFSK0) Slack channel and move lead to their name in SFDC. +8. Mark the Slack message as complete with the "✅" emoji. + +> For any support-related questions, forward the submission to [Fleet's support team](https://docs.google.com/document/d/1tE-NpNfw1icmU2MjYuBRib0VWBPVAdmq4NiCrpuI0F0/edit#heading=h.wqalwz1je6rq). + + ## Rituals - Note: Some rituals (⏰) are especially time-sensitive and require attention multiple times (3+) per day. Set reminders for the following times (CT): diff --git a/handbook/digital-experience/digital-experience.rituals.yml b/handbook/digital-experience/digital-experience.rituals.yml index c36e75db31ec..f7f1f8d5aedc 100644 --- a/handbook/digital-experience/digital-experience.rituals.yml +++ b/handbook/digital-experience/digital-experience.rituals.yml @@ -9,6 +9,16 @@ autoIssue: labels: [ "#g-digital-experience" ] repo: "fleet" +- + task: "Confirm consultant hours" + startedOn: "2024-09-30" + frequency: "Weekly" + description: "Perform step three in “Inform managers about hours worked” responsibility" + moreInfoUrl: "https://fleetdm.com/handbook/digital-experience#inform-managers-about-hours-worked" + dri: "SFriendLee" + autoIssue: + labels: [ "#g-digital-experience" ] + repo: "fleet" - task: "Prep 1:1s for OKR planning" startedOn: "2024-09-09" From 658431e17fd76f7cdda9f3d3bec9d0a31fc0e052 Mon Sep 17 00:00:00 2001 From: Tim Lee Date: Mon, 30 Sep 2024 15:39:17 -0600 Subject: [PATCH 019/385] Query optimization on Hosts query stats (#22417) --- changes/22094-query-optimization | 1 + server/datastore/mysql/hosts.go | 23 +++- ...40930171917_AddScheduleAutomationsIndex.go | 33 ++++++ ...171917_AddScheduleAutomationsIndex_test.go | 104 ++++++++++++++++++ server/datastore/mysql/schema.sql | 6 +- tools/seed_data/queries/seed_queries.go | 74 +++++++++++++ .../seed_data/{ => vulnerabilities}/README.md | 0 .../{ => vulnerabilities}/seed_vuln_data.go | 0 .../{ => vulnerabilities}/software-macos.csv | 0 .../{ => vulnerabilities}/software-ubuntu.csv | 0 .../{ => vulnerabilities}/software-win.csv | 0 11 files changed, 236 insertions(+), 5 deletions(-) create mode 100644 changes/22094-query-optimization create mode 100644 server/datastore/mysql/migrations/tables/20240930171917_AddScheduleAutomationsIndex.go create mode 100644 server/datastore/mysql/migrations/tables/20240930171917_AddScheduleAutomationsIndex_test.go create mode 100644 tools/seed_data/queries/seed_queries.go rename tools/seed_data/{ => vulnerabilities}/README.md (100%) rename tools/seed_data/{ => vulnerabilities}/seed_vuln_data.go (100%) rename tools/seed_data/{ => vulnerabilities}/software-macos.csv (100%) rename tools/seed_data/{ => vulnerabilities}/software-ubuntu.csv (100%) rename tools/seed_data/{ => vulnerabilities}/software-win.csv (100%) diff --git a/changes/22094-query-optimization b/changes/22094-query-optimization new file mode 100644 index 000000000000..bd779b451346 --- /dev/null +++ b/changes/22094-query-optimization @@ -0,0 +1 @@ +- Increased performance for Host details and Fleet Desktop, particularly in environments using high volumes of live queries \ No newline at end of file diff --git a/server/datastore/mysql/hosts.go b/server/datastore/mysql/hosts.go index ee23749de80a..6026dcd9b140 100644 --- a/server/datastore/mysql/hosts.go +++ b/server/datastore/mysql/hosts.go @@ -403,13 +403,17 @@ func loadHostPackStatsDB(ctx context.Context, db sqlx.QueryerContext, hid uint, return ps, nil } +// loadHostScheduledQueryStatsDB will load all the scheduled query stats for the given host. +// The filter is split into two statements joined by a UNION ALL to take advantage of indexes. +// Using an OR in the WHERE clause causes a full table scan which causes issues with a large +// queries table due to the high volume of live queries (created by zero trust workflows) func loadHostScheduledQueryStatsDB(ctx context.Context, db sqlx.QueryerContext, hid uint, hostPlatform string, teamID *uint) ([]fleet.QueryStats, error) { var teamID_ uint if teamID != nil { teamID_ = *teamID } - sqlQuery := ` + baseQuery := ` SELECT q.id, q.name, @@ -442,12 +446,19 @@ func loadHostScheduledQueryStatsDB(ctx context.Context, db sqlx.QueryerContext, SUM(stats.wall_time) AS wall_time FROM scheduled_query_stats stats WHERE stats.host_id = ? GROUP BY stats.scheduled_query_id) as sqs ON (q.id = sqs.scheduled_query_id) LEFT JOIN query_results qr ON (q.id = qr.query_id AND qr.host_id = ?) + ` + + filter1 := ` WHERE (q.platform = '' OR q.platform IS NULL OR FIND_IN_SET(?, q.platform) != 0) - AND q.schedule_interval > 0 + AND q.is_scheduled = 1 AND (q.automations_enabled IS TRUE OR (q.discard_data IS FALSE AND q.logging_type = ?)) AND (q.team_id IS NULL OR q.team_id = ?) - OR EXISTS ( + GROUP BY q.id + ` + + filter2 := ` + WHERE EXISTS ( SELECT 1 FROM query_results WHERE query_results.query_id = q.id AND query_results.host_id = ? @@ -455,6 +466,8 @@ func loadHostScheduledQueryStatsDB(ctx context.Context, db sqlx.QueryerContext, GROUP BY q.id ` + sqlQuery := baseQuery + filter1 + " UNION ALL " + baseQuery + filter2 + args := []interface{}{ pastDate, hid, @@ -462,8 +475,12 @@ func loadHostScheduledQueryStatsDB(ctx context.Context, db sqlx.QueryerContext, fleet.PlatformFromHost(hostPlatform), fleet.LoggingSnapshot, teamID_, + pastDate, + hid, + hid, hid, } + var stats []fleet.QueryStats if err := sqlx.SelectContext(ctx, db, &stats, sqlQuery, args...); err != nil { return nil, ctxerr.Wrap(ctx, err, "load query stats") diff --git a/server/datastore/mysql/migrations/tables/20240930171917_AddScheduleAutomationsIndex.go b/server/datastore/mysql/migrations/tables/20240930171917_AddScheduleAutomationsIndex.go new file mode 100644 index 000000000000..592e8349df32 --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20240930171917_AddScheduleAutomationsIndex.go @@ -0,0 +1,33 @@ +package tables + +import ( + "database/sql" + "fmt" +) + +func init() { + MigrationClient.AddMigration(Up_20240930171917, Down_20240930171917) +} + +func Up_20240930171917(tx *sql.Tx) error { + _, err := tx.Exec(` + ALTER TABLE queries + ADD COLUMN is_scheduled BOOLEAN GENERATED ALWAYS AS (schedule_interval > 0) STORED NOT NULL + `) + if err != nil { + return fmt.Errorf("error creating generated column is_scheduled: %w", err) + } + + _, err = tx.Exec(` + CREATE INDEX idx_queries_schedule_automations ON queries (is_scheduled, automations_enabled) + `) + if err != nil { + return fmt.Errorf("error creating index idx_queries_schedule_automations: %w", err) + } + + return nil +} + +func Down_20240930171917(tx *sql.Tx) error { + return nil +} diff --git a/server/datastore/mysql/migrations/tables/20240930171917_AddScheduleAutomationsIndex_test.go b/server/datastore/mysql/migrations/tables/20240930171917_AddScheduleAutomationsIndex_test.go new file mode 100644 index 000000000000..27f7d814c52b --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20240930171917_AddScheduleAutomationsIndex_test.go @@ -0,0 +1,104 @@ +package tables + +import ( + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestUp_20240930171917(t *testing.T) { + db := applyUpToPrev(t) + + // + // Insert data to test the migration + // + // ... + + // Apply current migration. + applyNext(t, db) + + // Assert the index was created. + rows, err := db.Query("SHOW INDEX FROM queries WHERE Key_name = 'idx_queries_schedule_automations'") + require.NoError(t, err) + defer rows.Close() + + var indexCount int + for rows.Next() { + indexCount++ + } + + require.NoError(t, rows.Err()) + require.Greater(t, indexCount, 0) + + // + // Assert the index is used when there are rows in the queries table + // (wrong index is used when there are no rows in the queries table) + // + + stmtPrefix := "INSERT INTO `queries` (`saved`, `name`, `description`, `query`, `author_id`, `observer_can_run`, `team_id`, `team_id_char`, `platform`, `min_osquery_version`, `schedule_interval`, `automations_enabled`, `logging_type`, `discard_data`) VALUES " + stmtSuffix := ";" + + var valueStrings []string + var valueArgs []interface{} + + // Generate 10 records + for i := 0; i < 10; i++ { + queryID := i + 1 + valueStrings = append(valueStrings, "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") + valueArgs = append(valueArgs, 0, fmt.Sprintf("query_%d", queryID), "", "SELECT * FROM processes;", 1, 0, nil, "", "", "", 0, 0, "snapshot", 0) + } + + // Disable foreign key checks to improve performance + _, err = db.Exec("SET FOREIGN_KEY_CHECKS=0") + require.NoError(t, err) + + // Construct and execute the batch insert + stmt := stmtPrefix + strings.Join(valueStrings, ",") + stmtSuffix + _, err = db.Exec(stmt, valueArgs...) + require.NoError(t, err) + + // Re-enable foreign key checks + _, err = db.Exec(`SET FOREIGN_KEY_CHECKS=1`) + require.NoError(t, err) + + result := struct { + ID int `db:"id"` + SelectType string `db:"select_type"` + Table string `db:"table"` + Type string `db:"type"` + PossibleKeys *string `db:"possible_keys"` + Key *string `db:"key"` + KeyLen *int `db:"key_len"` + Ref *string `db:"ref"` + Rows int `db:"rows"` + Filtered float64 `db:"filtered"` + Extra *string `db:"Extra"` + Partitions *string `db:"partitions"` + }{} + + // Query based on loadHostScheduledQueryStatsDB in server/datastore/mysql/hosts.go + err = db.Get(&result, ` + EXPLAIN + SELECT + q.id + FROM + queries q + WHERE (q.platform = '' + OR q.platform IS NULL + OR FIND_IN_SET('darwin', q.platform) != 0) + AND q.is_scheduled = 1 + AND(q.automations_enabled IS TRUE + OR(q.discard_data IS FALSE + AND q.logging_type = 'snapshot')) + AND(q.team_id IS NULL + OR q.team_id = 0) + GROUP BY + q.id +`) + require.NoError(t, err) + + // Assert the correct index is used + require.Equal(t, *result.Key, "idx_queries_schedule_automations") +} diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql index 3a5eef4003e5..58a5ec1eb4d3 100644 --- a/server/datastore/mysql/schema.sql +++ b/server/datastore/mysql/schema.sql @@ -1038,9 +1038,9 @@ CREATE TABLE `migration_status_tables` ( `tstamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `id` (`id`) -) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=314 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=315 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; -INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20240927081858,1,'2020-01-01 01:01:01'); +INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20240927081858,1,'2020-01-01 01:01:01'),(314,20240930171917,1,'2020-01-01 01:01:01'); /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `mobile_device_management_solutions` ( @@ -1459,11 +1459,13 @@ CREATE TABLE `queries` ( `automations_enabled` tinyint unsigned NOT NULL DEFAULT '0', `logging_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'snapshot', `discard_data` tinyint(1) NOT NULL DEFAULT '1', + `is_scheduled` tinyint(1) GENERATED ALWAYS AS ((`schedule_interval` > 0)) STORED NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `idx_team_id_name_unq` (`team_id_char`,`name`), UNIQUE KEY `idx_name_team_id_unq` (`name`,`team_id_char`), KEY `author_id` (`author_id`), KEY `idx_team_id_saved_auto_interval` (`team_id`,`saved`,`automations_enabled`,`schedule_interval`), + KEY `idx_queries_schedule_automations` (`is_scheduled`,`automations_enabled`), CONSTRAINT `queries_ibfk_1` FOREIGN KEY (`author_id`) REFERENCES `users` (`id`) ON DELETE SET NULL, CONSTRAINT `queries_ibfk_2` FOREIGN KEY (`team_id`) REFERENCES `teams` (`id`) ON DELETE CASCADE ) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/tools/seed_data/queries/seed_queries.go b/tools/seed_data/queries/seed_queries.go new file mode 100644 index 000000000000..857fe4b2b4f7 --- /dev/null +++ b/tools/seed_data/queries/seed_queries.go @@ -0,0 +1,74 @@ +package main + +import ( + "database/sql" + "fmt" + "log" + "strings" + + _ "github.com/go-sql-driver/mysql" +) + +const ( + batchSize = 1000 + totalRecords = 1000000 +) + +func main() { + // MySQL connection details from your Docker Compose file + user := "fleet" + password := "insecure" + host := "localhost" // Assuming you are running this script on the same host as Docker + port := "3306" + database := "fleet" + + // Construct the MySQL DSN (Data Source Name) + dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", user, password, host, port, database) + + // Open MySQL connection + db, err := sql.Open("mysql", dsn) + if err != nil { + log.Fatal(err) + } + defer db.Close() + + // Disable foreign key checks to improve performance + _, err = db.Exec("SET FOREIGN_KEY_CHECKS=0") + if err != nil { + log.Fatal(err) + } + + // Prepare the insert statement + stmtPrefix := "INSERT INTO `queries` (`saved`, `name`, `description`, `query`, `author_id`, `observer_can_run`, `team_id`, `team_id_char`, `platform`, `min_osquery_version`, `schedule_interval`, `automations_enabled`, `logging_type`, `discard_data`) VALUES " + stmtSuffix := ";" + + // Insert records in batches + for batch := 0; batch < totalRecords/batchSize; batch++ { + var valueStrings []string + var valueArgs []interface{} + + // Generate batch of 1000 records + for i := 0; i < batchSize; i++ { + queryID := batch*batchSize + i + 1 + valueStrings = append(valueStrings, "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") + valueArgs = append(valueArgs, 0, fmt.Sprintf("query_%d", queryID), "", "SELECT * FROM processes;", 1, 0, nil, "", "", "", 0, 0, "snapshot", 0) + } + + // Construct and execute the batch insert + stmt := stmtPrefix + strings.Join(valueStrings, ",") + stmtSuffix + _, err := db.Exec(stmt, valueArgs...) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("Inserted batch %d/%d\n", batch+1, totalRecords/batchSize) + } + + // Re-enable foreign key checks + _, err = db.Exec("SET FOREIGN_KEY_CHECKS=1") + if err != nil { + log.Fatal(err) + } + + fmt.Println("Finished inserting 1 million records.") +} diff --git a/tools/seed_data/README.md b/tools/seed_data/vulnerabilities/README.md similarity index 100% rename from tools/seed_data/README.md rename to tools/seed_data/vulnerabilities/README.md diff --git a/tools/seed_data/seed_vuln_data.go b/tools/seed_data/vulnerabilities/seed_vuln_data.go similarity index 100% rename from tools/seed_data/seed_vuln_data.go rename to tools/seed_data/vulnerabilities/seed_vuln_data.go diff --git a/tools/seed_data/software-macos.csv b/tools/seed_data/vulnerabilities/software-macos.csv similarity index 100% rename from tools/seed_data/software-macos.csv rename to tools/seed_data/vulnerabilities/software-macos.csv diff --git a/tools/seed_data/software-ubuntu.csv b/tools/seed_data/vulnerabilities/software-ubuntu.csv similarity index 100% rename from tools/seed_data/software-ubuntu.csv rename to tools/seed_data/vulnerabilities/software-ubuntu.csv diff --git a/tools/seed_data/software-win.csv b/tools/seed_data/vulnerabilities/software-win.csv similarity index 100% rename from tools/seed_data/software-win.csv rename to tools/seed_data/vulnerabilities/software-win.csv From 937627f4eab1cbc0591b3edcd40bdb41ddb04d72 Mon Sep 17 00:00:00 2001 From: Tim Lee Date: Mon, 30 Sep 2024 16:58:00 -0600 Subject: [PATCH 020/385] Windows Battery Status (#22455) --- changes/19619-win-battery | 1 + .../Contributing/Understanding-host-vitals.md | 16 +++- server/service/osquery_test.go | 1 + server/service/osquery_utils/queries.go | 75 ++++++++++++++++--- server/service/osquery_utils/queries_test.go | 39 +++++++++- 5 files changed, 117 insertions(+), 15 deletions(-) create mode 100644 changes/19619-win-battery diff --git a/changes/19619-win-battery b/changes/19619-win-battery new file mode 100644 index 000000000000..124e58114048 --- /dev/null +++ b/changes/19619-win-battery @@ -0,0 +1 @@ +- Windows host details now include battery status \ No newline at end of file diff --git a/docs/Contributing/Understanding-host-vitals.md b/docs/Contributing/Understanding-host-vitals.md index 3cdabf2fde06..3aa2078574f1 100644 --- a/docs/Contributing/Understanding-host-vitals.md +++ b/docs/Contributing/Understanding-host-vitals.md @@ -3,7 +3,7 @@ Following is a summary of the detail queries hardcoded in Fleet used to populate the device details: -## battery +## battery_macos - Platforms: darwin @@ -12,6 +12,20 @@ Following is a summary of the detail queries hardcoded in Fleet used to populate SELECT serial_number, cycle_count, health FROM battery; ``` +## battery_windows + +- Platforms: windows + +- Discovery query: +```sql +SELECT 1 FROM osquery_registry WHERE active = true AND registry = 'table' AND name = 'battery' +``` + +- Query: +```sql +SELECT serial_number, cycle_count, designed_capacity, max_capacity FROM battery +``` + ## chromeos_profile_user_info - Platforms: chrome diff --git a/server/service/osquery_test.go b/server/service/osquery_test.go index 0697c7de69ce..ff913ac46f30 100644 --- a/server/service/osquery_test.go +++ b/server/service/osquery_test.go @@ -1062,6 +1062,7 @@ func verifyDiscovery(t *testing.T, queries, discovery map[string]string) { hostDetailQueryPrefix + "orbit_info": {}, hostDetailQueryPrefix + "software_vscode_extensions": {}, hostDetailQueryPrefix + "software_macos_firefox": {}, + hostDetailQueryPrefix + "battery_windows": {}, } for name := range queries { require.NotEmpty(t, discovery[name]) diff --git a/server/service/osquery_utils/queries.go b/server/service/osquery_utils/queries.go index f13454b2d110..5c93b5d2d680 100644 --- a/server/service/osquery_utils/queries.go +++ b/server/service/osquery_utils/queries.go @@ -555,7 +555,7 @@ var extraDetailQueries = map[string]DetailQuery{ DirectIngestFunc: directIngestChromeProfiles, Discovery: discoveryTable("google_chrome_profiles"), }, - "battery": { + "battery_macos": { Query: `SELECT serial_number, cycle_count, health FROM battery;`, Platforms: []string{"darwin"}, DirectIngestFunc: directIngestBattery, @@ -563,6 +563,12 @@ var extraDetailQueries = map[string]DetailQuery{ // osquery table on darwin (https://osquery.io/schema/5.3.0#battery), it is // always present. }, + "battery_windows": { + Query: `SELECT serial_number, cycle_count, designed_capacity, max_capacity FROM battery`, + Platforms: []string{"windows"}, + DirectIngestFunc: directIngestBattery, + Discovery: discoveryTable("battery"), // added to Windows in v5.12.1 (https://github.com/osquery/osquery/releases/tag/5.12.1) + }, "os_windows": { // This query is used to populate the `operating_systems` and `host_operating_system` // tables. Separately, the `hosts` table is populated via the `os_version` and @@ -1297,23 +1303,70 @@ func directIngestChromeProfiles(ctx context.Context, logger log.Logger, host *fl func directIngestBattery(ctx context.Context, logger log.Logger, host *fleet.Host, ds fleet.Datastore, rows []map[string]string) error { mapping := make([]*fleet.HostBattery, 0, len(rows)) for _, row := range rows { - cycleCount, err := strconv.ParseInt(EmptyToZero(row["cycle_count"]), 10, 64) + cycleCount, err := strconv.Atoi(EmptyToZero(row["cycle_count"])) if err != nil { return err } - mapping = append(mapping, &fleet.HostBattery{ - HostID: host.ID, - SerialNumber: row["serial_number"], - CycleCount: int(cycleCount), - // database type is VARCHAR(40) and since there isn't a - // canonical list of strings we can get for health, we - // truncate the value just in case. - Health: fmt.Sprintf("%.40s", row["health"]), - }) + + switch host.Platform { + case "darwin": + mapping = append(mapping, &fleet.HostBattery{ + HostID: host.ID, + SerialNumber: row["serial_number"], + CycleCount: cycleCount, + // database type is VARCHAR(40) and since there isn't a + // canonical list of strings we can get for health, we + // truncate the value just in case. + Health: fmt.Sprintf("%.40s", row["health"]), + }) + case "windows": + health, err := generateWindowsBatteryHealth(row["designed_capacity"], row["max_capacity"]) + if err != nil { + level.Error(logger).Log("op", "directIngestBattery", "hostID", host.ID, "err", err) + } + + mapping = append(mapping, &fleet.HostBattery{ + HostID: host.ID, + SerialNumber: row["serial_number"], + CycleCount: cycleCount, + Health: health, + }) + } } return ds.ReplaceHostBatteries(ctx, host.ID, mapping) } +const ( + batteryStatusUnknown = "Unknown" + batteryStatusDegraded = "Check Battery" + batteryStatusGood = "Good" + batteryDegradedThreshold = 80 +) + +func generateWindowsBatteryHealth(designedCapacity, maxCapacity string) (string, error) { + if designedCapacity == "" || maxCapacity == "" { + return batteryStatusUnknown, fmt.Errorf("missing battery capacity values, designed: %s, max: %s", designedCapacity, maxCapacity) + } + + designed, err := strconv.ParseInt(designedCapacity, 10, 64) + if err != nil { + return batteryStatusUnknown, err + } + + max, err := strconv.ParseInt(maxCapacity, 10, 64) + if err != nil { + return batteryStatusUnknown, err + } + + health := float64(max) / float64(designed) * 100 + + if health < batteryDegradedThreshold { + return batteryStatusDegraded, nil + } + + return batteryStatusGood, nil +} + func directIngestWindowsUpdateHistory( ctx context.Context, logger log.Logger, diff --git a/server/service/osquery_utils/queries_test.go b/server/service/osquery_utils/queries_test.go index 8a2931447083..3593ff20f1a5 100644 --- a/server/service/osquery_utils/queries_test.go +++ b/server/service/osquery_utils/queries_test.go @@ -279,7 +279,8 @@ func TestGetDetailQueries(t *testing.T) { "mdm_windows", "munki_info", "google_chrome_profiles", - "battery", + "battery_macos", + "battery_windows", "os_windows", "os_unix_like", "os_chrome", @@ -296,7 +297,7 @@ func TestGetDetailQueries(t *testing.T) { sortedKeysCompare(t, queriesNoConfig, baseQueries) queriesWithoutWinOSVuln := GetDetailQueries(context.Background(), config.FleetConfig{Vulnerabilities: config.VulnerabilitiesConfig{DisableWinOSVulnerabilities: true}}, nil, nil) - require.Len(t, queriesWithoutWinOSVuln, 25) + require.Len(t, queriesWithoutWinOSVuln, 26) queriesWithUsers := GetDetailQueries(context.Background(), config.FleetConfig{App: config.AppConfig{EnableScheduledQueryStats: true}}, nil, &fleet.Features{EnableHostUsers: true}) qs := append(baseQueries, "users", "users_chrome", "scheduled_query_stats") @@ -984,7 +985,8 @@ func TestDirectIngestBattery(t *testing.T) { } host := fleet.Host{ - ID: 1, + ID: 1, + Platform: "darwin", } err := directIngestBattery(context.Background(), log.NewNopLogger(), &host, ds, []map[string]string{ @@ -994,6 +996,37 @@ func TestDirectIngestBattery(t *testing.T) { require.NoError(t, err) require.True(t, ds.ReplaceHostBatteriesFuncInvoked) + + ds.ReplaceHostBatteriesFunc = func(ctx context.Context, id uint, mappings []*fleet.HostBattery) error { + require.Equal(t, mappings, []*fleet.HostBattery{ + {HostID: uint(2), SerialNumber: "a", CycleCount: 2, Health: batteryStatusGood}, + {HostID: uint(2), SerialNumber: "b", CycleCount: 3, Health: batteryStatusDegraded}, + {HostID: uint(2), SerialNumber: "c", CycleCount: 4, Health: batteryStatusUnknown}, + {HostID: uint(2), SerialNumber: "d", CycleCount: 5, Health: batteryStatusUnknown}, + {HostID: uint(2), SerialNumber: "e", CycleCount: 6, Health: batteryStatusUnknown}, + {HostID: uint(2), SerialNumber: "f", CycleCount: 7, Health: batteryStatusUnknown}, + }) + return nil + } + + // reset the ds flag + ds.ReplaceHostBatteriesFuncInvoked = false + + host = fleet.Host{ + ID: 2, + Platform: "windows", + } + + err = directIngestBattery(context.Background(), log.NewNopLogger(), &host, ds, []map[string]string{ + {"serial_number": "a", "cycle_count": "2", "designed_capacity": "3000", "max_capacity": "2400"}, // max_capacity >= 80% + {"serial_number": "b", "cycle_count": "3", "designed_capacity": "3000", "max_capacity": "2399"}, // max_capacity < 50% + {"serial_number": "c", "cycle_count": "4", "designed_capacity": "3000", "max_capacity": ""}, // missing max_capacity + {"serial_number": "d", "cycle_count": "5", "designed_capacity": "", "max_capacity": ""}, // missing designed_capacity and max_capacity + {"serial_number": "e", "cycle_count": "6", "designed_capacity": "", "max_capacity": "2000"}, // missing designed_capacity + {"serial_number": "f", "cycle_count": "7", "designed_capacity": "foo", "max_capacity": "bar"}, // invalid designed_capacity and max_capacity + }) + require.NoError(t, err) + require.True(t, ds.ReplaceHostBatteriesFuncInvoked) } func TestDirectIngestOSWindows(t *testing.T) { From 3f249fd11b3e9020d0b23af9b0cd85013168a5da Mon Sep 17 00:00:00 2001 From: Brock Walters <153771548+nonpunctual@users.noreply.github.com> Date: Tue, 1 Oct 2024 08:25:59 -0400 Subject: [PATCH 021/385] Nonpunctual passcode ddm (#22531) # Checklist for submitter Adds passcode DDM declaration to workstations. --- .../macos-passcode-settings.json | 12 ++++++++++++ it-and-security/teams/workstations.yml | 1 + 2 files changed, 13 insertions(+) create mode 100644 it-and-security/lib/configuration-profiles/macos-passcode-settings.json diff --git a/it-and-security/lib/configuration-profiles/macos-passcode-settings.json b/it-and-security/lib/configuration-profiles/macos-passcode-settings.json new file mode 100644 index 000000000000..e68b0a46c64f --- /dev/null +++ b/it-and-security/lib/configuration-profiles/macos-passcode-settings.json @@ -0,0 +1,12 @@ +{ + "Type": "com.apple.configuration.passcode.settings", + "Identifier": "com.fleetdm.config.passcode.settings", + "Payload": { + "RequireComplexPasscode": true, + "MinimumLength": 10, + "MinimumComplexCharacters": 1, + "MaximumFailedAttempts": 11, + "MaximumGracePeriodInMinutes": 1, + "MaximumInactivityInMinutes": 15 + } +} diff --git a/it-and-security/teams/workstations.yml b/it-and-security/teams/workstations.yml index 0ad266f905d9..f586b2b5d724 100644 --- a/it-and-security/teams/workstations.yml +++ b/it-and-security/teams/workstations.yml @@ -38,6 +38,7 @@ controls: - path: ../lib/configuration-profiles/macos-password.mobileconfig - path: ../lib/configuration-profiles/macos-prevent-autologon.mobileconfig - path: ../lib/configuration-profiles/macos-secure-terminal-keyboard.mobileconfig + - path: ../lib/configuration-profiles/macos-passcode-settings.json macos_setup: bootstrap_package: "" enable_end_user_authentication: true From cfd4159487075811c7134daea3fe178203836c5d Mon Sep 17 00:00:00 2001 From: Brock Walters <153771548+nonpunctual@users.noreply.github.com> Date: Tue, 1 Oct 2024 08:50:30 -0400 Subject: [PATCH 022/385] Update macos-passcode-settings.json (#22533) Cuz JD is dum. --- .../lib/configuration-profiles/macos-passcode-settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/it-and-security/lib/configuration-profiles/macos-passcode-settings.json b/it-and-security/lib/configuration-profiles/macos-passcode-settings.json index e68b0a46c64f..d433a91826fe 100644 --- a/it-and-security/lib/configuration-profiles/macos-passcode-settings.json +++ b/it-and-security/lib/configuration-profiles/macos-passcode-settings.json @@ -2,7 +2,7 @@ "Type": "com.apple.configuration.passcode.settings", "Identifier": "com.fleetdm.config.passcode.settings", "Payload": { - "RequireComplexPasscode": true, + "RequireAlphanumericPasscode": true, "MinimumLength": 10, "MinimumComplexCharacters": 1, "MaximumFailedAttempts": 11, From 80f0fd8889a7b8ed66b92e1762e6e5fda085d427 Mon Sep 17 00:00:00 2001 From: Jahziel Villasana-Espinoza Date: Tue, 1 Oct 2024 10:26:16 -0400 Subject: [PATCH 023/385] fix: reset token team assignments to defaults (#22326) > Related issue: #22198 # 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] Added/updated tests - [x] Manual QA for all new/changed functionality --- changes/22198-defaults | 2 + cmd/fleetctl/apply_test.go | 91 +++++++++++++++++++ cmd/fleetctl/gitops_test.go | 80 ++++++++++++++++ .../VppTable/TeamsCell/TeamsCell.tsx | 4 +- server/service/appconfig.go | 42 ++++++++- server/service/appconfig_test.go | 78 ++++++++++++++++ server/service/integration_mdm_test.go | 47 ++++++++-- server/service/mail_test.go | 12 +++ server/service/service_appconfig_test.go | 12 +++ 9 files changed, 357 insertions(+), 11 deletions(-) create mode 100644 changes/22198-defaults diff --git a/changes/22198-defaults b/changes/22198-defaults new file mode 100644 index 000000000000..ec243e9a48e4 --- /dev/null +++ b/changes/22198-defaults @@ -0,0 +1,2 @@ +- Fixes a bug where removing a VPP or ABM token from a GitOps YAML file would leave the team + assignments unchanged. \ No newline at end of file diff --git a/cmd/fleetctl/apply_test.go b/cmd/fleetctl/apply_test.go index f35b39dc84f1..bd80bb84347c 100644 --- a/cmd/fleetctl/apply_test.go +++ b/cmd/fleetctl/apply_test.go @@ -657,6 +657,18 @@ func TestApplyAppConfig(t *testing.T) { return []*fleet.TeamSummary{{Name: "team1", ID: 1}}, nil } + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{{OrganizationName: t.Name()}}, nil + } + name := writeTmpYml(t, `--- apiVersion: v1 kind: config @@ -782,6 +794,18 @@ func TestApplyAppConfigDryRunIssue(t *testing.T) { return nil } + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{}, nil + } + // first, set the default app config's agent options as set after fleetctl setup name := writeTmpYml(t, `--- apiVersion: v1 @@ -914,6 +938,18 @@ func TestApplyAppConfigDeprecatedFields(t *testing.T) { return nil } + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{}, nil + } + name := writeTmpYml(t, `--- apiVersion: v1 kind: config @@ -1316,6 +1352,14 @@ func TestApplyAsGitOps(t *testing.T) { return []*fleet.ABMToken{{ID: 1}}, nil } + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + // Apply global config. name := writeTmpYml(t, `--- apiVersion: v1 @@ -1873,6 +1917,18 @@ func TestCanApplyIntervalsInNanoseconds(t *testing.T) { return nil } + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{}, nil + } + name := writeTmpYml(t, `--- apiVersion: v1 kind: config @@ -1908,6 +1964,18 @@ func TestCanApplyIntervalsUsingDurations(t *testing.T) { return nil } + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{}, nil + } + name := writeTmpYml(t, `--- apiVersion: v1 kind: config @@ -2091,6 +2159,18 @@ func TestApplyMacosSetup(t *testing.T) { return []*fleet.ABMToken{{ID: 1}}, nil } + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{}, nil + } + return ds } @@ -2764,6 +2844,17 @@ func TestApplySpecs(t *testing.T) { ds.DeleteMDMWindowsConfigProfileByTeamAndNameFunc = func(ctx context.Context, teamID *uint, profileName string) error { return nil } + + // VPP/AMB + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{}, nil + } } cases := []struct { diff --git a/cmd/fleetctl/gitops_test.go b/cmd/fleetctl/gitops_test.go index 1a054482f618..5a688b2fd91b 100644 --- a/cmd/fleetctl/gitops_test.go +++ b/cmd/fleetctl/gitops_test.go @@ -83,6 +83,18 @@ func TestGitOpsBasicGlobalFree(t *testing.T) { return nil } + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{}, nil + } + tmpFile, err := os.CreateTemp(t.TempDir(), "*.yml") require.NoError(t, err) @@ -238,6 +250,18 @@ func TestGitOpsBasicGlobalPremium(t *testing.T) { return nil, nil } + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{}, nil + } + tmpFile, err := os.CreateTemp(t.TempDir(), "*.yml") require.NoError(t, err) @@ -591,6 +615,17 @@ func TestGitOpsFullGlobal(t *testing.T) { return nil } + // Needed for checking tokens + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{}, nil + } + const ( fleetServerURL = "https://fleet.example.com" orgName = "GitOps Test" @@ -1079,6 +1114,18 @@ func TestGitOpsBasicGlobalAndTeam(t *testing.T) { return nil, 0, nil, nil } + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{}, nil + } + globalFile, err := os.CreateTemp(t.TempDir(), "*.yml") require.NoError(t, err) @@ -1345,6 +1392,18 @@ func TestGitOpsBasicGlobalAndNoTeam(t *testing.T) { return nil, 0, nil, nil } + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{}, nil + } + globalFileBasic, err := os.CreateTemp(t.TempDir(), "*.yml") require.NoError(t, err) @@ -1604,6 +1663,18 @@ func TestGitOpsFullGlobalAndTeam(t *testing.T) { return team, nil } + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{}, nil + } + apnsCert, apnsKey, err := mysql.GenerateTestCertBytes() require.NoError(t, err) crt, key, err := apple_mdm.NewSCEPCACertKey() @@ -2234,6 +2305,15 @@ func setupFullGitOpsPremiumServer(t *testing.T) (*mock.Store, **fleet.AppConfig, ds.ListSoftwareTitlesFunc = func(ctx context.Context, opt fleet.SoftwareTitleListOptions, tmFilter fleet.TeamFilter) ([]fleet.SoftwareTitleListResult, int, *fleet.PaginationMetadata, error) { return nil, 0, nil, nil } + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{}, nil + } t.Setenv("FLEET_SERVER_URL", fleetServerURL) t.Setenv("ORG_NAME", orgName) diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/components/VppTable/TeamsCell/TeamsCell.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/components/VppTable/TeamsCell/TeamsCell.tsx index 57a21e7e48b1..570d7af8ed0f 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/components/VppTable/TeamsCell/TeamsCell.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/components/VppTable/TeamsCell/TeamsCell.tsx @@ -17,7 +17,7 @@ const generateCell = (teams: ITokenTeam[] | null) => { } if (teams.length === 0) { - return ; + return ; } let text = ""; @@ -83,7 +83,7 @@ const TeamsCell = ({ teams, className }: ITeamsCellProps) => { } if (teams.length === 0) { - return ; + return ; } if (teams.length === 1) { diff --git a/server/service/appconfig.go b/server/service/appconfig.go index 17b83dcbbfa1..22998d24d601 100644 --- a/server/service/appconfig.go +++ b/server/service/appconfig.go @@ -545,15 +545,55 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle } } + // Reset teams for ABM tokens that exist in Fleet but aren't present in the config being passed + tokensInCfg := make(map[string]struct{}) + for _, t := range newAppConfig.MDM.AppleBusinessManager.Value { + tokensInCfg[t.OrganizationName] = struct{}{} + } + + toks, err := svc.ds.ListABMTokens(ctx) + if err != nil { + return nil, ctxerr.Wrap(ctx, err, "listing ABM tokens") + } + for _, tok := range toks { + if _, ok := tokensInCfg[tok.OrganizationName]; !ok { + tok.MacOSDefaultTeamID = nil + tok.IOSDefaultTeamID = nil + tok.IPadOSDefaultTeamID = nil + if err := svc.ds.SaveABMToken(ctx, tok); err != nil { + return nil, ctxerr.Wrap(ctx, err, "saving ABM token assignments") + } + } + } + if (appConfig.MDM.AppleBusinessManager.Set && appConfig.MDM.AppleBusinessManager.Valid) || appConfig.MDM.DeprecatedAppleBMDefaultTeam != "" { for _, tok := range abmAssignments { - fmt.Println(tok.EncryptedToken) if err := svc.ds.SaveABMToken(ctx, tok); err != nil { return nil, ctxerr.Wrap(ctx, err, "saving ABM token assignments") } } } + // Reset teams for VPP tokens that exist in Fleet but aren't present in the config being passed + clear(tokensInCfg) + + for _, t := range newAppConfig.MDM.VolumePurchasingProgram.Value { + tokensInCfg[t.Location] = struct{}{} + } + + vppToks, err := svc.ds.ListVPPTokens(ctx) + if err != nil { + return nil, ctxerr.Wrap(ctx, err, "listing VPP tokens") + } + for _, tok := range vppToks { + if _, ok := tokensInCfg[tok.Location]; !ok { + tok.Teams = nil + if _, err := svc.ds.UpdateVPPTokenTeams(ctx, tok.ID, nil); err != nil { + return nil, ctxerr.Wrap(ctx, err, "saving VPP token teams") + } + } + } + if appConfig.MDM.VolumePurchasingProgram.Set && appConfig.MDM.VolumePurchasingProgram.Valid { for tokenID, tokenTeams := range vppAssignments { if _, err := svc.ds.UpdateVPPTokenTeams(ctx, tokenID, tokenTeams); err != nil { diff --git a/server/service/appconfig_test.go b/server/service/appconfig_test.go index 0fb0d318d26f..2c6e6bc10849 100644 --- a/server/service/appconfig_test.go +++ b/server/service/appconfig_test.go @@ -51,6 +51,18 @@ func TestAppConfigAuth(t *testing.T) { return nil } + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{}, nil + } + testCases := []struct { name string user *fleet.User @@ -647,6 +659,18 @@ func TestModifyAppConfigSMTPConfigured(t *testing.T) { return nil } + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{}, nil + } + // Disable SMTP. newAppConfig := fleet.AppConfig{ SMTPSettings: &fleet.SMTPSettings{ @@ -751,6 +775,18 @@ func TestTransparencyURL(t *testing.T) { return nil } + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{}, nil + } + ac, err := svc.AppConfigObfuscated(ctx) require.NoError(t, err) require.Equal(t, tt.initialURL, ac.FleetDesktop.TransparencyURL) @@ -800,6 +836,18 @@ func TestTransparencyURLDowngradeLicense(t *testing.T) { return nil } + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{}, nil + } + ac, err := svc.AppConfigObfuscated(ctx) require.NoError(t, err) require.Equal(t, "https://example.com/transparency", ac.FleetDesktop.TransparencyURL) @@ -1090,6 +1138,15 @@ func TestMDMAppleConfig(t *testing.T) { depStorage.StoreAssignerProfileFunc = func(ctx context.Context, name string, profileUUID string) error { return nil } + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{{OrganizationName: t.Name()}}, nil + } ac, err := svc.AppConfigObfuscated(ctx) require.NoError(t, err) @@ -1168,6 +1225,15 @@ func TestModifyAppConfigSMTPSSOAgentOptions(t *testing.T) { ) error { return nil } + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{}, nil + } // Not sending smtp_settings, sso_settings or agent_settings will do nothing. b := []byte(`{}`) @@ -1297,6 +1363,18 @@ func TestModifyEnableAnalytics(t *testing.T) { return nil } + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{}, nil + } + ac, err := svc.AppConfigObfuscated(ctx) require.NoError(t, err) require.Equal(t, tt.initialEnabled, ac.ServerSettings.EnableAnalytics) diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go index ce134a2bd5fe..6eee1327bb2b 100644 --- a/server/service/integration_mdm_test.go +++ b/server/service/integration_mdm_test.go @@ -879,6 +879,26 @@ func (s *integrationMDMTestSuite) TestAppleGetAppleMDM() { require.Equal(t, tm.Name, tok.MacOSTeam.Name) require.Equal(t, tm.Name, tok.IOSTeam.Name) require.Equal(t, tm.Name, tok.IPadOSTeam.Name) + + // Reset the teams via app config + acResp = appConfigResponse{} + s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{ + "mdm": { + "apple_business_manager": [] + } + }`), http.StatusOK, &acResp) + + tokensResp = listABMTokensResponse{} + s.DoJSON("GET", "/api/latest/fleet/abm_tokens", nil, http.StatusOK, &tokensResp) + tok = s.getABMTokenByName(tmOrgName, tokensResp.Tokens) + require.NotNil(t, tok) + require.False(t, tok.TermsExpired) + require.Equal(t, "abc", tok.AppleID) + require.Equal(t, tmOrgName, tok.OrganizationName) + require.Equal(t, s.server.URL+"/mdm/apple/mdm", tok.MDMServerURL) + require.Equal(t, fleet.TeamNameNoTeam, tok.MacOSTeam.Name) + require.Equal(t, fleet.TeamNameNoTeam, tok.IOSTeam.Name) + require.Equal(t, fleet.TeamNameNoTeam, tok.IPadOSTeam.Name) } func (s *integrationMDMTestSuite) getABMTokenByName(orgName string, tokens []*fleet.ABMToken) *fleet.ABMToken { @@ -10532,6 +10552,25 @@ func (s *integrationMDMTestSuite) TestVPPApps() { var resPatchVPP patchVPPTokensTeamsResponse s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/vpp_tokens/%d/teams", resp.Tokens[0].ID), patchVPPTokensTeamsRequest{TeamIDs: []uint{}}, http.StatusOK, &resPatchVPP) + // Reset the token's teams by omitting the token from app config + acResp := appConfigResponse{} + s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{ + "mdm": { "volume_purchasing_program": null } + }`), http.StatusOK, &acResp) + + resp = getVPPTokensResponse{} + s.DoJSON("GET", "/api/latest/fleet/vpp_tokens", &getVPPTokensRequest{}, http.StatusOK, &resp) + require.NoError(t, resp.Err) + require.Len(t, resp.Tokens, 1) + require.Equal(t, orgName, resp.Tokens[0].OrgName) + require.Equal(t, location, resp.Tokens[0].Location) + require.Equal(t, expTime, resp.Tokens[0].RenewDate) + require.Empty(t, resp.Tokens[0].Teams) + + // Add the team back + resPatchVPP = patchVPPTokensTeamsResponse{} + s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/vpp_tokens/%d/teams", resp.Tokens[0].ID), patchVPPTokensTeamsRequest{TeamIDs: []uint{}}, http.StatusOK, &resPatchVPP) + // Get list of VPP apps from "Apple" // We're passing team 1 here, but we haven't added any app store apps to that team, so we get // back all available apps in our VPP location. @@ -10801,14 +10840,6 @@ func (s *integrationMDMTestSuite) TestVPPApps() { s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/vpp_tokens/%d/teams", vppRes.Token.ID), patchVPPTokensTeamsRequest{TeamIDs: []uint{team.ID}}, http.StatusOK, &resPatchVPP) - // mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error { - // _, err := q.ExecContext(context.Background(), "UPDATE vpp_tokens SET renew_at = ? WHERE organization_name = ?", time.Now().Add(-1*time.Hour), "badtoken") - // return err - // }) - - // r := s.Do("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/software/install/%d", mdmHost.ID, errTitleID), &installSoftwareRequest{}, http.StatusUnprocessableEntity) - // require.Contains(t, extractServerErrorText(r.Body), "VPP token expired") - // Disable the token s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/vpp_tokens/%d/teams", vppRes.Token.ID), patchVPPTokensTeamsRequest{}, http.StatusOK, &resPatchVPP) diff --git a/server/service/mail_test.go b/server/service/mail_test.go index be041e7e6ec6..c83168301cac 100644 --- a/server/service/mail_test.go +++ b/server/service/mail_test.go @@ -86,6 +86,18 @@ func TestMailService(t *testing.T) { return invite, nil } + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{}, nil + } + ctx = test.UserContext(ctx, test.UserAdmin) // (1) Modifying the app config `sender_address` field to trigger a test e-mail send. diff --git a/server/service/service_appconfig_test.go b/server/service/service_appconfig_test.go index b6318c584b39..d83479e5f156 100644 --- a/server/service/service_appconfig_test.go +++ b/server/service/service_appconfig_test.go @@ -373,6 +373,18 @@ func TestModifyAppConfigPatches(t *testing.T) { return nil } + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{}, nil + } + configJSON := []byte(`{"org_info": { "org_name": "Acme", "org_logo_url": "somelogo.jpg" }}`) ctx = test.UserContext(ctx, test.UserAdmin) From aa887d3d5fb603aa1f9cf290e5cef3b941498387 Mon Sep 17 00:00:00 2001 From: Gabriel Hernandez Date: Tue, 1 Oct 2024 16:36:13 +0100 Subject: [PATCH 024/385] add 1password softare icon (#22534) add 1password software icon. In the future we'll need more time to fix the jank at smaller sized **icon at small size** ![image](https://github.com/user-attachments/assets/92217433-3cb9-4776-8405-d899a7120d4c) --- .../components/icons/OnePassword.tsx | 286 ++++++++++++++++++ .../SoftwarePage/components/icons/index.ts | 2 + 2 files changed, 288 insertions(+) create mode 100644 frontend/pages/SoftwarePage/components/icons/OnePassword.tsx diff --git a/frontend/pages/SoftwarePage/components/icons/OnePassword.tsx b/frontend/pages/SoftwarePage/components/icons/OnePassword.tsx new file mode 100644 index 000000000000..5da10b15830c --- /dev/null +++ b/frontend/pages/SoftwarePage/components/icons/OnePassword.tsx @@ -0,0 +1,286 @@ +import React from "react"; + +import type { SVGProps } from "react"; + +const OnePassword = (props: SVGProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); +export default OnePassword; diff --git a/frontend/pages/SoftwarePage/components/icons/index.ts b/frontend/pages/SoftwarePage/components/icons/index.ts index 1b8b222562b8..1e15ed9ae66a 100644 --- a/frontend/pages/SoftwarePage/components/icons/index.ts +++ b/frontend/pages/SoftwarePage/components/icons/index.ts @@ -34,6 +34,7 @@ import Notion from "./Notion"; import WindowsDefender from "./WindowsDefender"; import WhatsApp from "./WhatsApp"; import Postman from "./Postman"; +import OnePassword from "./OnePassword"; // Maps all known Linux platforms to the LinuxOS icon const LINUX_OS_NAME_TO_ICON_MAP = HOST_LINUX_PLATFORMS.reduce( @@ -75,6 +76,7 @@ const SOFTWARE_NAME_TO_ICON_MAP = { teamviewer: TeamViewer, "windows defender": WindowsDefender, postman: Postman, + "1password": OnePassword, ...LINUX_OS_NAME_TO_ICON_MAP, } as const; From f7fc22d766bafdfe8923b767d1fcb43e78aa0b3d Mon Sep 17 00:00:00 2001 From: George Karr Date: Tue, 1 Oct 2024 10:37:19 -0500 Subject: [PATCH 025/385] Adding changes for Fleet v4.57.1 (#22537) --- CHANGELOG.md | 9 +++++++++ charts/fleet/Chart.yaml | 2 +- charts/fleet/values.yaml | 2 +- infrastructure/dogfood/terraform/aws/variables.tf | 2 +- infrastructure/dogfood/terraform/gcp/variables.tf | 2 +- terraform/addons/vuln-processing/variables.tf | 4 ++-- terraform/byo-vpc/byo-db/byo-ecs/variables.tf | 4 ++-- terraform/byo-vpc/byo-db/variables.tf | 4 ++-- terraform/byo-vpc/example/main.tf | 2 +- terraform/byo-vpc/variables.tf | 4 ++-- terraform/example/main.tf | 4 ++-- terraform/variables.tf | 4 ++-- tools/fleetctl-npm/package.json | 2 +- 13 files changed, 27 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1f4dd2cee3e..056966f521be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## Fleet 4.57.1 (Oct 01, 2024) + +### Bug fixes + +* Improved performance of SQL queries used to determine MDM profile status for Apple hosts. +* Ensured request timeouts for software installer edits were just as high as for initial software installer uploads. +* Fixed an issue with the migration that added support for multiple VPP tokens, which would happen if a token was removed prior to upgrading Fleet. +* Fixed a "no rows" error when adding a software installer that matched an existing title's name and source but not its bundle ID. + ## Fleet 4.57.0 (Sep 23, 2024) **Endpoint Operations** diff --git a/charts/fleet/Chart.yaml b/charts/fleet/Chart.yaml index c23438bf22aa..f9abb8e29901 100644 --- a/charts/fleet/Chart.yaml +++ b/charts/fleet/Chart.yaml @@ -8,7 +8,7 @@ version: v6.2.0 home: https://github.com/fleetdm/fleet sources: - https://github.com/fleetdm/fleet.git -appVersion: v4.57.0 +appVersion: v4.57.1 dependencies: - name: mysql condition: mysql.enabled diff --git a/charts/fleet/values.yaml b/charts/fleet/values.yaml index 03539df9da98..15eda9b9e8ce 100644 --- a/charts/fleet/values.yaml +++ b/charts/fleet/values.yaml @@ -3,7 +3,7 @@ hostName: fleet.localhost replicas: 3 # The number of Fleet instances to deploy imageRepository: fleetdm/fleet -imageTag: v4.57.0 # Version of Fleet to deploy +imageTag: v4.57.1 # Version of Fleet to deploy podAnnotations: {} # Additional annotations to add to the Fleet pod serviceAccountAnnotations: {} # Additional annotations to add to the Fleet service account resources: diff --git a/infrastructure/dogfood/terraform/aws/variables.tf b/infrastructure/dogfood/terraform/aws/variables.tf index 2020de2f8306..d61f8819ec4b 100644 --- a/infrastructure/dogfood/terraform/aws/variables.tf +++ b/infrastructure/dogfood/terraform/aws/variables.tf @@ -56,7 +56,7 @@ variable "database_name" { variable "fleet_image" { description = "the name of the container image to run" - default = "fleetdm/fleet:v4.57.0" + default = "fleetdm/fleet:v4.57.1" } variable "software_inventory" { diff --git a/infrastructure/dogfood/terraform/gcp/variables.tf b/infrastructure/dogfood/terraform/gcp/variables.tf index 906a58c153f8..a8877ab5e00f 100644 --- a/infrastructure/dogfood/terraform/gcp/variables.tf +++ b/infrastructure/dogfood/terraform/gcp/variables.tf @@ -68,7 +68,7 @@ variable "redis_mem" { } variable "image" { - default = "fleetdm/fleet:v4.57.0" + default = "fleetdm/fleet:v4.57.1" } variable "software_installers_bucket_name" { diff --git a/terraform/addons/vuln-processing/variables.tf b/terraform/addons/vuln-processing/variables.tf index 8d296903fdc4..11c5bb870715 100644 --- a/terraform/addons/vuln-processing/variables.tf +++ b/terraform/addons/vuln-processing/variables.tf @@ -24,7 +24,7 @@ variable "fleet_config" { vuln_processing_cpu = optional(number, 2048) vuln_data_stream_mem = optional(number, 1024) vuln_data_stream_cpu = optional(number, 512) - image = optional(string, "fleetdm/fleet:v4.57.0") + image = optional(string, "fleetdm/fleet:v4.57.1") family = optional(string, "fleet-vuln-processing") sidecars = optional(list(any), []) extra_environment_variables = optional(map(string), {}) @@ -82,7 +82,7 @@ variable "fleet_config" { vuln_processing_cpu = 2048 vuln_data_stream_mem = 1024 vuln_data_stream_cpu = 512 - image = "fleetdm/fleet:v4.57.0" + image = "fleetdm/fleet:v4.57.1" family = "fleet-vuln-processing" sidecars = [] extra_environment_variables = {} diff --git a/terraform/byo-vpc/byo-db/byo-ecs/variables.tf b/terraform/byo-vpc/byo-db/byo-ecs/variables.tf index 27565cb90fa8..51dc799bc5b7 100644 --- a/terraform/byo-vpc/byo-db/byo-ecs/variables.tf +++ b/terraform/byo-vpc/byo-db/byo-ecs/variables.tf @@ -16,7 +16,7 @@ variable "fleet_config" { mem = optional(number, 4096) cpu = optional(number, 512) pid_mode = optional(string, null) - image = optional(string, "fleetdm/fleet:v4.57.0") + image = optional(string, "fleetdm/fleet:v4.57.1") family = optional(string, "fleet") sidecars = optional(list(any), []) depends_on = optional(list(any), []) @@ -119,7 +119,7 @@ variable "fleet_config" { mem = 512 cpu = 256 pid_mode = null - image = "fleetdm/fleet:v4.57.0" + image = "fleetdm/fleet:v4.57.1" family = "fleet" sidecars = [] depends_on = [] diff --git a/terraform/byo-vpc/byo-db/variables.tf b/terraform/byo-vpc/byo-db/variables.tf index 041ff9d0f861..b19d74931ec5 100644 --- a/terraform/byo-vpc/byo-db/variables.tf +++ b/terraform/byo-vpc/byo-db/variables.tf @@ -77,7 +77,7 @@ variable "fleet_config" { mem = optional(number, 4096) cpu = optional(number, 512) pid_mode = optional(string, null) - image = optional(string, "fleetdm/fleet:v4.57.0") + image = optional(string, "fleetdm/fleet:v4.57.1") family = optional(string, "fleet") sidecars = optional(list(any), []) depends_on = optional(list(any), []) @@ -205,7 +205,7 @@ variable "fleet_config" { mem = 512 cpu = 256 pid_mode = null - image = "fleetdm/fleet:v4.57.0" + image = "fleetdm/fleet:v4.57.1" family = "fleet" sidecars = [] depends_on = [] diff --git a/terraform/byo-vpc/example/main.tf b/terraform/byo-vpc/example/main.tf index 3176d07def1f..7f75b975ad42 100644 --- a/terraform/byo-vpc/example/main.tf +++ b/terraform/byo-vpc/example/main.tf @@ -17,7 +17,7 @@ provider "aws" { } locals { - fleet_image = "fleetdm/fleet:v4.57.0" + fleet_image = "fleetdm/fleet:v4.57.1" domain_name = "example.com" } diff --git a/terraform/byo-vpc/variables.tf b/terraform/byo-vpc/variables.tf index ce2a81f88c41..2c8fdb1d1742 100644 --- a/terraform/byo-vpc/variables.tf +++ b/terraform/byo-vpc/variables.tf @@ -170,7 +170,7 @@ variable "fleet_config" { mem = optional(number, 4096) cpu = optional(number, 512) pid_mode = optional(string, null) - image = optional(string, "fleetdm/fleet:v4.57.0") + image = optional(string, "fleetdm/fleet:v4.57.1") family = optional(string, "fleet") sidecars = optional(list(any), []) depends_on = optional(list(any), []) @@ -298,7 +298,7 @@ variable "fleet_config" { mem = 512 cpu = 256 pid_mode = null - image = "fleetdm/fleet:v4.57.0" + image = "fleetdm/fleet:v4.57.1" family = "fleet" sidecars = [] depends_on = [] diff --git a/terraform/example/main.tf b/terraform/example/main.tf index 2b2112517925..bf8f569a36b8 100644 --- a/terraform/example/main.tf +++ b/terraform/example/main.tf @@ -63,8 +63,8 @@ module "fleet" { fleet_config = { # To avoid pull-rate limiting from dockerhub, consider using our quay.io mirror - # for the Fleet image. e.g. "quay.io/fleetdm/fleet:v4.57.0" - image = "fleetdm/fleet:v4.57.0" # override default to deploy the image you desire + # for the Fleet image. e.g. "quay.io/fleetdm/fleet:v4.57.1" + image = "fleetdm/fleet:v4.57.1" # override default to deploy the image you desire # See https://fleetdm.com/docs/deploy/reference-architectures#aws for appropriate scaling # memory and cpu. autoscaling = { diff --git a/terraform/variables.tf b/terraform/variables.tf index 7dc798cf63d8..de37b0020f25 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -218,7 +218,7 @@ variable "fleet_config" { mem = optional(number, 4096) cpu = optional(number, 512) pid_mode = optional(string, null) - image = optional(string, "fleetdm/fleet:v4.57.0") + image = optional(string, "fleetdm/fleet:v4.57.1") family = optional(string, "fleet") sidecars = optional(list(any), []) depends_on = optional(list(any), []) @@ -346,7 +346,7 @@ variable "fleet_config" { mem = 512 cpu = 256 pid_mode = null - image = "fleetdm/fleet:v4.57.0" + image = "fleetdm/fleet:v4.57.1" family = "fleet" sidecars = [] depends_on = [] diff --git a/tools/fleetctl-npm/package.json b/tools/fleetctl-npm/package.json index 96a4dcd08170..13ac3172e426 100644 --- a/tools/fleetctl-npm/package.json +++ b/tools/fleetctl-npm/package.json @@ -1,6 +1,6 @@ { "name": "fleetctl", - "version": "v4.57.0", + "version": "v4.57.1", "description": "Installer for the fleetctl CLI tool", "bin": { "fleetctl": "./run.js" From f8f24e0a80322cf0c6d541761a2583ebeb8b0f3f Mon Sep 17 00:00:00 2001 From: Lucas Manuel Rodriguez Date: Tue, 1 Oct 2024 13:02:13 -0300 Subject: [PATCH 026/385] Add support to upload RPM packages (#22502) #22473 - [X] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files) for more information. - [X] Added/updated tests - [X] Manual QA for all new/changed functionality - For Orbit and Fleet Desktop changes: - [x] Manual QA must be performed in the three main OSs, macOS, Windows and Linux. --------- Co-authored-by: RachelElysia <71795832+RachelElysia@users.noreply.github.com> Co-authored-by: Ian Littman --- changes/20537-add-rpm-support | 1 + cmd/fleetctl/gitops_test.go | 4 +- ee/server/service/software_installers.go | 4 +- frontend/interfaces/package_type.ts | 2 +- frontend/interfaces/software.ts | 2 +- .../PackageAdvancedOptions.tsx | 1 + .../components/PackageForm/PackageForm.tsx | 4 +- .../InstallSoftwareModal.tsx | 1 + frontend/utilities/file/fileUtils.tests.ts | 23 +++-- frontend/utilities/file/fileUtils.ts | 5 +- .../utilities/software_install_scripts.ts | 4 + .../utilities/software_uninstall_scripts.ts | 4 + go.mod | 2 + go.sum | 4 + orbit/pkg/installer/installer.go | 10 +- pkg/file/file.go | 6 ++ pkg/file/management.go | 15 +++ pkg/file/management_test.go | 11 +- pkg/file/rpm.go | 33 ++++++ pkg/file/rpm_test.go | 94 ++++++++++++++++++ pkg/file/scripts/README.md | 8 +- pkg/file/scripts/install_rpm.sh | 3 + pkg/file/scripts/remove_rpm.sh | 6 ++ pkg/file/scripts/uninstall_rpm.sh | 4 + .../testdata/scripts/install_rpm.sh.golden | 3 + .../testdata/scripts/remove_rpm.sh.golden | 6 ++ .../testdata/scripts/uninstall_rpm.sh.golden | 4 + server/datastore/mysql/software_installers.go | 1 + .../mysql/software_installers_test.go | 16 ++- server/fleet/software_installer.go | 4 +- server/service/integration_enterprise_test.go | 71 +++++++++++++ .../testdata/software-installers/README.md | 3 +- .../testdata/software-installers/ruby.rpm | Bin 0 -> 40422 bytes 33 files changed, 328 insertions(+), 31 deletions(-) create mode 100644 changes/20537-add-rpm-support create mode 100644 pkg/file/rpm.go create mode 100644 pkg/file/rpm_test.go create mode 100644 pkg/file/scripts/install_rpm.sh create mode 100644 pkg/file/scripts/remove_rpm.sh create mode 100644 pkg/file/scripts/uninstall_rpm.sh create mode 100644 pkg/file/testdata/scripts/install_rpm.sh.golden create mode 100644 pkg/file/testdata/scripts/remove_rpm.sh.golden create mode 100644 pkg/file/testdata/scripts/uninstall_rpm.sh.golden create mode 100644 server/service/testdata/software-installers/ruby.rpm diff --git a/changes/20537-add-rpm-support b/changes/20537-add-rpm-support new file mode 100644 index 000000000000..2238298b2413 --- /dev/null +++ b/changes/20537-add-rpm-support @@ -0,0 +1 @@ +* Added support for uploading RPM packages. diff --git a/cmd/fleetctl/gitops_test.go b/cmd/fleetctl/gitops_test.go index 5a688b2fd91b..295172612d74 100644 --- a/cmd/fleetctl/gitops_test.go +++ b/cmd/fleetctl/gitops_test.go @@ -1727,7 +1727,7 @@ func TestGitOpsTeamSofwareInstallers(t *testing.T) { wantErr string }{ {"testdata/gitops/team_software_installer_not_found.yml", "Please make sure that URLs are reachable from your Fleet server."}, - {"testdata/gitops/team_software_installer_unsupported.yml", "The file should be .pkg, .msi, .exe or .deb."}, + {"testdata/gitops/team_software_installer_unsupported.yml", "The file should be .pkg, .msi, .exe, .deb or .rpm."}, {"testdata/gitops/team_software_installer_too_large.yml", "The maximum file size is 500 MiB"}, {"testdata/gitops/team_software_installer_valid.yml", ""}, {"testdata/gitops/team_software_installer_valid_apply.yml", ""}, @@ -1782,7 +1782,7 @@ func TestGitOpsNoTeamSoftwareInstallers(t *testing.T) { wantErr string }{ {"testdata/gitops/no_team_software_installer_not_found.yml", "Please make sure that URLs are reachable from your Fleet server."}, - {"testdata/gitops/no_team_software_installer_unsupported.yml", "The file should be .pkg, .msi, .exe or .deb."}, + {"testdata/gitops/no_team_software_installer_unsupported.yml", "The file should be .pkg, .msi, .exe, .deb or .rpm."}, {"testdata/gitops/no_team_software_installer_too_large.yml", "The maximum file size is 500 MiB"}, {"testdata/gitops/no_team_software_installer_valid.yml", ""}, {"testdata/gitops/no_team_software_installer_pre_condition_multiple_queries.yml", "should have only one query."}, diff --git a/ee/server/service/software_installers.go b/ee/server/service/software_installers.go index ac4461a592b2..a2dea22138bb 100644 --- a/ee/server/service/software_installers.go +++ b/ee/server/service/software_installers.go @@ -1054,7 +1054,7 @@ func (svc *Service) addMetadataToSoftwarePayload(ctx context.Context, payload *f if err != nil { if errors.Is(err, file.ErrUnsupportedType) { return "", &fleet.BadRequestError{ - Message: "Couldn't edit software. File type not supported. The file should be .pkg, .msi, .exe or .deb.", + Message: "Couldn't edit software. File type not supported. The file should be .pkg, .msi, .exe, .deb or .rpm.", InternalErr: ctxerr.Wrap(ctx, err, "extracting metadata from installer"), } } @@ -1517,7 +1517,7 @@ func packageExtensionToPlatform(ext string) string { requiredPlatform = "windows" case ".pkg": requiredPlatform = "darwin" - case ".deb": + case ".deb", ".rpm": requiredPlatform = "linux" default: return "" diff --git a/frontend/interfaces/package_type.ts b/frontend/interfaces/package_type.ts index 8afb43cef382..b8b83b93fe9d 100644 --- a/frontend/interfaces/package_type.ts +++ b/frontend/interfaces/package_type.ts @@ -1,4 +1,4 @@ -const unixPackageTypes = ["pkg", "deb"] as const; +const unixPackageTypes = ["pkg", "deb", "rpm"] as const; const windowsPackageTypes = ["msi", "exe"] as const; export const packageTypes = [ ...unixPackageTypes, diff --git a/frontend/interfaces/software.ts b/frontend/interfaces/software.ts index a3e633a8bb6b..0992aad99a05 100644 --- a/frontend/interfaces/software.ts +++ b/frontend/interfaces/software.ts @@ -270,7 +270,7 @@ export interface ISoftwareInstallResults { // ISoftwareInstallerType defines the supported installer types for // software uploaded by the IT admin. -export type ISoftwareInstallerType = "pkg" | "msi" | "deb" | "exe"; +export type ISoftwareInstallerType = "pkg" | "msi" | "deb" | "rpm" | "exe"; export interface ISoftwareLastInstall { install_uuid: string; diff --git a/frontend/pages/SoftwarePage/components/PackageAdvancedOptions/PackageAdvancedOptions.tsx b/frontend/pages/SoftwarePage/components/PackageAdvancedOptions/PackageAdvancedOptions.tsx index ad06e516ec0a..4ab30c32c859 100644 --- a/frontend/pages/SoftwarePage/components/PackageAdvancedOptions/PackageAdvancedOptions.tsx +++ b/frontend/pages/SoftwarePage/components/PackageAdvancedOptions/PackageAdvancedOptions.tsx @@ -23,6 +23,7 @@ const getSupportedScriptTypeText = (pkgType: PackageType) => { const PKG_TYPE_TO_ID_TEXT = { pkg: "package IDs", deb: "package name", + rpm: "package name", msi: "product code", exe: "software name", } as const; diff --git a/frontend/pages/SoftwarePage/components/PackageForm/PackageForm.tsx b/frontend/pages/SoftwarePage/components/PackageForm/PackageForm.tsx index 343423d2d829..4e9d7aa74de0 100644 --- a/frontend/pages/SoftwarePage/components/PackageForm/PackageForm.tsx +++ b/frontend/pages/SoftwarePage/components/PackageForm/PackageForm.tsx @@ -58,7 +58,7 @@ interface IPackageFormProps { defaultSelfService?: boolean; } -const ACCEPTED_EXTENSIONS = ".pkg,.msi,.exe,.deb"; +const ACCEPTED_EXTENSIONS = ".pkg,.msi,.exe,.deb,.rpm"; const PackageForm = ({ isUploading, @@ -173,7 +173,7 @@ const PackageForm = ({ canEdit={isEditingSoftware} graphicName={"file-pkg"} accept={ACCEPTED_EXTENSIONS} - message=".pkg, .msi, .exe, or .deb" + message=".pkg, .msi, .exe, .deb, or .rpm" onFileUpload={onFileSelect} buttonMessage="Choose file" buttonType="link" diff --git a/frontend/pages/policies/ManagePoliciesPage/components/InstallSoftwareModal/InstallSoftwareModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/InstallSoftwareModal/InstallSoftwareModal.tsx index 84b8c01ffa42..483a786e62eb 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/InstallSoftwareModal/InstallSoftwareModal.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/InstallSoftwareModal/InstallSoftwareModal.tsx @@ -28,6 +28,7 @@ const getPlatformDisplayFromPackageSuffix = (packageName: string) => { case "pkg": return "macOS"; case "deb": + case "rpm": return "Linux"; case "exe": return "Windows"; diff --git a/frontend/utilities/file/fileUtils.tests.ts b/frontend/utilities/file/fileUtils.tests.ts index 8d650638920a..f21f84001959 100644 --- a/frontend/utilities/file/fileUtils.tests.ts +++ b/frontend/utilities/file/fileUtils.tests.ts @@ -2,15 +2,22 @@ import { getPlatformDisplayName } from "./fileUtils"; describe("fileUtils", () => { describe("getPlatformDisplayName", () => { - it("should return the correct platform display name depending on the file extension", () => { - const file = new File([""], "test.pkg"); - expect(getPlatformDisplayName(file)).toEqual("macOS"); + const testCases = [ + { extension: "pkg", platform: "macOS" }, + { extension: "json", platform: "macOS" }, + { extension: "mobileconfig", platform: "macOS" }, + { extension: "exe", platform: "Windows" }, + { extension: "msi", platform: "Windows" }, + { extension: "xml", platform: "Windows" }, + { extension: "deb", platform: "Linux" }, + { extension: "rpm", platform: "Linux" }, + ]; - const file2 = new File([""], "test.exe"); - expect(getPlatformDisplayName(file2)).toEqual("Windows"); - - const file3 = new File([""], "test.deb"); - expect(getPlatformDisplayName(file3)).toEqual("linux"); + testCases.forEach(({ extension, platform }) => { + it(`should return ${platform} for .${extension} files`, () => { + const file = new File([""], `test.${extension}`); + expect(getPlatformDisplayName(file)).toEqual(platform); + }); }); }); }); diff --git a/frontend/utilities/file/fileUtils.ts b/frontend/utilities/file/fileUtils.ts index ca7b74fdd12b..6853a68a69c3 100644 --- a/frontend/utilities/file/fileUtils.ts +++ b/frontend/utilities/file/fileUtils.ts @@ -1,4 +1,4 @@ -type IPlatformDisplayName = "macOS" | "Windows" | "linux"; +type IPlatformDisplayName = "macOS" | "Windows" | "Linux"; const getFileExtension = (file: File) => { const nameParts = file.name.split("."); @@ -15,7 +15,8 @@ export const FILE_EXTENSIONS_TO_PLATFORM_DISPLAY_NAME: Record< exe: "Windows", msi: "Windows", xml: "Windows", - deb: "linux", + deb: "Linux", + rpm: "Linux", }; /** diff --git a/frontend/utilities/software_install_scripts.ts b/frontend/utilities/software_install_scripts.ts index 5c21e8a1f465..be0a9f30d3ce 100644 --- a/frontend/utilities/software_install_scripts.ts +++ b/frontend/utilities/software_install_scripts.ts @@ -6,6 +6,8 @@ import installMsi from "../../pkg/file/scripts/install_msi.ps1"; import installExe from "../../pkg/file/scripts/install_exe.ps1"; // @ts-ignore import installDeb from "../../pkg/file/scripts/install_deb.sh"; +// @ts-ignore +import installRPM from "../../pkg/file/scripts/install_rpm.sh"; /* * getInstallScript returns a string with a script to install the @@ -20,6 +22,8 @@ const getDefaultInstallScript = (fileName: string): string => { return installMsi; case "deb": return installDeb; + case "rpm": + return installRPM; case "exe": return installExe; default: diff --git a/frontend/utilities/software_uninstall_scripts.ts b/frontend/utilities/software_uninstall_scripts.ts index 041d2519c4c1..172c53c14f68 100644 --- a/frontend/utilities/software_uninstall_scripts.ts +++ b/frontend/utilities/software_uninstall_scripts.ts @@ -6,6 +6,8 @@ import uninstallMsi from "../../pkg/file/scripts/uninstall_msi.ps1"; import uninstallExe from "../../pkg/file/scripts/uninstall_exe.ps1"; // @ts-ignore import uninstallDeb from "../../pkg/file/scripts/uninstall_deb.sh"; +// @ts-ignore +import uninstallRPM from "../../pkg/file/scripts/uninstall_rpm.sh"; /* * getUninstallScript returns a string with a script to uninstall the @@ -20,6 +22,8 @@ const getDefaultUninstallScript = (fileName: string): string => { return uninstallMsi; case "deb": return uninstallDeb; + case "rpm": + return uninstallRPM; case "exe": return uninstallExe; default: diff --git a/go.mod b/go.mod index 056fe1b18413..83670b64fdd1 100644 --- a/go.mod +++ b/go.mod @@ -199,6 +199,8 @@ require ( github.com/caarlos0/env/v6 v6.7.0 // indirect github.com/caarlos0/go-shellwords v1.0.12 // indirect github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e // indirect + github.com/cavaliergopher/cpio v1.0.1 // indirect + github.com/cavaliergopher/rpm v1.2.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cloudflare/circl v1.3.8 // indirect diff --git a/go.sum b/go.sum index beec8ba3e049..8bf177e83697 100644 --- a/go.sum +++ b/go.sum @@ -320,6 +320,10 @@ github.com/caarlos0/testfs v0.4.3 h1:q1zEM5hgsssqWanAfevJYYa0So60DdK6wlJeTc/yfUE github.com/caarlos0/testfs v0.4.3/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk= github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e h1:hHg27A0RSSp2Om9lubZpiMgVbvn39bsUmW9U5h0twqc= github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A= +github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM= +github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc= +github.com/cavaliergopher/rpm v1.2.0 h1:s0h+QeVK252QFTolkhGiMeQ1f+tMeIMhGl8B1HUmGUc= +github.com/cavaliergopher/rpm v1.2.0/go.mod h1:R0q3vTqa7RUvPofAZYrnjJ63hh2vngjFfphuXiExVos= github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= diff --git a/orbit/pkg/installer/installer.go b/orbit/pkg/installer/installer.go index c8fac3fcca4a..4059416f31ca 100644 --- a/orbit/pkg/installer/installer.go +++ b/orbit/pkg/installer/installer.go @@ -22,8 +22,10 @@ import ( "github.com/rs/zerolog/log" ) -type QueryResponse = osquery_gen.ExtensionResponse -type QueryResponseStatus = osquery_gen.ExtensionStatus +type ( + QueryResponse = osquery_gen.ExtensionResponse + QueryResponseStatus = osquery_gen.ExtensionStatus +) // Client defines the methods required for the API requests to the server. The // fleet.OrbitClient type satisfies this interface. @@ -202,7 +204,7 @@ func (r *Runner) installSoftware(ctx context.Context, installID string) (*fleet. return payload, fmt.Errorf("creating temporary directory: %w", err) } - log.Debug().Msgf("about to download software installer") + log.Debug().Str("install_id", installID).Msgf("about to download software installer") installerPath, err := r.OrbitClient.DownloadSoftwareInstaller(installer.InstallerID, tmpDir) if err != nil { return payload, err @@ -233,7 +235,7 @@ func (r *Runner) installSoftware(ctx context.Context, installID string) (*fleet. } if installer.PostInstallScript != "" { - log.Debug().Msgf("about to run post-install script") + log.Debug().Msgf("about to run post-install script for %s", installerPath) postOutput, postExitCode, postErr := r.runInstallerScript(ctx, installer.PostInstallScript, installerPath, "post-install-script"+scriptExtension) payload.PostInstallScriptOutput = &postOutput payload.PostInstallScriptExitCode = &postExitCode diff --git a/pkg/file/file.go b/pkg/file/file.go index 7abd79169f1d..45774a3e9d8c 100644 --- a/pkg/file/file.go +++ b/pkg/file/file.go @@ -42,6 +42,8 @@ func ExtractInstallerMetadata(r io.Reader) (*InstallerMetadata, error) { switch extension { case "deb": meta, err = ExtractDebMetadata(br) + case "rpm": + meta, err = ExtractRPMMetadata(br) case "exe": meta, err = ExtractPEMetadata(br) case "pkg": @@ -59,12 +61,16 @@ func ExtractInstallerMetadata(r io.Reader) (*InstallerMetadata, error) { return meta, err } +// typeFromBytes deduces the type from the magic bytes. +// See https://en.wikipedia.org/wiki/List_of_file_signatures. func typeFromBytes(br *bufio.Reader) (string, error) { switch { case hasPrefix(br, []byte{0x78, 0x61, 0x72, 0x21}): return "pkg", nil case hasPrefix(br, []byte("!\ndebian")): return "deb", nil + case hasPrefix(br, []byte{0xed, 0xab, 0xee, 0xdb}): + return "rpm", nil case hasPrefix(br, []byte{0xd0, 0xcf}): return "msi", nil case hasPrefix(br, []byte("MZ")): diff --git a/pkg/file/management.go b/pkg/file/management.go index 26d1294952a9..3f83255142d2 100644 --- a/pkg/file/management.go +++ b/pkg/file/management.go @@ -16,6 +16,9 @@ var installExeScript string //go:embed scripts/install_deb.sh var installDebScript string +//go:embed scripts/install_rpm.sh +var installRPMScript string + // GetInstallScript returns a script that can be used to install the given extension func GetInstallScript(extension string) string { switch extension { @@ -23,6 +26,8 @@ func GetInstallScript(extension string) string { return installMsiScript case "deb": return installDebScript + case "rpm": + return installRPMScript case "pkg": return installPkgScript case "exe": @@ -44,6 +49,9 @@ var removeMsiScript string //go:embed scripts/remove_deb.sh var removeDebScript string +//go:embed scripts/remove_rpm.sh +var removeRPMScript string + // GetRemoveScript returns a script that can be used to remove an // installer with the given extension. func GetRemoveScript(extension string) string { @@ -52,6 +60,8 @@ func GetRemoveScript(extension string) string { return removeMsiScript case "deb": return removeDebScript + case "rpm": + return removeRPMScript case "pkg": return removePkgScript case "exe": @@ -73,6 +83,9 @@ var uninstallMsiScript string //go:embed scripts/uninstall_deb.sh var uninstallDebScript string +//go:embed scripts/uninstall_rpm.sh +var uninstallRPMScript string + // GetUninstallScript returns a script that can be used to uninstall a // software item with the given extension. func GetUninstallScript(extension string) string { @@ -81,6 +94,8 @@ func GetUninstallScript(extension string) string { return uninstallMsiScript case "deb": return uninstallDebScript + case "rpm": + return uninstallRPMScript case "pkg": return uninstallPkgScript case "exe": diff --git a/pkg/file/management_test.go b/pkg/file/management_test.go index 8b5d8608c589..6f94fb293458 100644 --- a/pkg/file/management_test.go +++ b/pkg/file/management_test.go @@ -11,9 +11,7 @@ import ( "github.com/stretchr/testify/require" ) -var ( - update = flag.Bool("update", false, "update the golden files of this test") -) +var update = flag.Bool("update", false, "update the golden files of this test") func TestMain(m *testing.M) { flag.Parse() @@ -41,6 +39,11 @@ func TestGetInstallAndRemoveScript(t *testing.T) { "remove": "./scripts/remove_deb.sh", "uninstall": "./scripts/uninstall_deb.sh", }, + "rpm": { + "install": "./scripts/install_rpm.sh", + "remove": "./scripts/remove_rpm.sh", + "uninstall": "./scripts/uninstall_rpm.sh", + }, "exe": { "install": "./scripts/install_exe.ps1", "remove": "./scripts/remove_exe.ps1", @@ -64,7 +67,7 @@ func assertGoldenMatches(t *testing.T, goldenFile string, actual string, update t.Helper() goldenPath := filepath.Join("testdata", goldenFile+".golden") - f, err := os.OpenFile(goldenPath, os.O_RDWR|os.O_CREATE, 0644) + f, err := os.OpenFile(goldenPath, os.O_RDWR|os.O_CREATE, 0o644) require.NoError(t, err) defer f.Close() diff --git a/pkg/file/rpm.go b/pkg/file/rpm.go new file mode 100644 index 000000000000..b82221a46b70 --- /dev/null +++ b/pkg/file/rpm.go @@ -0,0 +1,33 @@ +package file + +import ( + "crypto/sha256" + "fmt" + "io" + + "github.com/cavaliergopher/rpm" +) + +func ExtractRPMMetadata(r io.Reader) (*InstallerMetadata, error) { + h := sha256.New() + r = io.TeeReader(r, h) + + // Read the package headers + pkg, err := rpm.Read(r) + if err != nil { + return nil, fmt.Errorf("read headers: %w", err) + } + // r is now positioned at the RPM payload. + + // Ensure the whole file is read to get the correct hash + if _, err := io.Copy(io.Discard, r); err != nil { + return nil, fmt.Errorf("read all RPM content: %w", err) + } + + return &InstallerMetadata{ + Name: pkg.Name(), + Version: pkg.Version(), + SHASum: h.Sum(nil), + PackageIDs: []string{pkg.Name()}, + }, nil +} diff --git a/pkg/file/rpm_test.go b/pkg/file/rpm_test.go new file mode 100644 index 000000000000..d9d3127250e2 --- /dev/null +++ b/pkg/file/rpm_test.go @@ -0,0 +1,94 @@ +package file + +import ( + "crypto/sha256" + "io" + "os" + "path/filepath" + "testing" + + "github.com/fleetdm/fleet/v4/orbit/pkg/constant" + "github.com/goreleaser/nfpm/v2" + "github.com/goreleaser/nfpm/v2/files" + "github.com/goreleaser/nfpm/v2/rpm" + "github.com/stretchr/testify/require" +) + +func TestExtractRPMMetadata(t *testing.T) { + // + // Build an RPM package on the fly with nfpm. + // + tmpDir := t.TempDir() + err := os.WriteFile(filepath.Join(tmpDir, "foo.sh"), []byte("#!/bin/sh\n\necho \"Foo!\"\n"), constant.DefaultFileMode) + require.NoError(t, err) + contents := files.Contents{ + &files.Content{ + Source: filepath.Join(tmpDir, "**"), + Destination: "/", + }, + } + postInstallPath := filepath.Join(t.TempDir(), "postinstall.sh") + err = os.WriteFile(postInstallPath, []byte("#!/bin/sh\n\necho \"Hello world!\"\n"), constant.DefaultFileMode) + require.NoError(t, err) + info := &nfpm.Info{ + Name: "foobar", + Version: "1.2.3", + Description: "Foo bar", + Arch: "x86_64", + Maintainer: "Fleet Device Management", + Vendor: "Fleet Device Management", + License: "LICENSE", + Homepage: "https://example.com", + Overridables: nfpm.Overridables{ + Contents: contents, + Scripts: nfpm.Scripts{ + PostInstall: postInstallPath, + }, + }, + } + rpmPath := filepath.Join(t.TempDir(), "foobar.rpm") + out, err := os.OpenFile(rpmPath, os.O_CREATE|os.O_RDWR, constant.DefaultFileMode) + require.NoError(t, err) + t.Cleanup(func() { + out.Close() + }) + err = rpm.Default.Package(info, out) + require.NoError(t, err) + err = out.Close() + require.NoError(t, err) + + // + // Test ExtractRPMMetadata with the generated package. + // Using ExtractInstallerMetadata for broader testing (for a file + // with rpm extension it will call ExtractRPMMetadata). + // + f, err := os.Open(rpmPath) + require.NoError(t, err) + t.Cleanup(func() { + f.Close() + }) + m, err := ExtractInstallerMetadata(f) + require.NoError(t, err) + err = f.Close() + require.NoError(t, err) + require.Empty(t, m.BundleIdentifier) + require.Equal(t, "rpm", m.Extension) + require.Equal(t, "foobar", m.Name) + require.Equal(t, []string{"foobar"}, m.PackageIDs) + require.Equal(t, sha256FilePath(t, rpmPath), m.SHASum) + require.Equal(t, "1.2.3", m.Version) +} + +func sha256FilePath(t *testing.T, path string) []byte { + f, err := os.Open(path) + require.NoError(t, err) + t.Cleanup(func() { + f.Close() + }) + h := sha256.New() + _, err = io.Copy(h, f) + require.NoError(t, err) + err = f.Close() + require.NoError(t, err) + return h.Sum(nil) +} diff --git a/pkg/file/scripts/README.md b/pkg/file/scripts/README.md index 606801ce682c..c2e0bff612bb 100644 --- a/pkg/file/scripts/README.md +++ b/pkg/file/scripts/README.md @@ -3,10 +3,15 @@ This folder contains scripts to install/remove software for different types of installers. Scripts are stored on their own files for two reasons: - 1. Some of them are read and displayed in the UI. 2. It's helpful to have good syntax highlighting and easy ways to run them. +#### Scripts + +- `install_*.*`: Default installer scripts for each platform. +- `uninstall_*.*`: Default uinstaller scripts for each platform. +- `remove_*.*`: Uninstaller scripts used when the uninstall script is not set (for packages added before the uninstall feature was released) or empty uninstaller scripts. + #### Variables The scripts in this folder accept variables like `$VAR_NAME` that will be replaced/populated by `fleetd` when they run. @@ -14,4 +19,3 @@ The scripts in this folder accept variables like `$VAR_NAME` that will be replac Supported variables are: - `$INSTALLER_PATH` path to the installer file. - diff --git a/pkg/file/scripts/install_rpm.sh b/pkg/file/scripts/install_rpm.sh new file mode 100644 index 000000000000..fb01bc2acda5 --- /dev/null +++ b/pkg/file/scripts/install_rpm.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +dnf install --assumeyes "$INSTALLER_PATH" diff --git a/pkg/file/scripts/remove_rpm.sh b/pkg/file/scripts/remove_rpm.sh new file mode 100644 index 000000000000..efd8e0bb0c89 --- /dev/null +++ b/pkg/file/scripts/remove_rpm.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +package_name=$PACKAGE_ID + +# Fleet uninstalls app using product name that's extracted on upload +dnf remove --assumeyes "$package_name" diff --git a/pkg/file/scripts/uninstall_rpm.sh b/pkg/file/scripts/uninstall_rpm.sh new file mode 100644 index 000000000000..0d664e32f439 --- /dev/null +++ b/pkg/file/scripts/uninstall_rpm.sh @@ -0,0 +1,4 @@ +package_name=$PACKAGE_ID + +# Fleet uninstalls app using product name that's extracted on upload +dnf remove --assumeyes "$package_name" diff --git a/pkg/file/testdata/scripts/install_rpm.sh.golden b/pkg/file/testdata/scripts/install_rpm.sh.golden new file mode 100644 index 000000000000..fb01bc2acda5 --- /dev/null +++ b/pkg/file/testdata/scripts/install_rpm.sh.golden @@ -0,0 +1,3 @@ +#!/bin/sh + +dnf install --assumeyes "$INSTALLER_PATH" diff --git a/pkg/file/testdata/scripts/remove_rpm.sh.golden b/pkg/file/testdata/scripts/remove_rpm.sh.golden new file mode 100644 index 000000000000..efd8e0bb0c89 --- /dev/null +++ b/pkg/file/testdata/scripts/remove_rpm.sh.golden @@ -0,0 +1,6 @@ +#!/bin/sh + +package_name=$PACKAGE_ID + +# Fleet uninstalls app using product name that's extracted on upload +dnf remove --assumeyes "$package_name" diff --git a/pkg/file/testdata/scripts/uninstall_rpm.sh.golden b/pkg/file/testdata/scripts/uninstall_rpm.sh.golden new file mode 100644 index 000000000000..0d664e32f439 --- /dev/null +++ b/pkg/file/testdata/scripts/uninstall_rpm.sh.golden @@ -0,0 +1,4 @@ +package_name=$PACKAGE_ID + +# Fleet uninstalls app using product name that's extracted on upload +dnf remove --assumeyes "$package_name" diff --git a/server/datastore/mysql/software_installers.go b/server/datastore/mysql/software_installers.go index da9f5f7ff395..c14942189a71 100644 --- a/server/datastore/mysql/software_installers.go +++ b/server/datastore/mysql/software_installers.go @@ -557,6 +557,7 @@ SELECT hsi.self_service, hsi.host_deleted_at, hsi.created_at as created_at, + hsi.updated_at as updated_at, si.user_id AS software_installer_user_id, si.user_name AS software_installer_user_name, si.user_email AS software_installer_user_email diff --git a/server/datastore/mysql/software_installers_test.go b/server/datastore/mysql/software_installers_test.go index 49be5c29c220..487f2ddaadba 100644 --- a/server/datastore/mysql/software_installers_test.go +++ b/server/datastore/mysql/software_installers_test.go @@ -511,8 +511,18 @@ func testGetSoftwareInstallResult(t *testing.T, ds *Datastore) { }) require.NoError(t, err) + beforeInstallRequest := time.Now() installUUID, err := ds.InsertSoftwareInstallRequest(ctx, host.ID, installerID, false) require.NoError(t, err) + + res, err := ds.GetSoftwareInstallResults(ctx, installUUID) + require.NoError(t, err) + require.NotNil(t, res.UpdatedAt) + require.Less(t, beforeInstallRequest, res.CreatedAt) + createdAt := res.CreatedAt + require.Less(t, beforeInstallRequest, *res.UpdatedAt) + + beforeInstallResult := time.Now() err = ds.SetHostSoftwareInstallResult(ctx, &fleet.HostSoftwareInstallResultPayload{ HostID: host.ID, InstallUUID: installUUID, @@ -524,7 +534,7 @@ func testGetSoftwareInstallResult(t *testing.T, ds *Datastore) { }) require.NoError(t, err) - res, err := ds.GetSoftwareInstallResults(ctx, installUUID) + res, err = ds.GetSoftwareInstallResults(ctx, installUUID) require.NoError(t, err) require.Equal(t, installUUID, res.InstallUUID) @@ -534,6 +544,10 @@ func testGetSoftwareInstallResult(t *testing.T, ds *Datastore) { require.Equal(t, tc.preInstallQueryOutput, res.PreInstallQueryOutput) require.Equal(t, tc.postInstallScriptOutput, res.PostInstallScriptOutput) require.Equal(t, tc.installScriptOutput, res.Output) + require.NotNil(t, res.CreatedAt) + require.Equal(t, createdAt, res.CreatedAt) + require.NotNil(t, res.UpdatedAt) + require.Less(t, beforeInstallResult, *res.UpdatedAt) }) } } diff --git a/server/fleet/software_installer.go b/server/fleet/software_installer.go index 09debe069579..84bcefa02ff6 100644 --- a/server/fleet/software_installer.go +++ b/server/fleet/software_installer.go @@ -363,6 +363,8 @@ func SofwareInstallerSourceFromExtensionAndName(ext, name string) (string, error switch ext { case "deb": return "deb_packages", nil + case "rpm": + return "rpm_packages", nil case "exe", "msi": return "programs", nil case "pkg": @@ -378,7 +380,7 @@ func SofwareInstallerSourceFromExtensionAndName(ext, name string) (string, error func SofwareInstallerPlatformFromExtension(ext string) (string, error) { ext = strings.TrimPrefix(ext, ".") switch ext { - case "deb": + case "deb", "rpm": return "linux", nil case "exe", "msi": return "windows", nil diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go index 3d6673afd3b6..79f11cdbeb9e 100644 --- a/server/service/integration_enterprise_test.go +++ b/server/service/integration_enterprise_test.go @@ -14404,3 +14404,74 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallersWithoutBundleIden } s.uploadSoftwareInstaller(payload, http.StatusOK, "") } + +func (s *integrationEnterpriseTestSuite) TestSoftwareUploadRPM() { + ctx := context.Background() + t := s.T() + + // Fedora and RHEL have hosts.platform = 'rhel'. + host := createOrbitEnrolledHost(t, "rhel", "", s.ds) + + // Upload an RPM package. + payload := &fleet.UploadSoftwareInstallerPayload{ + InstallScript: "install script", + PreInstallQuery: "pre install query", + PostInstallScript: "post install script", + Filename: "ruby.rpm", + Title: "ruby", + } + s.uploadSoftwareInstaller(payload, http.StatusOK, "") + titleID := getSoftwareTitleID(t, s.ds, payload.Title, "rpm_packages") + + latestInstallUUID := func() string { + var id string + mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error { + return sqlx.GetContext(ctx, q, &id, `SELECT execution_id FROM host_software_installs ORDER BY id DESC LIMIT 1`) + }) + return id + } + + // Send a request to the host to install the RPM package. + var installSoftwareResp installSoftwareResponse + beforeInstallRequest := time.Now() + s.DoJSON("POST", fmt.Sprintf("/api/v1/fleet/hosts/%d/software/%d/install", host.ID, titleID), nil, http.StatusAccepted, &installSoftwareResp) + installUUID := latestInstallUUID() + + // Simulate host installing the RPM package. + beforeInstallResult := time.Now() + s.Do("POST", "/api/fleet/orbit/software_install/result", + json.RawMessage(fmt.Sprintf(`{ + "orbit_node_key": %q, + "install_uuid": %q, + "pre_install_condition_output": "1", + "install_script_exit_code": 1, + "install_script_output": "failed" + }`, *host.OrbitNodeKey, installUUID)), + http.StatusNoContent, + ) + + var resp getSoftwareInstallResultsResponse + s.DoJSON("GET", fmt.Sprintf("/api/v1/fleet/software/install/%s/results", installUUID), nil, http.StatusOK, &resp) + assert.Equal(t, host.ID, resp.Results.HostID) + assert.Equal(t, installUUID, resp.Results.InstallUUID) + assert.Equal(t, fleet.SoftwareInstallFailed, resp.Results.Status) + assert.NotNil(t, resp.Results.PreInstallQueryOutput) + assert.Equal(t, fleet.SoftwareInstallerQuerySuccessCopy, *resp.Results.PreInstallQueryOutput) + assert.NotNil(t, resp.Results.Output) + assert.Equal(t, fmt.Sprintf(fleet.SoftwareInstallerInstallFailCopy, "failed"), *resp.Results.Output) + assert.Empty(t, resp.Results.PostInstallScriptOutput) + assert.Less(t, beforeInstallRequest, resp.Results.CreatedAt) + assert.Greater(t, time.Now(), resp.Results.CreatedAt) + assert.NotNil(t, resp.Results.UpdatedAt) + assert.Less(t, beforeInstallResult, *resp.Results.UpdatedAt) + + wantAct := fleet.ActivityTypeInstalledSoftware{ + HostID: host.ID, + HostDisplayName: host.DisplayName(), + SoftwareTitle: payload.Title, + SoftwarePackage: payload.Filename, + InstallUUID: installUUID, + Status: string(fleet.SoftwareInstallFailed), + } + s.lastActivityMatches(wantAct.ActivityName(), string(jsonMustMarshal(t, wantAct)), 0) +} diff --git a/server/service/testdata/software-installers/README.md b/server/service/testdata/software-installers/README.md index b5a59d9daf64..7baa9592be02 100644 --- a/server/service/testdata/software-installers/README.md +++ b/server/service/testdata/software-installers/README.md @@ -1,3 +1,4 @@ # testdata -- `fleet-osquery.msi` is a dummy MSI installer created by `packaging.BuildMSI` with a fake `orbit.exe` that just has `hello world` in it. Its software title is `Fleet osquery` and its version is `1.0.0`. \ No newline at end of file +- `fleet-osquery.msi` is a dummy MSI installer created by `packaging.BuildMSI` with a fake `orbit.exe` that just has `hello world` in it. Its software title is `Fleet osquery` and its version is `1.0.0`. +- `ruby.rpm` was downloaded from https://rpmfind.net/linux/fedora/linux/development/rawhide/Everything/x86_64/os/Packages/r/ruby-3.3.5-15.fc42.x86_64.rpm. \ No newline at end of file diff --git a/server/service/testdata/software-installers/ruby.rpm b/server/service/testdata/software-installers/ruby.rpm new file mode 100644 index 0000000000000000000000000000000000000000..e7020796dab0014408676cc5777297253bf075b0 GIT binary patch literal 40422 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 literal 0 HcmV?d00001 From 24c84edd7b8737d8de7dd0bb5a907a8d6b0272dc Mon Sep 17 00:00:00 2001 From: Jahziel Villasana-Espinoza Date: Tue, 1 Oct 2024 12:32:41 -0400 Subject: [PATCH 027/385] fix: attempt to prevent race in profile adding and deleting (#22338) > Follow up on: #21891 # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [x] Added/updated tests - [x] Manual QA for all new/changed functionality --- server/datastore/mysql/apple_mdm.go | 29 +++++++++-- server/service/apple_mdm.go | 3 ++ .../service/integration_mdm_profiles_test.go | 51 +++++++++++++++++-- 3 files changed, 74 insertions(+), 9 deletions(-) diff --git a/server/datastore/mysql/apple_mdm.go b/server/datastore/mysql/apple_mdm.go index 610a25db52fa..ad18242eb754 100644 --- a/server/datastore/mysql/apple_mdm.go +++ b/server/datastore/mysql/apple_mdm.go @@ -291,7 +291,9 @@ WHERE } func (ds *Datastore) DeleteMDMAppleConfigProfileByDeprecatedID(ctx context.Context, profileID uint) error { - return ds.deleteMDMAppleConfigProfileByIDOrUUID(ctx, profileID, "") + return ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error { + return deleteMDMAppleConfigProfileByIDOrUUID(ctx, tx, profileID, "") + }) } func (ds *Datastore) DeleteMDMAppleConfigProfile(ctx context.Context, profileUUID string) error { @@ -299,10 +301,20 @@ func (ds *Datastore) DeleteMDMAppleConfigProfile(ctx context.Context, profileUUI if strings.HasPrefix(profileUUID, fleet.MDMAppleDeclarationUUIDPrefix) { return ds.deleteMDMAppleDeclaration(ctx, profileUUID) } - return ds.deleteMDMAppleConfigProfileByIDOrUUID(ctx, 0, profileUUID) + return ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error { + if err := deleteMDMAppleConfigProfileByIDOrUUID(ctx, tx, 0, profileUUID); err != nil { + return err + } + + if err := deleteUnsentAppleHostMDMProfile(ctx, tx, profileUUID); err != nil { + return err + } + + return nil + }) } -func (ds *Datastore) deleteMDMAppleConfigProfileByIDOrUUID(ctx context.Context, id uint, uuid string) error { +func deleteMDMAppleConfigProfileByIDOrUUID(ctx context.Context, tx sqlx.ExtContext, id uint, uuid string) error { var arg any stmt := `DELETE FROM mdm_apple_configuration_profiles WHERE ` if uuid != "" { @@ -312,7 +324,7 @@ func (ds *Datastore) deleteMDMAppleConfigProfileByIDOrUUID(ctx context.Context, arg = id stmt += `profile_id = ?` } - res, err := ds.writer(ctx).ExecContext(ctx, stmt, arg) + res, err := tx.ExecContext(ctx, stmt, arg) if err != nil { return ctxerr.Wrap(ctx, err) } @@ -328,6 +340,15 @@ func (ds *Datastore) deleteMDMAppleConfigProfileByIDOrUUID(ctx context.Context, return nil } +func deleteUnsentAppleHostMDMProfile(ctx context.Context, tx sqlx.ExtContext, uuid string) error { + const stmt = `DELETE FROM host_mdm_apple_profiles WHERE profile_uuid = ? AND status IS NULL AND operation_type = ? AND command_uuid = ''` + if _, err := tx.ExecContext(ctx, stmt, uuid, fleet.MDMOperationTypeInstall); err != nil { + return ctxerr.Wrap(ctx, err, "deleting host profile that has not been sent to host") + } + + return nil +} + func (ds *Datastore) DeleteMDMAppleDeclarationByName(ctx context.Context, teamID *uint, name string) error { const stmt = `DELETE FROM mdm_apple_declarations WHERE team_id = ? AND name = ?` diff --git a/server/service/apple_mdm.go b/server/service/apple_mdm.go index 4387367ef017..c96777ea580e 100644 --- a/server/service/apple_mdm.go +++ b/server/service/apple_mdm.go @@ -770,9 +770,12 @@ func (svc *Service) DeleteMDMAppleConfigProfile(ctx context.Context, profileUUID } } + // This call will also delete host_mdm_apple_profiles references IFF the profile has not been sent to + // the host yet. if err := svc.ds.DeleteMDMAppleConfigProfile(ctx, profileUUID); err != nil { return ctxerr.Wrap(ctx, err) } + // cannot use the profile ID as it is now deleted if _, err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, nil, []uint{teamID}, nil, nil); err != nil { return ctxerr.Wrap(ctx, err, "bulk set pending host profiles") diff --git a/server/service/integration_mdm_profiles_test.go b/server/service/integration_mdm_profiles_test.go index 49a1f6eab0cf..1566fae3b114 100644 --- a/server/service/integration_mdm_profiles_test.go +++ b/server/service/integration_mdm_profiles_test.go @@ -4359,6 +4359,9 @@ func (s *integrationMDMTestSuite) TestMDMAppleConfigProfileCRUD() { testTeam, err := s.ds.NewTeam(ctx, &fleet.Team{Name: "TestTeam"}) require.NoError(t, err) + teamDelete, err := s.ds.NewTeam(ctx, &fleet.Team{Name: "TeamDelete"}) + require.NoError(t, err) + testProfiles := make(map[string]fleet.MDMAppleConfigProfile) generateTestProfile := func(name string, identifier string) { i := identifier @@ -4402,6 +4405,12 @@ func (s *integrationMDMTestSuite) TestMDMAppleConfigProfileCRUD() { require.Equal(t, expected.Identifier, actual.Identifier) } + host, _ := createHostThenEnrollMDM(s.ds, s.server.URL, t) + s.Do("POST", "/api/latest/fleet/hosts/transfer", addHostsToTeamRequest{ + TeamID: &teamDelete.ID, + HostIDs: []uint{host.ID}, + }, http.StatusOK) + // create new profile (no team) generateTestProfile("TestNoTeam", "") body, headers := generateNewReq("TestNoTeam", nil) @@ -4421,9 +4430,42 @@ func (s *integrationMDMTestSuite) TestMDMAppleConfigProfileCRUD() { require.NotEmpty(t, newCP.ProfileID) setTestProfileID("TestWithTeamID", newCP.ProfileID) + // Create a profile that we're going to remove immediately + generateTestProfile("TestImmediateDelete", "") + body, headers = generateNewReq("TestImmediateDelete", &teamDelete.ID) + newResp = s.DoRawWithHeaders("POST", "/api/latest/fleet/mdm/apple/profiles", body.Bytes(), http.StatusOK, headers) + newCP = fleet.MDMAppleConfigProfile{} + err = json.NewDecoder(newResp.Body).Decode(&newCP) + require.NoError(t, err) + require.NotEmpty(t, newCP.ProfileID) + setTestProfileID("TestImmediateDelete", newCP.ProfileID) + + // check that host_mdm_apple_profiles entry was created + var hostResp getHostResponse + s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d", host.ID), nil, http.StatusOK, &hostResp) + require.NotNil(t, hostResp.Host.MDM.Profiles) + require.Len(t, *hostResp.Host.MDM.Profiles, 1) + require.Equal(t, (*hostResp.Host.MDM.Profiles)[0].Name, "TestImmediateDelete") + + // now delete the profile before it's sent, we should see the host_mdm_apple_profiles entry go + // away + deletedCP := testProfiles["TestImmediateDelete"] + deletePath := fmt.Sprintf("/api/latest/fleet/mdm/apple/profiles/%d", deletedCP.ProfileID) + var deleteResp deleteMDMAppleConfigProfileResponse + s.DoJSON("DELETE", deletePath, nil, http.StatusOK, &deleteResp) + // confirm deleted + var listResp listMDMAppleConfigProfilesResponse + s.DoJSON("GET", "/api/latest/fleet/mdm/apple/profiles", listMDMAppleConfigProfilesRequest{TeamID: teamDelete.ID}, http.StatusOK, &listResp) + require.Len(t, listResp.ConfigProfiles, 0) + getPath := fmt.Sprintf("/api/latest/fleet/mdm/apple/profiles/%d", deletedCP.ProfileID) + _ = s.DoRawWithHeaders("GET", getPath, nil, http.StatusNotFound, map[string]string{"Authorization": fmt.Sprintf("Bearer %s", s.token)}) + // confirm no host profiles + hostResp = getHostResponse{} + s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d", host.ID), nil, http.StatusOK, &hostResp) + require.Nil(t, hostResp.Host.MDM.Profiles) + // list profiles (no team) expectedCP := testProfiles["TestNoTeam"] - var listResp listMDMAppleConfigProfilesResponse s.DoJSON("GET", "/api/latest/fleet/mdm/apple/profiles", nil, http.StatusOK, &listResp) require.Len(t, listResp.ConfigProfiles, 1) respCP := listResp.ConfigProfiles[0] @@ -4445,7 +4487,7 @@ func (s *integrationMDMTestSuite) TestMDMAppleConfigProfileCRUD() { // get profile (no team) expectedCP = testProfiles["TestNoTeam"] - getPath := fmt.Sprintf("/api/latest/fleet/mdm/apple/profiles/%d", expectedCP.ProfileID) + getPath = fmt.Sprintf("/api/latest/fleet/mdm/apple/profiles/%d", expectedCP.ProfileID) getResp := s.DoRawWithHeaders("GET", getPath, nil, http.StatusOK, map[string]string{"Authorization": fmt.Sprintf("Bearer %s", s.token)}) checkGetResponse(getResp, expectedCP) @@ -4456,9 +4498,8 @@ func (s *integrationMDMTestSuite) TestMDMAppleConfigProfileCRUD() { checkGetResponse(getResp, expectedCP) // delete profile (no team) - deletedCP := testProfiles["TestNoTeam"] - deletePath := fmt.Sprintf("/api/latest/fleet/mdm/apple/profiles/%d", deletedCP.ProfileID) - var deleteResp deleteMDMAppleConfigProfileResponse + deletedCP = testProfiles["TestNoTeam"] + deletePath = fmt.Sprintf("/api/latest/fleet/mdm/apple/profiles/%d", deletedCP.ProfileID) s.DoJSON("DELETE", deletePath, nil, http.StatusOK, &deleteResp) // confirm deleted listResp = listMDMAppleConfigProfilesResponse{} From 8977594a809026ba387918a05ae2c3ab4de1e15c Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Tue, 1 Oct 2024 12:53:09 -0400 Subject: [PATCH 028/385] Product design handbook: missing reference docs are bugs (#22541) This + announcement addresses of the following Fleet objective ([OKRs](https://docs.google.com/spreadsheets/d/1Hso0LxqwrRVINCyW_n436bNHmoqhoLhC8bcbvLPOs9A/edit?gid=1846478041#gid=1846478041)): Handbook and train team that anytime there is a missing or incorrect config setting or REST API in the docs, it is a released bug to be filed and fixed ASAP. --- handbook/product-design/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/handbook/product-design/README.md b/handbook/product-design/README.md index a81f4376f9e7..6b9a74ce64fe 100644 --- a/handbook/product-design/README.md +++ b/handbook/product-design/README.md @@ -137,6 +137,8 @@ Next, the API design DRI reviews all user stories and bugs with the release mile To signal that the reference docs branch is ready for release, the API design DRI opens a PR to `main`, adds the DRI for what goes in a release as the reviewer, and adds the release milestone. +> Anytime there is a missing or incorrect configuration option or REST API endpoint in the docs, it is treated as a released bug to be filed and fixed ASAP. + ### Interview a Product Designer candidate Ensure the interview process follows these steps in order. This process must follow [creating a new position](https://fleetdm.com/handbook/company/leadership#creating-a-new-position) through [receiving job applications](https://fleetdm.com/handbook/company/leadership#receiving-job-applications). From 60bc54d93bfc18dfbdfdc14c6856cf5ee5483b71 Mon Sep 17 00:00:00 2001 From: Martin Angers Date: Tue, 1 Oct 2024 13:25:27 -0400 Subject: [PATCH 029/385] SE: prevent deleting used script content (#22545) --- server/datastore/mysql/schema.sql | 6 +++--- server/datastore/mysql/scripts.go | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql index 4b7012ccc273..5f83203f842e 100644 --- a/server/datastore/mysql/schema.sql +++ b/server/datastore/mysql/schema.sql @@ -1041,9 +1041,9 @@ CREATE TABLE `migration_status_tables` ( `tstamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `id` (`id`) -) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=315 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=317 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; -INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20240927081858,1,'2020-01-01 01:01:01'),(314,20240930171917,1,'2020-01-01 01:01:01'); +INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20240925111236,1,'2020-01-01 01:01:01'),(314,20240925112748,1,'2020-01-01 01:01:01'),(315,20240927081858,1,'2020-01-01 01:01:01'),(316,20240930171917,1,'2020-01-01 01:01:01'); /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `mobile_device_management_solutions` ( @@ -1254,7 +1254,7 @@ CREATE TABLE `nano_users` ( /*!40101 SET character_set_client = @saved_cs_client */; SET @saved_cs_client = @@character_set_client; /*!50503 SET character_set_client = utf8mb4 */; -/*!50001 CREATE VIEW `nano_view_queue` AS SELECT +/*!50001 CREATE VIEW `nano_view_queue` AS SELECT 1 AS `id`, 1 AS `created_at`, 1 AS `active`, diff --git a/server/datastore/mysql/scripts.go b/server/datastore/mysql/scripts.go index f0ad52acb014..4d14703f4b6c 100644 --- a/server/datastore/mysql/scripts.go +++ b/server/datastore/mysql/scripts.go @@ -1166,7 +1166,9 @@ WHERE SELECT 1 FROM software_installers si WHERE script_contents.id IN (si.install_script_content_id, si.post_install_script_content_id, si.uninstall_script_content_id) ) - ` + AND NOT EXISTS ( + SELECT 1 FROM setup_experience_scripts WHERE script_content_id = script_contents.id) +` _, err := ds.writer(ctx).ExecContext(ctx, deleteStmt) if err != nil { return ctxerr.Wrap(ctx, err, "cleaning up unused script contents") From 514ca727ec75c5ba81be17155229d3362bd73e1a Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 1 Oct 2024 15:38:00 -0500 Subject: [PATCH 030/385] Update why-fleet.md (#22499) --- docs/Get started/why-fleet.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Get started/why-fleet.md b/docs/Get started/why-fleet.md index e0db75d68759..6ee517a43da3 100644 --- a/docs/Get started/why-fleet.md +++ b/docs/Get started/why-fleet.md @@ -4,9 +4,9 @@ Fleet is an open-source device management platform for Linux, macOS, Windows, Ch ## What's it for? -Managing computers today is getting harder. You have to juggle a mix of operating systems and devices, with a whole bunch of middleman vendors in between. +Managing computers today is getting harder. You have to juggle a mix of operating systems and devices, with a bunch of middleman vendors in between. -Fleet makes things easier by giving you a single system to manage and secure all your computing devices. You can do MDM, patch stuff, and verify anything—all from one dashboard. It's like having a universal remote control for all your organization's computers. +Fleet makes things easier by giving you a single system to secure and maintain all your computing devices over the air. You can do MDM, patch stuff, and verify anything—all from one system. It's like having a universal remote control for all your organization's computers. Fleet is open source, and free features will always be free. @@ -15,7 +15,7 @@ Fleet is open source, and free features will always be free. Fleet is used in production by IT and security teams with thousands of laptops and servers. Many deployments support tens of thousands of hosts, and a few large organizations manage deployments as large as 400,000+ hosts. - **Get what you need:** Fleet lets you work directly with [data](https://fleetdm.com/integrations) and events from the native operating system. It lets you go all the way down to the bare metal. It’s also modular. (You can turn off features you are not using.) -- **Out of the box:** Ready-to-use integrations exist for the [most common tools](https://fleetdm.com/integrations). You can also build custom workflows with the REST API, webhook events, and the fleetctl command line tool. Or go all in and manage computers [with GitOps](https://fleetdm.com/handbook/company#history). +- **Out of the box:** Ready-to-use integrations exist for the [most common tools](https://fleetdm.com/integrations). You can also build custom workflows with the REST API, webhook events, and the fleetctl command line tool. Or go all in and govern computers [with GitOps](https://github.com/fleetdm/fleet-gitops). - **Good neighbors:** We think tools should be as easy as possible for everyone to understand. We helped [create osquery](https://fleetdm.com/handbook/company#history), and we are committed to improving it. - **Free as in free:** The free version of Fleet will [always be free](https://fleetdm.com/pricing). Fleet is independently backed and actively maintained with the help of many amazing contributors. From a9a9e92f3f1fc3616d9d81e046e38c2a5d2d5df0 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Rodriguez Date: Tue, 1 Oct 2024 17:38:22 -0300 Subject: [PATCH 031/385] Use node version defined in package.json (#22504) We did the same thing for Go. (This allows us to not require admin permissions to update the used Node version in CI.) --- .github/workflows/build-binaries.yaml | 7 +++---- .github/workflows/fleet-and-orbit.yml | 5 ++--- .github/workflows/goreleaser-fleet.yaml | 5 ++--- .github/workflows/goreleaser-snapshot-fleet.yaml | 4 ++-- .github/workflows/test-js.yml | 14 +++++++------- 5 files changed, 16 insertions(+), 19 deletions(-) diff --git a/.github/workflows/build-binaries.yaml b/.github/workflows/build-binaries.yaml index 278f958b2842..8499171c1583 100644 --- a/.github/workflows/build-binaries.yaml +++ b/.github/workflows/build-binaries.yaml @@ -37,11 +37,10 @@ jobs: with: go-version-file: 'go.mod' - # Set the Node.js version - - name: Set up Node.js ${{ vars.NODE_VERSION }} + - name: Set up Node.js uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 with: - node-version: ${{ vars.NODE_VERSION }} + node-version-file: package.json - name: JS Dependency Cache id: js-cache @@ -51,7 +50,7 @@ jobs: **/node_modules # Use a separate cache for this from other JS jobs since we run the # webpack steps and will have more to cache. - key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }}-node_version-${{ vars.NODE_VERSION }} + key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }} restore-keys: | ${{ runner.os }}-node_modules- diff --git a/.github/workflows/fleet-and-orbit.yml b/.github/workflows/fleet-and-orbit.yml index f4dfb2780eb7..22be676c7868 100644 --- a/.github/workflows/fleet-and-orbit.yml +++ b/.github/workflows/fleet-and-orbit.yml @@ -79,11 +79,10 @@ jobs: with: go-version-file: 'go.mod' - # Set the Node.js version - - name: Set up Node.js ${{ vars.NODE_VERSION }} + - name: Set up Node.js uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 with: - node-version: ${{ vars.NODE_VERSION }} + node-version-file: package.json - name: Start tunnel env: diff --git a/.github/workflows/goreleaser-fleet.yaml b/.github/workflows/goreleaser-fleet.yaml index 6ba9aff8f08f..7a23296ef2fe 100644 --- a/.github/workflows/goreleaser-fleet.yaml +++ b/.github/workflows/goreleaser-fleet.yaml @@ -46,11 +46,10 @@ jobs: with: go-version-file: 'go.mod' - # Set the Node.js version - - name: Set up Node.js ${{ vars.NODE_VERSION }} + - name: Set up Node.js uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 with: - node-version: ${{ vars.NODE_VERSION }} + node-version-file: package.json - name: Install JS Dependencies run: make deps-js diff --git a/.github/workflows/goreleaser-snapshot-fleet.yaml b/.github/workflows/goreleaser-snapshot-fleet.yaml index 927cf31be1da..f3c6a9340c96 100644 --- a/.github/workflows/goreleaser-snapshot-fleet.yaml +++ b/.github/workflows/goreleaser-snapshot-fleet.yaml @@ -60,10 +60,10 @@ jobs: go-version-file: 'go.mod' # Set the Node.js version - - name: Set up Node.js ${{ vars.NODE_VERSION }} + - name: Set up Node.js uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 with: - node-version: ${{ vars.NODE_VERSION }} + node-version-file: package.json - name: Install Dependencies run: make deps diff --git a/.github/workflows/test-js.yml b/.github/workflows/test-js.yml index 15b4fd05cee5..2b547e1ad5c7 100644 --- a/.github/workflows/test-js.yml +++ b/.github/workflows/test-js.yml @@ -42,14 +42,14 @@ jobs: with: egress-policy: audit - - name: Set up Node.js ${{ vars.NODE_VERSION }} - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 - with: - node-version: ${{ vars.NODE_VERSION }} - - name: Checkout Code uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - name: Set up Node.js + uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 + with: + node-version-file: package.json + - name: JS Dependency Cache id: js-cache uses: actions/cache@69d9d449aced6a2ede0bc19182fadc3a0a42d2b0 # v2 @@ -87,10 +87,10 @@ jobs: with: egress-policy: audit - - name: Set up Node.js ${{ vars.NODE_VERSION }} + - name: Set up Node.js uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 with: - node-version: ${{ vars.NODE_VERSION }} + node-version-file: package.json - name: Checkout Code uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 From 00d31e84505907824e6a4bfd1183bf6d026e7806 Mon Sep 17 00:00:00 2001 From: Rachael Shaw Date: Tue, 1 Oct 2024 15:39:00 -0500 Subject: [PATCH 032/385] Update linux-device-health.policies.yml (#22516) See https://github.com/fleetdm/fleet/pull/22498 --- it-and-security/lib/linux-device-health.policies.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/it-and-security/lib/linux-device-health.policies.yml b/it-and-security/lib/linux-device-health.policies.yml index 0d9e2f8aa2fb..b7093c02acdc 100644 --- a/it-and-security/lib/linux-device-health.policies.yml +++ b/it-and-security/lib/linux-device-health.policies.yml @@ -1,6 +1,6 @@ - name: Linux - Enable disk encryption - query: SELECT 1 FROM disk_encryption WHERE encrypted=1 AND name LIKE '/dev/dm-1'; + query: SELECT 1 FROM mounts m, disk_encryption d WHERE m.device_alias = d.name AND d.encrypted = 1 AND m.path = '/'; critical: false description: This policy checks if disk encryption is enabled. resolution: As an IT admin, deploy an image that includes disk encryption. - platform: linux \ No newline at end of file + platform: linux From beec753a3f055f63f3eacfb6ce1c84e6fcb9a6d2 Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Tue, 1 Oct 2024 17:07:30 -0400 Subject: [PATCH 033/385] API docs: OTA enrollment profile (#22457) - Bring OTA enrollment profile endpoint into REST API docs --- .../config-less-fleetd-agent-deployment.md | 43 ++++++++++- docs/Contributing/API-for-contributors.md | 1 - docs/REST API/rest-api.md | 75 +++++++++++++++++++ 3 files changed, 116 insertions(+), 3 deletions(-) diff --git a/articles/config-less-fleetd-agent-deployment.md b/articles/config-less-fleetd-agent-deployment.md index eb37159fdcf5..2ca3b5cd7c93 100644 --- a/articles/config-less-fleetd-agent-deployment.md +++ b/articles/config-less-fleetd-agent-deployment.md @@ -4,7 +4,7 @@ Deploying Fleet's agent across a diverse range of devices often involves the crucial step of enrolling each device. Traditionally, this involves [packaging](https://fleetdm.com/docs/using-fleet/fleetd#packaging) `fleetd` with configuration including the enroll secret and server URL. While effective, an alternative offers more flexibility in your deployment process. This guide introduces a different approach for deploying Fleet's agent without embedding configuration settings directly into `fleetd`. Ideal for IT administrators who prefer to generate a single package and maintain greater control over the distribution of enrollment secrets and server URLs, this method simplifies the enrollment process across macOS and Windows hosts. -Emphasizing adaptability and convenience, this approach allows for a more efficient way to manage device enrollments. Let’s dive into how to deploy Fleet's agent using this alternative method, ensuring a more open and flexible deployment process. +This approach emphasizes adaptability and convenience and allows for a more efficient way to manage device enrollments. Let’s explore how to deploy Fleet's agent using this alternative method, ensuring a more open and flexible deployment process. ## For macOS: @@ -44,6 +44,18 @@ fleetctl package --type=pkg --use-system-configuration --fleet-desktop PayloadVersion 1 + + EndUserEmail + END_USER_EMAIL_HERE + PayloadIdentifier + com.fleetdm.fleet.mdm.apple.mdm + PayloadType + com.apple.mdm + PayloadUUID + 29713130-1602-4D27-90C9-B822A295E44E + PayloadVersion + 1 + PayloadDisplayName Fleetd configuration @@ -56,11 +68,38 @@ fleetctl package --type=pkg --use-system-configuration --fleet-desktop PayloadVersion 1 PayloadDescription - Default configuration for the fleetd agent. + Configuration for the fleetd agent. ``` +You can optionally specify the `END_USER_EMAIL` that will be added to the host's [human-device mapping](https://fleetdm.com/docs/rest-api/rest-api#get-human-device-mapping): + +```xml + + + + + PayloadContent + + ... + + EndUserEmail + END_USER_EMAIL + PayloadIdentifier + com.fleetdm.fleet.mdm.apple.mdm + PayloadType + com.apple.mdm + PayloadUUID + 29713130-1602-4D27-90C9-B822A295E44E + PayloadVersion + 1 + + + ... + + +``` ## For Windows: diff --git a/docs/Contributing/API-for-contributors.md b/docs/Contributing/API-for-contributors.md index 074c3338b4c2..64caabc4cbf6 100644 --- a/docs/Contributing/API-for-contributors.md +++ b/docs/Contributing/API-for-contributors.md @@ -549,7 +549,6 @@ The MDM endpoints exist to support the related command-line interface sub-comman - [Get FileVault statistics](#get-filevault-statistics) - [Upload VPP content token](#upload-vpp-content-token) - [Disable VPP](#disable-vpp) -- [Get an over the air (OTA) enrollment profile](#get-an-over-the-air-ota-enrollment-profile) ### Generate Apple Business Manager public key (ADE) diff --git a/docs/REST API/rest-api.md b/docs/REST API/rest-api.md index 0a760c32d5fc..a42ec1865781 100644 --- a/docs/REST API/rest-api.md +++ b/docs/REST API/rest-api.md @@ -5573,6 +5573,7 @@ solely on the response status code returned by this endpoint. ``` ###### Example response body + ```xml @@ -5720,6 +5721,7 @@ Get aggregate status counts of profiles for to macOS and Windows hosts that are - [Set custom MDM setup enrollment profile](#set-custom-mdm-setup-enrollment-profile) - [Get custom MDM setup enrollment profile](#get-custom-mdm-setup-enrollment-profile) - [Delete custom MDM setup enrollment profile](#delete-custom-mdm-setup-enrollment-profile) +- [Get Over-the-Air (OTA) enrollment profile](#get-over-the-air-ota-enrollment-profile) - [Get manual enrollment profile](#get-manual-enrollment-profile) - [Upload a bootstrap package](#upload-a-bootstrap-package) - [Get metadata about a bootstrap package](#get-metadata-about-a-bootstrap-package) @@ -5827,10 +5829,83 @@ Deletes the custom MDM setup enrollment profile assigned to a team or no team. `Status: 204` +### Get Over-the-Air (OTA) enrollment profile + +`GET /api/v1/fleet/enrollment_profiles/ota` + +The returned value is a signed `.mobileconfig` OTA enrollment profile. Install this profile on macOS, iOS, or iPadOS hosts to enroll them to a specific team in Fleet and turn on MDM features. + +To enroll macOS hosts, turn on MDM features, and add [human-device mapping](#get-human-device-mapping), install the [manual enrollment profile](#get-manual-enrollment-profile) instead. + +Learn more about OTA profiles [here](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/iPhoneOTAConfiguration/OTASecurity/OTASecurity.html). + +#### Parameters + +| Name | Type | In | Description | +|-------------------|---------|-------|----------------------------------------------------------------------------------| +| enroll_secret | string | query | **Required**. The enroll secret of the team this host will be assigned to. | + +#### Example + +`GET /api/v1/fleet/enrollment_profiles/ota?enroll_secret=foobar` + +##### Default response + +`Status: 200` + +> **Note:** To confirm success, it is important for clients to match content length with the response header (this is done automatically by most clients, including the browser) rather than relying solely on the response status code returned by this endpoint. + +##### Example response headers + +```http + Content-Length: 542 + Content-Type: application/x-apple-aspen-config; charset=urf-8 + Content-Disposition: attachment;filename="fleet-mdm-enrollment-profile.mobileconfig" + X-Content-Type-Options: nosniff +``` + +###### Example response body + +```xml + + + + + PayloadContent + + URL + https://foo.example.com/api/fleet/ota_enrollment?enroll_secret=foobar + DeviceAttributes + + UDID + VERSION + PRODUCT + SERIAL + + + PayloadOrganization + Acme Inc. + PayloadDisplayName + Acme Inc. enrollment + PayloadVersion + 1 + PayloadUUID + fdb376e5-b5bb-4d8c-829e-e90865f990c9 + PayloadIdentifier + com.fleetdm.fleet.mdm.apple.ota + PayloadType + Profile Service + + +``` + + ### Get manual enrollment profile Retrieves an unsigned manual enrollment profile for macOS hosts. Install this profile on macOS hosts to turn on MDM features manually. +To add [human-device mapping](#get-human-device-mapping), add the end user's email to the enrollment profle. Learn how [here](https://fleetdm.com/guides/config-less-fleetd-agent-deployment#basic-article). + `GET /api/v1/fleet/enrollment_profiles/manual` ##### Example From c545495f607cd4819288b52307cf5ba9b66a3573 Mon Sep 17 00:00:00 2001 From: Marko Lisica <83164494+marko-lisica@users.noreply.github.com> Date: Tue, 1 Oct 2024 23:09:33 +0200 Subject: [PATCH 034/385] API design: Self-service: Install Apple App Store apps on macOS (#22102) API design for: - #19620 --- docs/Configuration/yaml-files.md | 4 +- docs/Contributing/API-for-contributors.md | 191 +++++++++++++++++++++- docs/REST API/rest-api.md | 178 ++++---------------- 3 files changed, 228 insertions(+), 145 deletions(-) diff --git a/docs/Configuration/yaml-files.md b/docs/Configuration/yaml-files.md index 7599fd259f9a..fae1c212c192 100644 --- a/docs/Configuration/yaml-files.md +++ b/docs/Configuration/yaml-files.md @@ -354,7 +354,9 @@ software: - `app_store_id` is the ID of the Apple App Store app. You can find this at the end of the app's App Store URL. For example, "Bear - Markdown Notes" URL is "https://apps.apple.com/us/app/bear-markdown-notes/id1016366447" and the `app_store_id` is `1016366447`. -> Make sure to include only the ID itself, and not the `id` prefix shown in the URL. The ID must be wrapped in quotes as shown in the example so that it is processed as a string. +> Make sure to include only the ID itself, and not the `id` prefix shown in the URL. The ID must be wrapped in quotes as shown in the example so that it is processed as a string. + +`self_service` only applies to macOS, and is ignored for other platforms. For example, if the app is supported on macOS, iOS, and iPadOS, and `self_service` is set to `true`, it will be self-service on macOS workstations but not iPhones or iPads. ##### Separate file diff --git a/docs/Contributing/API-for-contributors.md b/docs/Contributing/API-for-contributors.md index 64caabc4cbf6..27f819f72467 100644 --- a/docs/Contributing/API-for-contributors.md +++ b/docs/Contributing/API-for-contributors.md @@ -541,6 +541,10 @@ The MDM endpoints exist to support the related command-line interface sub-comman - [Renew VPP token](#renew-vpp-token) - [Delete VPP token](#delete-vpp-token) - [Batch-apply MDM custom settings](#batch-apply-mdm-custom-settings) +- [Batch-apply packages](#batch-apply-packages) +- [Batch-apply App Store apps](#batch-apply-app-store-apps) +- [Get token to download package](#get-token-to-download-package) +- [Download package using a token](#download-package-using-a-token) - [Initiate SSO during DEP enrollment](#initiate-sso-during-dep-enrollment) - [Complete SSO during DEP enrollment](#complete-sso-during-dep-enrollment) - [Over the air enrollment](#over-the-air-enrollment) @@ -1743,7 +1747,7 @@ If the `name` is not already associated with an existing team, this API route cr | scripts | list | body | A list of script files to add to this team so they can be executed at a later time. | | software | object | body | The team's software that will be available for install. | | software.packages | list | body | An array of objects. Each object consists of:`url`- URL to the software package (PKG, MSI, EXE or DEB),`install_script` - command that Fleet runs to install software, `pre_install_query` - condition query that determines if the install will proceed, `post_install_script` - script that runs after software install, and `self_service` boolean. | -| software.app_store_apps | list | body | An array objects. Each object consists of `app_store_id` - ID of the App Store app formatted as a string (in quotes) rather than a number. | +| software.app_store_apps | list | body | An array of objects. Each object consists of `app_store_id` - ID of the App Store app and `self_service` boolean. | | mdm.macos_settings.enable_disk_encryption | bool | body | Whether disk encryption should be enabled for hosts that belong to this team. | | force | bool | query | Force apply the spec even if there are (ignorable) validation errors. Those are unknown keys and agent options-related validations. | | dry_run | bool | query | Validate the provided JSON for unknown keys and invalid value types and return any validation errors, but do not apply the changes. | @@ -1836,6 +1840,7 @@ If the `name` is not already associated with an existing team, this API route cr "app_store_apps": [ { "app_store_id": "12464567", + "self_service": true } ] } @@ -3376,3 +3381,187 @@ Run a live script and get results back (5 minute timeout). Live scripts only run "exit_code": 0 } ``` +## Software + +### Batch-apply software + +_Available in Fleet Premium._ + +`POST /api/v1/fleet/software/batch` + +This endpoint is asynchronous, meaning it will start a background process to download and apply the software and return a `request_uuid` in the JSON response that can be used to query the status of the batch-apply (using the `GET /api/v1/fleet/software/batch/:request_uuid` endpoint defined below). + +#### Parameters + +| Name | Type | In | Description | +| --------- | ------ | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| team_name | string | query | The name of the team to add the software package to. Ommitting these parameters will add software to 'No Team'. | +| dry_run | bool | query | If `true`, will validate the provided software packages and return any validation errors, but will not apply the changes. | +| software | object | body | The team's software that will be available for install. | +| software.packages | list | body | An array of objects. Each object consists of:`url`- URL to the software package (PKG, MSI, EXE or DEB),`install_script` - command that Fleet runs to install software, `pre_install_query` - condition query that determines if the install will proceed, `post_install_script` - script that runs after software install, and `uninstall_script` - command that Fleet runs to uninstall software. | + +#### Example + +`POST /api/v1/fleet/software/batch` + +##### Default response + +`Status: 200` +```json +{ + "request_uuid": "ec23c7b6-c336-4109-b89d-6afd859659b4", +} +``` + +### Get status of software batch-apply request + +_Available in Fleet Premium._ + +`GET /api/v1/fleet/software/batch/:request_uuid` + +This endpoint allows querying the status of a batch-apply software request (`POST /api/v1/fleet/software/batch`). +Returns `"status"` field that can be one of `"processing"`, `"complete"` or `"failed"`. +If `"status"` is `"completed"` then the `"packages"` field contains the applied packages. +If `"status"` is `"processing"` then the operation is ongoing and the request should be retried. +If `"status"` is `"failed"` then the `"message"` field contains the error message. + +#### Parameters + +| Name | Type | In | Description | +| ------------ | ------ | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| request_uuid | string | query | The request_uuid returned by the `POST /api/v1/fleet/software/batch` endpoint. | +| team_name | string | query | The name of the team to add the software package to. Ommitting these parameters will add software to 'No Team'. | +| dry_run | bool | query | If `true`, will validate the provided software packages and return any validation errors, but will not apply the changes. | + +##### Default responses + +`Status: 200` +```json +{ + "status": "processing", + "message": "", + "packages": null +} +``` + +`Status: 200` +```json +{ + "status": "completed", + "message": "", + "packages": [ + { + "team_id": 1, + "title_id": 2751, + "url": "https://ftp.mozilla.org/pub/firefox/releases/129.0.2/win64/en-US/Firefox%20Setup%20129.0.2.msi" + } + ] +} +``` + +`Status: 200` +```json +{ + "status": "failed", + "message": "validation failed: software.url Couldn't edit software. URL (\"https://foobar.does.not.exist.com\") returned \"Not Found\". Please make sure that URLs are reachable from your Fleet server.", + "packages": null +} +``` + +### Batch-apply App Store apps + +_Available in Fleet Premium._ + +`POST /api/latest/fleet/software/app_store_apps/batch` + +#### Parameters + +| Name | Type | In | Description | +| --------- | ------ | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| team_name | string | query | The name of the team to add the software package to. Ommitting this parameter will add software to "No team". | +| dry_run | bool | query | If `true`, will validate the provided VPP apps and return any validation errors, but will not apply the changes. | +| app_store_apps | list | body | An array of objects. Each object contains `app_store_id` and `self_service`. | +| app_store_apps.app_store_id | string | body | ID of the App Store app. | +| app_store_apps.self_service | boolean | body | Whether the VPP app is "Self-service" or not. | + +#### Example + +`POST /api/latest/fleet/software/app_store_apps/batch` +```json +{ + "team_name": "Foobar", + "app_store_apps": { + { + "app_store_id": "597799333", + "self_service": false, + }, + { + "app_store_id": "497799835", + "self_service": true, + } + } +} +``` + +##### Default response + +`Status: 204` + +### Get token to download package + +_Available in Fleet Premium._ + +`POST /api/v1/fleet/software/titles/:software_title_id/package/token?alt=media` + +The returned token is a one-time use token that expires after 10 minutes. + +#### Parameters + +| Name | Type | In | Description | +|-------------------|---------|-------|------------------------------------------------------------------| +| software_title_id | integer | path | **Required**. The ID of the software title for software package. | +| team_id | integer | query | **Required**. The team ID containing the software package. | +| alt | integer | query | **Required**. Must be specified and set to "media". | + +#### Example + +`POST /api/v1/fleet/software/titles/123/package/token?alt=media&team_id=2` + +##### Default response + +`Status: 200` + +```json +{ + "token": "e905e33e-07fe-4f82-889c-4848ed7eecb7" +} +``` + +### Download package using a token + +_Available in Fleet Premium._ + +`GET /api/v1/fleet/software/titles/:software_title_id/package/token/:token?alt=media` + +#### Parameters + +| Name | Type | In | Description | +|-------------------|---------|------|--------------------------------------------------------------------------| +| software_title_id | integer | path | **Required**. The ID of the software title to download software package. | +| token | string | path | **Required**. The token to download the software package. | + +#### Example + +`GET /api/v1/fleet/software/titles/123/package/token/e905e33e-07fe-4f82-889c-4848ed7eecb7` + +##### Default response + +`Status: 200` + +```http +Status: 200 +Content-Type: application/octet-stream +Content-Disposition: attachment +Content-Length: +Body: +``` diff --git a/docs/REST API/rest-api.md b/docs/REST API/rest-api.md index a42ec1865781..cb23ae838055 100644 --- a/docs/REST API/rest-api.md +++ b/docs/REST API/rest-api.md @@ -4295,8 +4295,10 @@ Resends a configuration profile for the specified host. "name": "Logic Pro", "software_package": null "app_store_app": { - "app_store_id": "1091189122" + "app_store_id": "1091189122", + "icon_url": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/f4/25/1f/f4251f60-e27a-6f05-daa7-9f3a63aac929/AppIcon-0-0-85-220-0-0-4-0-0-2x-0-0-0-0-0.png/512x512bb.png" "version": "2.04", + "self_service": false, "last_install": { "command_uuid": "0aa14ae5-58fe-491a-ac9a-e4ee2b3aac40", "installed_at": "2024-05-15T15:23:57Z" @@ -6517,7 +6519,8 @@ None. ] ``` -Get Volume Purchasing Program (VPP) +### Get Volume Purchasing Program (VPP) + > **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows. @@ -8727,10 +8730,6 @@ Deletes the session specified by ID. When the user associated with the session n - [Get package install result](#get-package-install-result) - [Download package](#download-package) - [Delete package or App Store app](#delete-package-or-app-store-app) -- [Batch-apply software](#batch-apply-software) -- [Batch-apply app store apps](#batch-apply-app-store-apps) -- [Get token to download package](#get-token-to-download-package) -- [Download package using a token](#download-package-using-a-token) ### List software @@ -9101,9 +9100,10 @@ Returns information about the specified software. By default, `versions` are sor "software_package": null, "app_store_app": { "name": "Logic Pro", - "app_store_id": "1091189122", + "app_store_id": 1091189122, "latest_version": "2.04", "icon_url": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/f1/65/1e/a4844ccd-486d-455f-bb31-67336fe46b14/AppIcon-1x_U007emarketing-0-7-0-85-220-0.png/512x512bb.jpg", + "self_service": true, "status": { "installed": 3, "pending": 1, @@ -9182,6 +9182,7 @@ Returns information about the specified software version. } ``` + ### Get operating system version Retrieves information about the specified operating system (OS) version. @@ -9454,6 +9455,7 @@ Add App Store (VPP) app purchased in Apple Business Manager. | app_store_id | string | body | **Required.** The ID of App Store app. | | team_id | integer | body | **Required**. The team ID. Adds VPP software to the specified team. | | platform | string | body | The platform of the app (`darwin`, `ios`, or `ipados`). Default is `darwin`. | +| self_service | boolean | body | Self-service software is optional and can be installed by the end user. | #### Example @@ -9466,6 +9468,7 @@ Add App Store (VPP) app purchased in Apple Business Manager. "app_store_id": "497799835", "team_id": 2, "platform": "ipados" + "self_service": true } ``` @@ -9473,38 +9476,6 @@ Add App Store (VPP) app purchased in Apple Business Manager. `Status: 200` -### Download package - -> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows. - -_Available in Fleet Premium._ - -`GET /api/v1/fleet/software/titles/:software_title_id/package?alt=media` - -#### Parameters - -| Name | Type | In | Description | -| ---- | ------- | ---- | -------------------------------------------- | -| software_title_id | integer | path | **Required**. The ID of the software title to download software package.| -| team_id | integer | query | **Required**. The team ID. Downloads a software package added to the specified team. | -| alt | integer | query | **Required**. If specified and set to "media", downloads the specified software package. | - -#### Example - -`GET /api/v1/fleet/software/titles/123/package?alt=media?team_id=2` - -##### Default response - -`Status: 200` - -```http -Status: 200 -Content-Type: application/octet-stream -Content-Disposition: attachment -Content-Length: -Body: -``` - ### Install package or App Store app > **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows. @@ -9562,7 +9533,7 @@ _Available in Fleet Premium._ `GET /api/v1/fleet/software/install/:install_uuid/results` -Get the results of a software package install. +Get the results of a software package install. To get the results of an App Store app install, use the [List MDM commands](#list-mdm-commands) and [Get MDM command results](#get-mdm-command-results) API enpoints. Fleet uses an MDM command to install App Store apps. @@ -9593,141 +9564,62 @@ To get the results of an App Store app install, use the [List MDM commands](#lis } ``` -### Delete package or App Store app +### Download package > **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows. _Available in Fleet Premium._ -Deletes software that's available for install (package or App Store app). - -`DELETE /api/v1/fleet/software/titles/:software_title_id/available_for_install` +`GET /api/v1/fleet/software/titles/:software_title_id/package?alt=media` #### Parameters | Name | Type | In | Description | | ---- | ------- | ---- | -------------------------------------------- | -| software_title_id | integer | path | **Required**. The ID of the software title to delete software available for install. | -| team_id | integer | query | **Required**. The team ID. Deletes a software package added to the specified team. | - -#### Example - -`DELETE /api/v1/fleet/software/titles/24/available_for_install?team_id=2` - -##### Default response - -`Status: 204` - -### Batch-apply software - -_Available in Fleet Premium._ - -`POST /api/v1/fleet/software/batch` - -#### Parameters - -| Name | Type | In | Description | -| --------- | ------ | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| team_id | number | query | The ID of the team to add the software package to. Only one team identifier (`team_id` or `team_name`) can be included in the request; omit this parameter if using `team_name`. Omitting these parameters will add software to "No Team". | -| team_name | string | query | The name of the team to add the software package to. Only one team identifier (`team_id` or `team_name`) can be included in the request; omit this parameter if using `team_id`. Omitting these parameters will add software to "No Team". | -| dry_run | bool | query | If `true`, will validate the provided software packages and return any validation errors, but will not apply the changes. | -| software | object | body | The team's software that will be available for install. | -| software.packages | list | body | An array of objects. Each object consists of:`url`- URL to the software package (PKG, MSI, EXE or DEB),`install_script` - command that Fleet runs to install software, `pre_install_query` - condition query that determines if the install will proceed, `post_install_script` - script that runs after software install, and `uninstall_script` - command that Fleet runs to uninstall software. | -| software.app_store_apps | list | body | An array objects. Each object consists of `app_store_id` - ID of the App Store app. | - -If both `team_id` and `team_name` parameters are included, this endpoint will respond with an error. If no `team_name` or `team_id` is provided, the scripts will be applied for **all hosts**. - -#### Example - -`POST /api/v1/fleet/software/batch` - -##### Default response - -`Status: 204` - -### Batch-apply app store apps - -_Available in Fleet Premium._ - -`POST /api/v1/fleet/software/app_store_apps/batch` - -#### Parameters - -| Name | Type | In | Description | -|-----------------|---------|-------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| team_name | integer | query | **Required**. The name of the team to add the app to. | -| dry_run | bool | query | If `true`, will validate the provided apps and return any validation errors, but will not apply the changes. | -| apps_store_apps | list | body | The list of objects containing `app_store_id`: a string representation of the app's App ID, `self_service`: a bool indicating if the app's installation can be initiated by end users. | - -> Note that this endpoint replaces all apps associated with a team. - -#### Example - -`POST /api/v1/fleet/software/app_store_apps/batch` - -#### Default response - -`Status: 204` - -### Get token to download package - -_Available in Fleet Premium._ - -`POST /api/v1/fleet/software/titles/:software_title_id/package/token?alt=media` - -The returned token is a one-time use token that expires after 10 minutes. - -#### Parameters - -| Name | Type | In | Description | -|-------------------|---------|-------|------------------------------------------------------------------| -| software_title_id | integer | path | **Required**. The ID of the software title for software package. | -| team_id | integer | query | **Required**. The team ID containing the software package. | -| alt | integer | query | **Required**. Must be specified and set to "media". | +| software_title_id | integer | path | **Required**. The ID of the software title to download software package.| +| team_id | integer | query | **Required**. The team ID. Downloads a software package added to the specified team. | +| alt | integer | query | **Required**. If specified and set to "media", downloads the specified software package. | #### Example -`POST /api/v1/fleet/software/titles/123/package/token?alt=media&team_id=2` +`GET /api/v1/fleet/software/titles/123/package?alt=media?team_id=2` ##### Default response `Status: 200` -```json -{ - "token": "e905e33e-07fe-4f82-889c-4848ed7eecb7" -} +```http +Status: 200 +Content-Type: application/octet-stream +Content-Disposition: attachment +Content-Length: +Body: ``` -### Download package using a token +### Delete package or App Store app + +> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows. _Available in Fleet Premium._ -`GET /api/v1/fleet/software/titles/:software_title_id/package/token/:token?alt=media` +Deletes software that's available for install (package or App Store app). + +`DELETE /api/v1/fleet/software/titles/:software_title_id/available_for_install` #### Parameters -| Name | Type | In | Description | -|-------------------|---------|------|--------------------------------------------------------------------------| -| software_title_id | integer | path | **Required**. The ID of the software title to download software package. | -| token | string | path | **Required**. The token to download the software package. | +| Name | Type | In | Description | +| ---- | ------- | ---- | -------------------------------------------- | +| software_title_id | integer | path | **Required**. The ID of the software title to delete software available for install. | +| team_id | integer | query | **Required**. The team ID. Deletes a software package added to the specified team. | #### Example -`GET /api/v1/fleet/software/titles/123/package/token/e905e33e-07fe-4f82-889c-4848ed7eecb7` +`DELETE /api/v1/fleet/software/titles/24/available_for_install?team_id=2` ##### Default response -`Status: 200` - -```http -Status: 200 -Content-Type: application/octet-stream -Content-Disposition: attachment -Content-Length: -Body: -``` - +`Status: 204` ## Vulnerabilities From f8fff1685dd51e51e3f6133555ce121d15f52be1 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Rodriguez Date: Tue, 1 Oct 2024 18:25:17 -0300 Subject: [PATCH 035/385] Fix lint-js (#22557) I missed this change in https://github.com/fleetdm/fleet/pull/22504 --- .github/workflows/test-js.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-js.yml b/.github/workflows/test-js.yml index 2b547e1ad5c7..ab60d81939a2 100644 --- a/.github/workflows/test-js.yml +++ b/.github/workflows/test-js.yml @@ -87,14 +87,14 @@ jobs: with: egress-policy: audit + - name: Checkout Code + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - name: Set up Node.js uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 with: node-version-file: package.json - - name: Checkout Code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - - name: JS Dependency Cache id: js-cache uses: actions/cache@69d9d449aced6a2ede0bc19182fadc3a0a42d2b0 # v2 From fd928e78b9742a2af715ecd490ad4f69fd58e1be Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 1 Oct 2024 16:34:01 -0500 Subject: [PATCH 036/385] Website: add JNUC banner to device-management page (#22560) Closes: https://github.com/fleetdm/fleet/issues/22559 Changes: -Added a banner to the top of the device management page for JNUC atendees to book a meeting with the CEO. --- .../assets/images/icon-hand-wave-20x20@2x.png | Bin 0 -> 3655 bytes .../styles/pages/device-management.less | 21 ++++++++++++++++++ website/views/pages/device-management.ejs | 1 + 3 files changed, 22 insertions(+) create mode 100644 website/assets/images/icon-hand-wave-20x20@2x.png diff --git a/website/assets/images/icon-hand-wave-20x20@2x.png b/website/assets/images/icon-hand-wave-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ab60da6214029ac30c0a3aed79d6d87e70278414 GIT binary patch literal 3655 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 Z@Edh7l=U&8RX_j$002ovPDHLkV1l!>@mc@? literal 0 HcmV?d00001 diff --git a/website/assets/styles/pages/device-management.less b/website/assets/styles/pages/device-management.less index 10557dc6196e..551df9ac5800 100644 --- a/website/assets/styles/pages/device-management.less +++ b/website/assets/styles/pages/device-management.less @@ -39,6 +39,27 @@ strong { color: @core-fleet-black; } + [purpose='jnuc-banner'] { + height: 44px; + background-color: #0587FF; + p { + color: #FFF; + font-size: 14px; + font-weight: 700; + margin-bottom: 0px; + } + a { + color: #FFF; + text-decoration-line: underline; + text-underline-offset: 2px; + + } + img { + display: inline; + height: 20px; + margin-right: 10px; + } + } [purpose='page-container'] { padding-left: 64px; padding-right: 64px; diff --git a/website/views/pages/device-management.ejs b/website/views/pages/device-management.ejs index 0c077596e59a..409a23381318 100644 --- a/website/views/pages/device-management.ejs +++ b/website/views/pages/device-management.ejs @@ -1,4 +1,5 @@
+
👋

Attending JNUC? Chat with our CEO.

From 570d1847f2a01de8df32dc65f6aa66441d75c2a4 Mon Sep 17 00:00:00 2001 From: Sam Pfluger <108141731+Sampfluger88@users.noreply.github.com> Date: Tue, 1 Oct 2024 17:08:48 -0500 Subject: [PATCH 037/385] Update change a fleetie's job title (#22561) --- handbook/company/leadership.md | 6 +++++- handbook/digital-experience/README.md | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/handbook/company/leadership.md b/handbook/company/leadership.md index 1cad2e84de28..90c9d0b2a70c 100644 --- a/handbook/company/leadership.md +++ b/handbook/company/leadership.md @@ -415,7 +415,11 @@ Although it's sad to see someone go, Fleet understands that not everything is me ## Changing someone's position -From time to time, someone's job title changes. To do this, reach out to [Digital Experience](https://fleetdm.com/handbook/digital-experience). + +From time to time, someone's job title changes. Use the following steps to change someone's position: +1. Create Slack channel: Create a private "#YYYY-change-title-for-xxxxxx" Slack channel (where "xxxxxx" is the Fleetie's name and YYYY is the current year) for discussion and invite the CEO and Head of Digital Experience. +2. At-mention the Head of Digital Experience in the new channel with any context regarding the title change. Share any related documents with the Head of Digital Experience and the CEO. +3. After getting approval from the [Head of People](https://fleetdm.com/handbook/digital-experience#team), the Digital Experience team will take the necessary steps to [change the fleetie's job title](https://fleetdm.com/handbook/digital-experience#change-a-fleeties-job-title). image diff --git a/handbook/digital-experience/README.md b/handbook/digital-experience/README.md index b634a973ec1f..ab17f0cf5c89 100644 --- a/handbook/digital-experience/README.md +++ b/handbook/digital-experience/README.md @@ -91,7 +91,8 @@ When a Fleetie, consultant or advisor requests an update to their personnel deta ### Change a Fleetie's job title When Digital Experience receives notification of a Fleetie's job title changing, follow these steps to ensure accurate recording of the change across our systems. -1. Update ["🧑‍🚀 Fleeties"](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0): +1. The Head of Digital Experience will bring the proposed title change to the next Roundup meeting with the CEO for approval. If the proposed change is rejected, the Head of Digital Experience will inform the requesting manager as to why. +1. If approved, update ["🧑‍🚀 Fleeties"](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0): - Search the spreadsheet for the Fleetie in need of a job title change. - Input the new job title in the Fleetie's row in the "Job title" cell. - Navigate to the "Org chart" tab of the spreadsheet, and verify that the Fleetie's title appears correctly in the org chart. From 83c44b9cd2a9856453198af7a588f72100775a49 Mon Sep 17 00:00:00 2001 From: Gabriel Hernandez Date: Wed, 2 Oct 2024 10:01:38 +0100 Subject: [PATCH 038/385] Update schema --- server/datastore/mysql/schema.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql index fab1ea3878b2..d9d88a531f62 100644 --- a/server/datastore/mysql/schema.sql +++ b/server/datastore/mysql/schema.sql @@ -1061,9 +1061,9 @@ CREATE TABLE `migration_status_tables` ( `tstamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `id` (`id`) -) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=315 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=316 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; -INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20240927081858,1,'2020-01-01 01:01:01'),(314,20240930171917,1,'2020-01-01 01:01:01'); +INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20240909145426,1,'2020-01-01 01:01:01'),(314,20240927081858,1,'2020-01-01 01:01:01'),(315,20240930171917,1,'2020-01-01 01:01:01'); /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `mobile_device_management_solutions` ( @@ -1274,7 +1274,7 @@ CREATE TABLE `nano_users` ( /*!40101 SET character_set_client = @saved_cs_client */; SET @saved_cs_client = @@character_set_client; /*!50503 SET character_set_client = utf8mb4 */; -/*!50001 CREATE VIEW `nano_view_queue` AS SELECT +/*!50001 CREATE VIEW `nano_view_queue` AS SELECT 1 AS `id`, 1 AS `created_at`, 1 AS `active`, From 8cfa35c1c55c7e830c7375910959770324579d0f Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Wed, 2 Oct 2024 09:55:54 -0400 Subject: [PATCH 039/385] fleetdm.com/pricing: Disk encryption for Linux coming soon (#22543) - Mention [LUKS](https://access.redhat.com/solutions/100463) --- handbook/company/pricing-features-table.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handbook/company/pricing-features-table.yml b/handbook/company/pricing-features-table.yml index 3070d8d19c7a..e36234d9e617 100644 --- a/handbook/company/pricing-features-table.yml +++ b/handbook/company/pricing-features-table.yml @@ -378,7 +378,7 @@ # ║╣ ║║║╠╣ ║ ║╠╦╝║ ║╣ ║║║╚═╗╠╩╗ ║╣ ║║║║ ╠╦╝╚╦╝╠═╝ ║ ║║ ║║║║ # ╚═╝╝╚╝╚ ╚═╝╩╚═╚═╝╚═╝ ═╩╝╩╚═╝╩ ╩ ╚═╝╝╚╝╚═╝╩╚═ ╩ ╩ ╩ ╩╚═╝╝╚╝ - industryName: Enforce disk encryption - description: Encrypt system drives on macOS and Windows computers, manage escrowed encryption keys, and report on disk encryption status (FileVault, BitLocker). + description: Encrypt system drives on macOS, Windows, and Linux (coming soon) computers, manage escrowed encryption keys, and report on disk encryption status (FileVault, BitLocker, LUKS). documentationUrl: https://fleetdm.com/docs/using-fleet/mdm-disk-encryption friendlyName: Ensure hard disks are encrypted productCategories: [Device management] From 37274740438aab5fb23cb0261670998cb8d04e71 Mon Sep 17 00:00:00 2001 From: Sarah Gillespie <73313222+gillespi314@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:43:20 -0500 Subject: [PATCH 040/385] Update add software UI to move software package modal into new tabbed layout (#22553) Co-authored-by: Gabriel Hernandez --- changes/20308-file-uploader | 1 + .../components/FileDetails/FileDetails.tsx | 29 +++- frontend/components/FileDetails/_styles.scss | 28 ++++ .../FileProgressModal/FileProgressModal.tsx | 41 ++++++ .../components/FileProgressModal/_styles.scss | 7 + .../components/FileProgressModal/index.ts | 1 + .../SoftwareAddPage/SoftwareAddPage.tsx | 94 ++++++++----- .../SoftwareCustomPackage.tsx} | 115 +++++++++------ .../SoftwareCustomPackage/_styles.scss | 14 ++ .../SoftwareCustomPackage/helpers.ts | 13 ++ .../SoftwareCustomPackage/index.ts | 1 + .../FleetMaintainedAppDetailsPage.tsx | 2 +- .../SoftwarePackage/SoftwarePackage.tsx | 23 --- .../SoftwarePackage/_styles.scss | 3 - .../SoftwareAddPage/SoftwarePackage/index.ts | 1 - .../EditSoftwareModal/EditSoftwareModal.tsx | 52 ++++--- .../EditSoftwareModal/_styles.scss | 14 ++ .../SoftwarePackageCard.tsx | 2 - .../components/AddPackage/_styles.scss | 3 - .../components/AddPackage/index.ts | 1 - .../AdvancedOptionsFields.tsx | 3 +- .../PackageAdvancedOptions.tsx | 15 +- .../components/PackageForm/PackageForm.tsx | 131 +++++++++--------- frontend/router/index.tsx | 4 +- frontend/services/entities/software.ts | 79 +++++++---- frontend/services/index.ts | 67 ++++++++- frontend/utilities/file/fileUtils.ts | 5 + 27 files changed, 509 insertions(+), 240 deletions(-) create mode 100644 changes/20308-file-uploader create mode 100644 frontend/components/FileProgressModal/FileProgressModal.tsx create mode 100644 frontend/components/FileProgressModal/_styles.scss create mode 100644 frontend/components/FileProgressModal/index.ts rename frontend/pages/SoftwarePage/{components/AddPackage/AddPackage.tsx => SoftwareAddPage/SoftwareCustomPackage/SoftwareCustomPackage.tsx} (54%) create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/_styles.scss create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/helpers.ts create mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/index.ts delete mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwarePackage/SoftwarePackage.tsx delete mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwarePackage/_styles.scss delete mode 100644 frontend/pages/SoftwarePage/SoftwareAddPage/SoftwarePackage/index.ts delete mode 100644 frontend/pages/SoftwarePage/components/AddPackage/_styles.scss delete mode 100644 frontend/pages/SoftwarePage/components/AddPackage/index.ts diff --git a/changes/20308-file-uploader b/changes/20308-file-uploader new file mode 100644 index 000000000000..675c212d4b51 --- /dev/null +++ b/changes/20308-file-uploader @@ -0,0 +1 @@ +- Update UI for software uploads to include upload progress bar. diff --git a/frontend/components/FileDetails/FileDetails.tsx b/frontend/components/FileDetails/FileDetails.tsx index 78d48d5bdee8..49c5da93aed7 100644 --- a/frontend/components/FileDetails/FileDetails.tsx +++ b/frontend/components/FileDetails/FileDetails.tsx @@ -1,19 +1,19 @@ import React from "react"; +import { IFileDetails } from "utilities/file/fileUtils"; + +import Button from "components/buttons/Button"; import { ISupportedGraphicNames } from "components/FileUploader/FileUploader"; import Graphic from "components/Graphic"; -import Button from "components/buttons/Button"; import Icon from "components/Icon"; interface IFileDetailsProps { graphicNames: ISupportedGraphicNames | ISupportedGraphicNames[]; - fileDetails: { - name: string; - platform?: string; - }; + fileDetails: IFileDetails; canEdit: boolean; onFileSelect: (e: React.ChangeEvent) => void; accept?: string; + progress?: number; } const baseClass = "file-details"; @@ -24,6 +24,7 @@ const FileDetails = ({ canEdit, onFileSelect, accept, + progress, }: IFileDetailsProps) => { return (
@@ -42,7 +43,7 @@ const FileDetails = ({ )}
- {canEdit && ( + {!progress && canEdit && (
)} + {!!progress && ( +
+
+
+
+
+ {Math.round(progress * 100)}% +
+
+ )}
); }; diff --git a/frontend/components/FileDetails/_styles.scss b/frontend/components/FileDetails/_styles.scss index 497dabcc3dbb..718574db0681 100644 --- a/frontend/components/FileDetails/_styles.scss +++ b/frontend/components/FileDetails/_styles.scss @@ -2,6 +2,7 @@ display: flex; justify-content: space-between; width: 100%; + gap: $pad-medium; &__info { display: flex; @@ -34,4 +35,31 @@ cursor: pointer; } } + + &__progress-wrapper { + display: flex; + width: 184px; // using fixed width to prevent jitter when text changes: 144px + $pad-medium + 24px for text + justify-content: space-between; + align-items: center; + gap: $pad-small; // shouldn't be necessary, but just in case + } + + &__progress-bar { + display: inline-block; + height: 6px; + width: 144px; + background-color: $ui-fleet-black-10; + border-radius: 32px; + overflow: hidden; + } + + &__progress-bar--uploaded { + background-color: $core-vibrant-blue; + height: 100%; + } + + &__progress-text { + font-size: $x-small; + color: $ui-fleet-black-50; + } } diff --git a/frontend/components/FileProgressModal/FileProgressModal.tsx b/frontend/components/FileProgressModal/FileProgressModal.tsx new file mode 100644 index 000000000000..b7f74822a3fb --- /dev/null +++ b/frontend/components/FileProgressModal/FileProgressModal.tsx @@ -0,0 +1,41 @@ +import Card from "components/Card"; +import FileDetails from "components/FileDetails"; +import Modal from "components/Modal"; +import { noop } from "lodash"; +import React from "react"; + +import { ISupportedGraphicNames } from "components/FileUploader/FileUploader"; + +import { IFileDetails } from "utilities/file/fileUtils"; + +const baseClass = "file-progress-modal"; + +const FileProgressModal = ({ + graphicNames = "file-pkg", + fileDetails, + fileProgress, +}: { + graphicNames?: ISupportedGraphicNames | ISupportedGraphicNames[]; + fileDetails: IFileDetails; + fileProgress: number; +}) => ( + + + + + +); + +export default FileProgressModal; diff --git a/frontend/components/FileProgressModal/_styles.scss b/frontend/components/FileProgressModal/_styles.scss new file mode 100644 index 000000000000..08cad626545f --- /dev/null +++ b/frontend/components/FileProgressModal/_styles.scss @@ -0,0 +1,7 @@ +.file-progress-modal { + // TODO: should we update the card component with this padding option? + &__card { + padding-top: $pad-medium; + padding-bottom: $pad-medium; + } +} diff --git a/frontend/components/FileProgressModal/index.ts b/frontend/components/FileProgressModal/index.ts new file mode 100644 index 000000000000..648a271fac75 --- /dev/null +++ b/frontend/components/FileProgressModal/index.ts @@ -0,0 +1 @@ +export { default } from "./FileProgressModal"; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAddPage.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAddPage.tsx index dbb8bded4c07..d026e48a13c8 100644 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAddPage.tsx +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAddPage.tsx @@ -1,15 +1,19 @@ -import React, { useCallback } from "react"; +import React, { useCallback, useContext } from "react"; import { Tab, TabList, Tabs } from "react-tabs"; import { InjectedRouter } from "react-router"; import { Location } from "history"; import PATHS from "router/paths"; import { buildQueryStringFromParams } from "utilities/url"; +import { QueryContext } from "context/query"; +import useToggleSidePanel from "hooks/useToggleSidePanel"; +import { APP_CONTEXT_NO_TEAM_ID } from "interfaces/team"; import MainContent from "components/MainContent"; import BackLink from "components/BackLink"; import TabsWrapper from "components/TabsWrapper"; -import { APP_CONTEXT_NO_TEAM_ID } from "interfaces/team"; +import SidePanelContent from "components/SidePanelContent"; +import QuerySidePanel from "components/side_panels/QuerySidePanel"; const baseClass = "software-add-page"; @@ -59,8 +63,14 @@ const SoftwareAddPage = ({ location, router, }: ISoftwareAddPageProps) => { + const { selectedOsqueryTable, setSelectedOsqueryTable } = useContext( + QueryContext + ); + const { isSidePanelOpen, setSidePanelOpen } = useToggleSidePanel(false); + const navigateToNav = useCallback( (i: number): void => { + setSidePanelOpen(false); // Only query param to persist between tabs is team id const teamIdParam = buildQueryStringFromParams({ team_id: location.query.team_id, @@ -69,7 +79,7 @@ const SoftwareAddPage = ({ const navPath = addSoftwareSubNav[i].pathname.concat(`?${teamIdParam}`); router.replace(navPath); }, - [location, router] + [location.query.team_id, router, setSidePanelOpen] ); // Quick exit if no team_id param. This page must have a team id to function @@ -84,41 +94,59 @@ const SoftwareAddPage = ({ return null; } + const onOsqueryTableSelect = (tableName: string) => { + setSelectedOsqueryTable(tableName); + }; + const backUrl = `${PATHS.SOFTWARE_TITLES}?${buildQueryStringFromParams({ team_id: location.query.team_id, })}`; return ( - - <> - -

Add Software

- - - - {addSoftwareSubNav.map((navItem) => { - return ( - - {navItem.name} - - ); - })} - - - - {React.cloneElement(children, { - router, - currentTeamId: parseInt(location.query.team_id, 10), - })} - -
+ <> + + <> + +

Add Software

+ + + + {addSoftwareSubNav.map((navItem) => { + return ( + + {navItem.name} + + ); + })} + + + + {React.cloneElement(children, { + router, + currentTeamId: parseInt(location.query.team_id, 10), + isSidePanelOpen, + setSidePanelOpen, + })} + +
+ {isSidePanelOpen && ( + + setSidePanelOpen(false)} + /> + + )} + ); }; diff --git a/frontend/pages/SoftwarePage/components/AddPackage/AddPackage.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/SoftwareCustomPackage.tsx similarity index 54% rename from frontend/pages/SoftwarePage/components/AddPackage/AddPackage.tsx rename to frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/SoftwareCustomPackage.tsx index df20f7845af3..a56059134ada 100644 --- a/frontend/pages/SoftwarePage/components/AddPackage/AddPackage.tsx +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/SoftwareCustomPackage.tsx @@ -1,43 +1,46 @@ -import React, { useContext, useEffect, useState } from "react"; +import React, { useContext, useEffect } from "react"; import { InjectedRouter } from "react-router"; -import { getErrorReason } from "interfaces/errors"; - import PATHS from "router/paths"; -import { NotificationContext } from "context/notification"; -import softwareAPI from "services/entities/software"; -import { QueryParams, buildQueryStringFromParams } from "utilities/url"; - import { LEARN_MORE_ABOUT_BASE_LINK } from "utilities/constants"; +import { getFileDetails, IFileDetails } from "utilities/file/fileUtils"; +import { buildQueryStringFromParams, QueryParams } from "utilities/url"; +import softwareAPI, { + MAX_FILE_SIZE_BYTES, + MAX_FILE_SIZE_MB, + UPLOAD_TIMEOUT, +} from "services/entities/software"; -import CustomLink from "components/CustomLink"; +import { NotificationContext } from "context/notification"; +import { getErrorReason } from "interfaces/errors"; -import PackageForm from "../PackageForm"; -import { IPackageFormData } from "../PackageForm/PackageForm"; -import { getErrorMessage } from "../AddSoftwareModal/helpers"; +import CustomLink from "components/CustomLink"; +import FileProgressModal from "components/FileProgressModal"; +import PackageForm from "pages/SoftwarePage/components/PackageForm"; +import { IPackageFormData } from "pages/SoftwarePage/components/PackageForm/PackageForm"; -const baseClass = "add-package"; +import { getErrorMessage } from "./helpers"; -// 8 minutes + 15 seconds to account for extra roundtrip time. -export const UPLOAD_TIMEOUT = (8 * 60 + 15) * 1000; -export const MAX_FILE_SIZE_MB = 500; -export const MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024; +const baseClass = "software-custom-package"; -interface IAddPackageProps { - teamId: number; +interface ISoftwarePackageProps { + currentTeamId: number; router: InjectedRouter; - onExit: () => void; - setAddedSoftwareToken: (token: string) => void; + isSidePanelOpen: boolean; + setSidePanelOpen: (isOpen: boolean) => void; } -const AddPackage = ({ - teamId, +const SoftwareCustomPackage = ({ + currentTeamId, router, - onExit, - setAddedSoftwareToken, -}: IAddPackageProps) => { + isSidePanelOpen, + setSidePanelOpen, +}: ISoftwarePackageProps) => { const { renderFlash } = useContext(NotificationContext); - const [isUploading, setIsUploading] = useState(false); + const [uploadProgress, setUploadProgress] = React.useState(0); + const [uploadDetails, setUploadDetails] = React.useState( + null + ); useEffect(() => { let timeout: NodeJS.Timeout; @@ -50,7 +53,7 @@ const AddPackage = ({ }; // set up event listener to prevent user from leaving page while uploading - if (isUploading) { + if (uploadDetails) { addEventListener("beforeunload", beforeUnloadHandler); timeout = setTimeout(() => { removeEventListener("beforeunload", beforeUnloadHandler); @@ -64,25 +67,47 @@ const AddPackage = ({ removeEventListener("beforeunload", beforeUnloadHandler); clearTimeout(timeout); }; - }, [isUploading]); + }, [uploadDetails]); + + const onCancel = () => { + router.push( + `${PATHS.SOFTWARE_TITLES}?${buildQueryStringFromParams({ + team_id: currentTeamId, + })}` + ); + }; - const onAddPackage = async (formData: IPackageFormData) => { - setIsUploading(true); + const onSubmit = async (formData: IPackageFormData) => { + console.log("submit", formData); + if (!formData.software) { + renderFlash( + "error", + `Couldn't add. Please refresh the page and try again.` + ); + return; + } if (formData.software && formData.software.size > MAX_FILE_SIZE_BYTES) { renderFlash( "error", `Couldn't add. The maximum file size is ${MAX_FILE_SIZE_MB} MB.` ); - onExit(); - setIsUploading(false); return; } + setUploadDetails(getFileDetails(formData.software)); + // Note: This TODO is copied to onSaveSoftwareChanges in EditSoftwareModal // TODO: confirm we are deleting the second sentence (not modifying it) for non-self-service installers try { - await softwareAPI.addSoftwarePackage(formData, teamId, UPLOAD_TIMEOUT); + await softwareAPI.addSoftwarePackage({ + data: formData, + teamId: currentTeamId, + timeout: UPLOAD_TIMEOUT, + onUploadProgress: (progressEvent) => { + setUploadProgress(progressEvent.progress || 0); + }, + }); renderFlash( "success", <> @@ -93,14 +118,12 @@ const AddPackage = ({ ); - const newQueryParams: QueryParams = { team_id: teamId }; + const newQueryParams: QueryParams = { team_id: currentTeamId }; if (formData.selfService) { newQueryParams.self_service = true; } else { newQueryParams.available_for_install = true; } - // any unique string - triggers SW refetch - setAddedSoftwareToken(`${Date.now()}`); router.push( `${PATHS.SOFTWARE_TITLES}?${buildQueryStringFromParams(newQueryParams)}` ); @@ -125,20 +148,26 @@ const AddPackage = ({ renderFlash("error", getErrorMessage(e)); } } - - onExit(); - setIsUploading(false); + setUploadDetails(null); }; return (
setSidePanelOpen(true)} + className={`${baseClass}__package-form`} + onCancel={onCancel} + onSubmit={onSubmit} /> + {uploadDetails && ( + + )}
); }; -export default AddPackage; +export default SoftwareCustomPackage; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/_styles.scss b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/_styles.scss new file mode 100644 index 000000000000..6276b13e92c1 --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/_styles.scss @@ -0,0 +1,14 @@ +.software-custom-package { + // formatting the form buttons to be on the left. Tshis is done here because + // this form can be used in other places where the buttons should be on + // the right. + &__package-form { + margin-top: $pad-xxlarge; + + .form-buttons { + display: flex; + flex-direction: row; + gap: $pad-large; + } + } +} diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/helpers.ts b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/helpers.ts new file mode 100644 index 000000000000..bf6bef64a8db --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/helpers.ts @@ -0,0 +1,13 @@ +import { getErrorReason } from "interfaces/errors"; + +const UPLOAD_ERROR_MESSAGES = { + default: { + message: "Couldn't add. Please try again.", + }, +}; + +// eslint-disable-next-line import/prefer-default-export +export const getErrorMessage = (err: unknown) => { + if (typeof err === "string") return err; + return getErrorReason(err) || UPLOAD_ERROR_MESSAGES.default.message; +}; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/index.ts b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/index.ts new file mode 100644 index 000000000000..729e678fe9b1 --- /dev/null +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/index.ts @@ -0,0 +1 @@ +export { default } from "./SoftwareCustomPackage"; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetMaintainedAppDetailsPage.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetMaintainedAppDetailsPage.tsx index a97b7a1fc112..f368e2608ed1 100644 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetMaintainedAppDetailsPage.tsx +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetMaintainedAppDetailsPage.tsx @@ -184,7 +184,7 @@ const FleetMaintainedAppDetailsPage = ({ version={data.version} /> ; -} - -const SoftwarePackage = ({ - currentTeamId, - router, - location, -}: ISoftwarePackageProps) => { - return
Sofware package page
; -}; - -export default SoftwarePackage; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwarePackage/_styles.scss b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwarePackage/_styles.scss deleted file mode 100644 index e197624a521a..000000000000 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwarePackage/_styles.scss +++ /dev/null @@ -1,3 +0,0 @@ -.software-package { - -} diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwarePackage/index.ts b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwarePackage/index.ts deleted file mode 100644 index c8ed0ce44b96..000000000000 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwarePackage/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./SoftwarePackage"; diff --git a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditSoftwareModal/EditSoftwareModal.tsx b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditSoftwareModal/EditSoftwareModal.tsx index 4b7cbc09c6a9..e1c23e43003a 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditSoftwareModal/EditSoftwareModal.tsx +++ b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditSoftwareModal/EditSoftwareModal.tsx @@ -1,27 +1,26 @@ import React, { useContext, useState, useEffect } from "react"; import { InjectedRouter } from "react-router"; import classnames from "classnames"; -import deepDifference from "utilities/deep_difference"; import { getErrorReason } from "interfaces/errors"; -import PATHS from "router/paths"; import { NotificationContext } from "context/notification"; -import softwareAPI from "services/entities/software"; -import { QueryParams, buildQueryStringFromParams } from "utilities/url"; +import softwareAPI, { + MAX_FILE_SIZE_BYTES, + MAX_FILE_SIZE_MB, + UPLOAD_TIMEOUT, +} from "services/entities/software"; import { LEARN_MORE_ABOUT_BASE_LINK } from "utilities/constants"; +import deepDifference from "utilities/deep_difference"; +import { getFileDetails } from "utilities/file/fileUtils"; import CustomLink from "components/CustomLink"; +import FileProgressModal from "components/FileProgressModal"; import Modal from "components/Modal"; import PackageForm from "pages/SoftwarePage/components/PackageForm"; import { IPackageFormData } from "pages/SoftwarePage/components/PackageForm/PackageForm"; -import { - UPLOAD_TIMEOUT, - MAX_FILE_SIZE_MB, - MAX_FILE_SIZE_BYTES, -} from "pages/SoftwarePage/components/AddPackage/AddPackage"; import { getErrorMessage } from "./helpers"; import ConfirmSaveChangesModal from "../ConfirmSaveChangesModal"; @@ -33,18 +32,14 @@ interface IEditSoftwareModalProps { router: InjectedRouter; software?: any; // TODO refetchSoftwareTitle: () => void; - onExit: () => void; - setAddedSoftwareToken: (token: string) => void; } const EditSoftwareModal = ({ softwareId, teamId, - router, software, onExit, - setAddedSoftwareToken, refetchSoftwareTitle, }: IEditSoftwareModalProps) => { const { renderFlash } = useContext(NotificationContext); @@ -62,16 +57,23 @@ const EditSoftwareModal = ({ installScript: "", selfService: false, }); + const [uploadProgress, setUploadProgress] = useState(0); // Work around to not lose Edit Software modal data when Save changes modal opens // by using CSS to hide Edit Software modal when Save changes modal is open useEffect(() => { setEditSoftwareModalClasses( classnames(baseClass, { - [`${baseClass}--hidden`]: showConfirmSaveChangesModal, + [`${baseClass}--hidden`]: + showConfirmSaveChangesModal || + (!!pendingUpdates.software && isUpdatingSoftware), }) ); - }, [showConfirmSaveChangesModal]); + }, [ + showConfirmSaveChangesModal, + pendingUpdates.software, + isUpdatingSoftware, + ]); useEffect(() => { let timeout: NodeJS.Timeout; @@ -120,12 +122,15 @@ const EditSoftwareModal = ({ // Note: This TODO is copied over from onAddPackage on AddPackage.tsx // TODO: confirm we are deleting the second sentence (not modifying it) for non-self-service installers try { - await softwareAPI.editSoftwarePackage( - formData, + await softwareAPI.editSoftwarePackage({ + data: formData, softwareId, teamId, - UPLOAD_TIMEOUT - ); + timeout: UPLOAD_TIMEOUT, + onUploadProgress: (progressEvent) => { + setUploadProgress(progressEvent.progress || 0); + }, + }); renderFlash( "success", @@ -182,7 +187,6 @@ const EditSoftwareModal = ({ const onlySelfServiceUpdated = Object.keys(updates).length === 1 && "selfService" in updates; if (!onlySelfServiceUpdated) { - console.log("non-self-service updates: ", updates); // Open the confirm save changes modal setShowConfirmSaveChangesModal(true); } else { @@ -205,8 +209,8 @@ const EditSoftwareModal = ({ width="large" > )} + {!!pendingUpdates.software && isUpdatingSoftware && ( + + )} ); }; diff --git a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditSoftwareModal/_styles.scss b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditSoftwareModal/_styles.scss index 6dd125240355..5b454abb0f8c 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditSoftwareModal/_styles.scss +++ b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditSoftwareModal/_styles.scss @@ -14,4 +14,18 @@ &--hidden { display: none; } + + + // formatting the form buttons to be on the right side of the modal. + // this is done here and not with .modal-cta-wrap because this form + // can be used in other places where the buttons should be on the left. + &__package-form { + .form-buttons { + align-self: flex-end; + display: flex; + flex-direction: row-reverse; + margin-top: $pad-xlarge; + gap: $pad-medium; + } + } } diff --git a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwarePackageCard/SoftwarePackageCard.tsx b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwarePackageCard/SoftwarePackageCard.tsx index 30a0578191fd..0a8863abe646 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwarePackageCard/SoftwarePackageCard.tsx +++ b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwarePackageCard/SoftwarePackageCard.tsx @@ -15,7 +15,6 @@ import softwareAPI from "services/entities/software"; import { buildQueryStringFromParams } from "utilities/url"; import { internationalTimeFormat } from "utilities/helpers"; import { uploadedFromNow } from "utilities/date_format"; -import { noop } from "lodash"; // @ts-ignore import Dropdown from "components/forms/fields/Dropdown"; @@ -390,7 +389,6 @@ const SoftwarePackageCard = ({ software={softwarePackage} onExit={() => setShowEditSoftwareModal(false)} router={router} - setAddedSoftwareToken={noop} refetchSoftwareTitle={refetchSoftwareTitle} /> )} diff --git a/frontend/pages/SoftwarePage/components/AddPackage/_styles.scss b/frontend/pages/SoftwarePage/components/AddPackage/_styles.scss deleted file mode 100644 index 2387af120ac2..000000000000 --- a/frontend/pages/SoftwarePage/components/AddPackage/_styles.scss +++ /dev/null @@ -1,3 +0,0 @@ -.add-package { - margin-top: $pad-large; -} diff --git a/frontend/pages/SoftwarePage/components/AddPackage/index.ts b/frontend/pages/SoftwarePage/components/AddPackage/index.ts deleted file mode 100644 index 393d7d621b4d..000000000000 --- a/frontend/pages/SoftwarePage/components/AddPackage/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./AddPackage"; diff --git a/frontend/pages/SoftwarePage/components/AdvancedOptionsFields/AdvancedOptionsFields.tsx b/frontend/pages/SoftwarePage/components/AdvancedOptionsFields/AdvancedOptionsFields.tsx index 95bf94be5e22..883647e7c9fd 100644 --- a/frontend/pages/SoftwarePage/components/AdvancedOptionsFields/AdvancedOptionsFields.tsx +++ b/frontend/pages/SoftwarePage/components/AdvancedOptionsFields/AdvancedOptionsFields.tsx @@ -46,7 +46,7 @@ const AdvancedOptionsFields = ({ const classNames = classnames(baseClass, className); const renderLabelComponent = (): JSX.Element | null => { - if (showSchemaButton) { + if (!showSchemaButton) { return null; } @@ -86,6 +86,7 @@ const AdvancedOptionsFields = ({ void; onChangePreInstallQuery: (value?: string) => void; onChangeInstallScript: (value: string) => void; onChangePostInstallScript: (value?: string) => void; @@ -76,12 +78,14 @@ interface IPackageAdvancedOptionsProps { } const PackageAdvancedOptions = ({ + showSchemaButton = false, errors, selectedPackage, preInstallQuery, installScript, postInstallScript, uninstallScript, + onClickShowSchema = noop, onChangePreInstallQuery, onChangeInstallScript, onChangePostInstallScript, @@ -99,7 +103,7 @@ const PackageAdvancedOptions = ({ return ( setShowAdvancedOptions(!showAdvancedOptions)} disabled={!selectedPackage} disabledTooltipContent={ - selectedPackage - ? "Choose a file to modify advanced options." - : undefined + <> + Choose a file to modify
+ advanced options. + } /> {showAdvancedOptions && !!selectedPackage && renderAdvancedOptions()} diff --git a/frontend/pages/SoftwarePage/components/PackageForm/PackageForm.tsx b/frontend/pages/SoftwarePage/components/PackageForm/PackageForm.tsx index 4e9d7aa74de0..0123e0130ac8 100644 --- a/frontend/pages/SoftwarePage/components/PackageForm/PackageForm.tsx +++ b/frontend/pages/SoftwarePage/components/PackageForm/PackageForm.tsx @@ -1,5 +1,6 @@ // Used in AddPackageModal.tsx and EditSoftwareModal.tsx import React, { useContext, useState } from "react"; +import classnames from "classnames"; import { NotificationContext } from "context/notification"; import { getFileDetails } from "utilities/file/fileUtils"; @@ -9,7 +10,6 @@ import getDefaultUninstallScript from "utilities/software_uninstall_scripts"; import Button from "components/buttons/Button"; import Checkbox from "components/forms/fields/Checkbox"; import FileUploader from "components/FileUploader"; -import Spinner from "components/Spinner"; import TooltipWrapper from "components/TooltipWrapper"; import PackageAdvancedOptions from "../PackageAdvancedOptions"; @@ -18,15 +18,6 @@ import { generateFormValidation } from "./helpers"; export const baseClass = "package-form"; -const UploadingSoftware = () => { - return ( -
- -

Uploading software. This may take a few minutes to finish.

-
- ); -}; - export interface IPackageFormData { software: File | null; preInstallQuery?: string; @@ -46,9 +37,10 @@ export interface IFormValidation { } interface IPackageFormProps { - isUploading: boolean; + showSchemaButton?: boolean; onCancel: () => void; onSubmit: (formData: IPackageFormData) => void; + onClickShowSchema?: () => void; isEditingSoftware?: boolean; defaultSoftware?: any; // TODO defaultInstallScript?: string; @@ -56,12 +48,14 @@ interface IPackageFormProps { defaultPostInstallScript?: string; defaultUninstallScript?: string; defaultSelfService?: boolean; + className?: string; } const ACCEPTED_EXTENSIONS = ".pkg,.msi,.exe,.deb,.rpm"; const PackageForm = ({ - isUploading, + showSchemaButton = false, + onClickShowSchema, onCancel, onSubmit, isEditingSoftware = false, @@ -71,6 +65,7 @@ const PackageForm = ({ defaultPostInstallScript, defaultUninstallScript, defaultSelfService, + className, }: IPackageFormProps) => { const { renderFlash } = useContext(NotificationContext); @@ -163,65 +158,65 @@ const PackageForm = ({ const isSubmitDisabled = !formValidation.isValid; + const classNames = classnames(baseClass, className); + return ( -
- {isUploading ? ( - // Note: Sarah is replacing uploading state as subsequent 4.57 feature - ) : ( -
- + + + + + End users can install from{" "} + Fleet Desktop {">"} Self-service. + } - /> - - - End users can install from{" "} - Fleet Desktop {">"} Self-service. - - } - > - Self-service - - - -
- - -
- - )} + Self-service +
+
+ +
+ + +
+
); }; diff --git a/frontend/router/index.tsx b/frontend/router/index.tsx index 68e7994f8048..89a802646f22 100644 --- a/frontend/router/index.tsx +++ b/frontend/router/index.tsx @@ -79,7 +79,7 @@ import SoftwareOSDetailsPage from "pages/SoftwarePage/SoftwareOSDetailsPage"; import SoftwareVulnerabilityDetailsPage from "pages/SoftwarePage/SoftwareVulnerabilityDetailsPage"; import SoftwareAddPage from "pages/SoftwarePage/SoftwareAddPage"; import SoftwareFleetMaintained from "pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained"; -import SoftwarePackage from "pages/SoftwarePage/SoftwareAddPage/SoftwarePackage"; +import SoftwareCustomPackage from "pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage"; import SoftwareAppStore from "pages/SoftwarePage/SoftwareAddPage/SoftwareAppStoreVpp"; import FleetMaintainedAppDetailsPage from "pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage"; @@ -284,7 +284,7 @@ const routes = ( path="fleet-maintained" component={SoftwareFleetMaintained} /> - + { + addSoftwarePackage: ({ + data, + teamId, + timeout, + onUploadProgress, + signal, + }: { + data: IPackageFormData; + teamId?: number; + timeout?: number; + onUploadProgress?: (progressEvent: AxiosProgressEvent) => void; + signal?: AbortSignal; + }) => { const { SOFTWARE_PACKAGE_ADD } = endpoints; if (!data.software) { @@ -262,21 +276,32 @@ export default { formData.append("post_install_script", data.postInstallScript); teamId && formData.append("team_id", teamId.toString()); - return sendRequest( - "POST", - SOFTWARE_PACKAGE_ADD, - formData, - undefined, + return sendRequestWithProgress({ + method: "POST", + path: SOFTWARE_PACKAGE_ADD, + data: formData, timeout, - true - ); + skipParseError: true, + onUploadProgress, + signal, + }); }, - editSoftwarePackage: ( - data: IPackageFormData, - softwareId: number, - teamId: number, - timeout?: number - ) => { + + editSoftwarePackage: ({ + data, + softwareId, + teamId, + timeout, + onUploadProgress, + signal, + }: { + data: IPackageFormData; + softwareId: number; + teamId: number; + timeout?: number; + onUploadProgress?: (progressEvent: AxiosProgressEvent) => void; + signal?: AbortSignal; + }) => { const { EDIT_SOFTWARE_PACKAGE } = endpoints; const formData = new FormData(); @@ -288,15 +313,17 @@ export default { formData.append("post_install_script", data.postInstallScript || ""); formData.append("uninstall_script", data.uninstallScript || ""); - return sendRequest( - "PATCH", - EDIT_SOFTWARE_PACKAGE(softwareId), - formData, - undefined, + return sendRequestWithProgress({ + method: "PATCH", + path: EDIT_SOFTWARE_PACKAGE(softwareId), + data: formData, timeout, - true - ); + skipParseError: true, + onUploadProgress, + signal, + }); }, + deleteSoftwarePackage: (softwareId: number, teamId: number) => { const { SOFTWARE_AVAILABLE_FOR_INSTALL } = endpoints; const path = `${SOFTWARE_AVAILABLE_FOR_INSTALL( diff --git a/frontend/services/index.ts b/frontend/services/index.ts index acca7f824f3c..a395d29b9191 100644 --- a/frontend/services/index.ts +++ b/frontend/services/index.ts @@ -1,7 +1,72 @@ -import axios, { isAxiosError, ResponseType as AxiosResponseType } from "axios"; +import axios, { + isAxiosError, + ResponseType as AxiosResponseType, + AxiosProgressEvent, +} from "axios"; import URL_PREFIX from "router/url_prefix"; import { authToken } from "utilities/local"; +export const sendRequestWithProgress = async ({ + method, + path, + data, + responseType = "json", + timeout, + skipParseError, + returnRaw, + onDownloadProgress, + onUploadProgress, + signal, +}: { + method: "GET" | "POST" | "PATCH" | "DELETE" | "HEAD"; + path: string; + data?: unknown; + responseType?: AxiosResponseType; + timeout?: number; + skipParseError?: boolean; + returnRaw?: boolean; + onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void; + onUploadProgress?: (progressEvent: AxiosProgressEvent) => void; + signal?: AbortSignal; +}) => { + const { origin } = global.window.location; + + const url = `${origin}${URL_PREFIX}/api${path}`; + const token = authToken(); + + try { + const response = await axios({ + method, + url, + data, + responseType, + timeout, + headers: { + Authorization: `Bearer ${token}`, + }, + onDownloadProgress, + onUploadProgress, + signal, + }); + + if (returnRaw) { + return response; + } + return response.data; + } catch (error) { + if (skipParseError) { + return Promise.reject(error); + } + let reason: unknown | undefined; + if (isAxiosError(error)) { + reason = error.response || error.message || error.code; + } + return Promise.reject( + reason || `send request: parse server error: ${error}` + ); + } +}; + export const sendRequest = async ( method: "GET" | "POST" | "PATCH" | "DELETE" | "HEAD", path: string, diff --git a/frontend/utilities/file/fileUtils.ts b/frontend/utilities/file/fileUtils.ts index 6853a68a69c3..30f752334039 100644 --- a/frontend/utilities/file/fileUtils.ts +++ b/frontend/utilities/file/fileUtils.ts @@ -36,3 +36,8 @@ export const getFileDetails = (file: File) => { platform: getPlatformDisplayName(file), }; }; + +export interface IFileDetails { + name: string; + platform?: string; +} From 38fe6d9a4377c17f7cd4ce349973337723aa458a Mon Sep 17 00:00:00 2001 From: Martin Angers Date: Wed, 2 Oct 2024 16:18:37 -0400 Subject: [PATCH 041/385] Fix windows installer stuck in pending state forever (#22592) --- orbit/changes/22558-fix-stuck-windows-installers | 1 + orbit/pkg/installer/installer.go | 14 +++++++++++--- pkg/scripts/scripts.go | 8 ++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 orbit/changes/22558-fix-stuck-windows-installers diff --git a/orbit/changes/22558-fix-stuck-windows-installers b/orbit/changes/22558-fix-stuck-windows-installers new file mode 100644 index 000000000000..029ec41c32d6 --- /dev/null +++ b/orbit/changes/22558-fix-stuck-windows-installers @@ -0,0 +1 @@ +* Added a timeout to all script executions during software installs to prevent having install requests stuck in pending state forever. diff --git a/orbit/pkg/installer/installer.go b/orbit/pkg/installer/installer.go index 4059416f31ca..e0faf63c0fdf 100644 --- a/orbit/pkg/installer/installer.go +++ b/orbit/pkg/installer/installer.go @@ -15,6 +15,7 @@ import ( "github.com/fleetdm/fleet/v4/orbit/pkg/constant" "github.com/fleetdm/fleet/v4/orbit/pkg/scripts" "github.com/fleetdm/fleet/v4/pkg/file" + pkgscripts "github.com/fleetdm/fleet/v4/pkg/scripts" "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/ptr" "github.com/osquery/osquery-go" @@ -43,6 +44,9 @@ type Runner struct { OsqueryClient QueryClient OrbitClient Client + // limit execution time of the various scripts run during software installation + installerExecutionTimeout time.Duration + // osquerySocketPath is used to establish the osquery connection // if it's ever lost or disconnected osquerySocketPath string @@ -70,9 +74,10 @@ type Runner struct { func NewRunner(client Client, socketPath string, scriptsEnabled func() bool) *Runner { r := &Runner{ - OrbitClient: client, - osquerySocketPath: socketPath, - scriptsEnabled: scriptsEnabled, + OrbitClient: client, + osquerySocketPath: socketPath, + scriptsEnabled: scriptsEnabled, + installerExecutionTimeout: pkgscripts.MaxHostSoftwareInstallExecutionTime, } return r @@ -277,6 +282,9 @@ func (r *Runner) runInstallerScript(ctx context.Context, scriptContents string, return "", -1, fmt.Errorf("writing script: %w", err) } + ctx, cancel := context.WithTimeout(context.Background(), r.installerExecutionTimeout) + defer cancel() + execFn := r.execCmdFn if execFn == nil { execFn = scripts.ExecCmd diff --git a/pkg/scripts/scripts.go b/pkg/scripts/scripts.go index 7a424b109c98..75f406be3ae7 100644 --- a/pkg/scripts/scripts.go +++ b/pkg/scripts/scripts.go @@ -13,4 +13,12 @@ const ( // to account for the notification system used to deliver scripts to the // host. MaxServerWaitTime = MaxHostExecutionTime + 1*time.Minute + + // MaxHostSoftwareInstallExecutionTime is the maximum time allowed for a + // software installer script to run on a host before is terminated. That same + // timeout is used for all steps of the install process - install, + // post-install, "implicit" uninstall after a failure of the post-install + // script, and "explicit" uninstall request. This does NOT include the + // download time. + MaxHostSoftwareInstallExecutionTime = 1 * time.Hour ) From 862cd142a386bfda0a8eb15aa6eb98e22f385498 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Rodriguez Date: Wed, 2 Oct 2024 17:50:27 -0300 Subject: [PATCH 042/385] Add filter to default unintaller for pkgs to only remove .app folders (#22585) #22571 - [X] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files) for more information. - [X] Added/updated tests - [X] Manual QA for all new/changed functionality --- changes/22571-fix-pkg-uninstall | 1 + ee/server/service/software_installers.go | 12 ++--- ee/server/service/software_installers_test.go | 44 ++++++------------- pkg/file/scripts/uninstall_pkg.sh | 33 +++++++------- .../testdata/scripts/uninstall_pkg.sh.golden | 33 +++++++------- server/service/integration_enterprise_test.go | 2 +- 6 files changed, 50 insertions(+), 75 deletions(-) create mode 100644 changes/22571-fix-pkg-uninstall diff --git a/changes/22571-fix-pkg-uninstall b/changes/22571-fix-pkg-uninstall new file mode 100644 index 000000000000..4118221f5f0b --- /dev/null +++ b/changes/22571-fix-pkg-uninstall @@ -0,0 +1 @@ +* Fixed software uninstaller script for `pkg`s to only remove '.app' directories installed by the package. diff --git a/ee/server/service/software_installers.go b/ee/server/service/software_installers.go index a2dea22138bb..52865afa25ba 100644 --- a/ee/server/service/software_installers.go +++ b/ee/server/service/software_installers.go @@ -105,17 +105,13 @@ func preProcessUninstallScript(payload *fleet.UploadSoftwareInstallerPayload) { var packageID string switch payload.Extension { case "pkg": - var sb strings.Builder - _, _ = sb.WriteString("(\n") - for _, pkgID := range payload.PackageIDs { - _, _ = sb.WriteString(fmt.Sprintf(" \"%s\"\n", pkgID)) - } - _, _ = sb.WriteString(")") // no ending newline - packageID = sb.String() + // For pkgs we've decided to use the app's name instead of relying on pkgutil. + // See https://github.com/fleetdm/fleet/issues/22571. + // In future iterations we may start using the stored package_ids. + packageID = fmt.Sprintf("\"%s\"", payload.Title) default: packageID = fmt.Sprintf("\"%s\"", payload.PackageIDs[0]) } - payload.UninstallScript = packageIDRegex.ReplaceAllString(payload.UninstallScript, fmt.Sprintf("%s${suffix}", packageID)) } diff --git a/ee/server/service/software_installers_test.go b/ee/server/service/software_installers_test.go index 6abd95085eb5..54c7712d83e0 100644 --- a/ee/server/service/software_installers_test.go +++ b/ee/server/service/software_installers_test.go @@ -15,7 +15,7 @@ import ( func TestPreProcessUninstallScript(t *testing.T) { t.Parallel() - var input = ` + input := ` blah$PACKAGE_IDS pkgids=$PACKAGE_ID they are $PACKAGE_ID, right $MY_SECRET? @@ -42,6 +42,7 @@ quotes and braces for "com.foo" assert.Equal(t, expected, payload.UninstallScript) payload = fleet.UploadSoftwareInstallerPayload{ + Title: "Foo bar", Extension: "pkg", UninstallScript: input, PackageIDs: []string{"com.foo", "com.bar"}, @@ -49,32 +50,13 @@ quotes and braces for "com.foo" preProcessUninstallScript(&payload) expected = ` blah$PACKAGE_IDS -pkgids=( - "com.foo" - "com.bar" -) -they are ( - "com.foo" - "com.bar" -), right $MY_SECRET? -quotes for ( - "com.foo" - "com.bar" -) -blah( - "com.foo" - "com.bar" -)withConcat -quotes and braces for ( - "com.foo" - "com.bar" -) -( - "com.foo" - "com.bar" -)` +pkgids="Foo bar" +they are "Foo bar", right $MY_SECRET? +quotes for "Foo bar" +blah"Foo bar"withConcat +quotes and braces for "Foo bar" +"Foo bar"` assert.Equal(t, expected, payload.UninstallScript) - } func TestInstallUninstallAuth(t *testing.T) { @@ -93,7 +75,8 @@ func TestInstallUninstallAuth(t *testing.T) { }, nil } ds.GetSoftwareInstallerMetadataByTeamAndTitleIDFunc = func(ctx context.Context, teamID *uint, titleID uint, - withScriptContents bool) (*fleet.SoftwareInstaller, error) { + withScriptContents bool, + ) (*fleet.SoftwareInstaller, error) { return &fleet.SoftwareInstaller{ Name: "installer.pkg", Platform: "darwin", @@ -104,14 +87,16 @@ func TestInstallUninstallAuth(t *testing.T) { return nil, nil } ds.InsertSoftwareInstallRequestFunc = func(ctx context.Context, hostID uint, softwareInstallerID uint, selfService bool) (string, - error) { + error, + ) { return "request_id", nil } ds.GetAnyScriptContentsFunc = func(ctx context.Context, id uint) ([]byte, error) { return []byte("script"), nil } ds.NewHostScriptExecutionRequestFunc = func(ctx context.Context, request *fleet.HostScriptRequestPayload) (*fleet.HostScriptResult, - error) { + error, + ) { return &fleet.HostScriptResult{ ExecutionID: "execution_id", }, nil @@ -197,7 +182,6 @@ func TestUninstallSoftwareTitle(t *testing.T) { // Host scripts disabled host.ScriptsEnabled = ptr.Bool(false) require.ErrorContains(t, svc.UninstallSoftwareTitle(context.Background(), 1, 10), fleet.RunScriptsOrbitDisabledErrMsg) - } func checkAuthErr(t *testing.T, shouldFail bool, err error) { diff --git a/pkg/file/scripts/uninstall_pkg.sh b/pkg/file/scripts/uninstall_pkg.sh index 473cff971b49..3bddaaf57fd7 100644 --- a/pkg/file/scripts/uninstall_pkg.sh +++ b/pkg/file/scripts/uninstall_pkg.sh @@ -1,21 +1,18 @@ #!/bin/sh -# Fleet extracts and saves package IDs. -pkg_ids=$PACKAGE_ID +package_app_name="$PACKAGE_ID" -# Get all files associated with package and remove them -for pkg_id in "${pkg_ids[@]}" -do - # Get volume and location of package - volume=$(pkgutil --pkg-info "$pkg_id" | grep -i "volume" | awk '{for (i=2; i Date: Wed, 2 Oct 2024 15:52:25 -0500 Subject: [PATCH 043/385] Migration to new uninstall script (#22582) #22571 --- .../20241002104104_UpdateUninstallScript.go | 130 ++++++++++++++++++ .../migrations/tables/data/uninstall_pkg.sh | 18 +++ server/datastore/mysql/schema.sql | 4 +- 3 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 server/datastore/mysql/migrations/tables/20241002104104_UpdateUninstallScript.go create mode 100644 server/datastore/mysql/migrations/tables/data/uninstall_pkg.sh diff --git a/server/datastore/mysql/migrations/tables/20241002104104_UpdateUninstallScript.go b/server/datastore/mysql/migrations/tables/20241002104104_UpdateUninstallScript.go new file mode 100644 index 000000000000..6f2833751e37 --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20241002104104_UpdateUninstallScript.go @@ -0,0 +1,130 @@ +package tables + +import ( + "crypto/md5" //nolint:gosec + "database/sql" + _ "embed" + "encoding/hex" + "fmt" + "regexp" + "strings" + + "github.com/jmoiron/sqlx" + "github.com/jmoiron/sqlx/reflectx" +) + +//go:embed data/uninstall_pkg.sh +var newScript string + +func init() { + MigrationClient.AddMigration(Up_20241002104104, Down_20241002104104) +} + +func Up_20241002104104(tx *sql.Tx) error { + existingUninstallScript := regexp.MustCompile( + `(?sU)^#!/bin/sh\n\n` + + `# Fleet extracts and saves package IDs.\n` + + `pkg_ids=\((?P.*)\)\n\n` + + `# Get all files associated with package and remove them\n` + + `for pkg_id in "\$\{pkg_ids\[@]}"\n` + + `do\n` + + ` # Get volume and location of package\n` + + ` volume=\$\(pkgutil --pkg-info "\$pkg_id" \| grep -i "volume" \| awk '\{for \(i=2; i Date: Wed, 2 Oct 2024 16:56:52 -0400 Subject: [PATCH 044/385] SwiftDialog interface (#22321) SwiftDialog interface for use in orbit. Built for the upcoming out of box experience later this sprint --------- Co-authored-by: Jahziel Villasana-Espinoza --- orbit/pkg/swiftdialog/options.go | 313 +++++++++++++++++++++ orbit/pkg/swiftdialog/run.go | 458 +++++++++++++++++++++++++++++++ 2 files changed, 771 insertions(+) create mode 100644 orbit/pkg/swiftdialog/options.go create mode 100644 orbit/pkg/swiftdialog/run.go diff --git a/orbit/pkg/swiftdialog/options.go b/orbit/pkg/swiftdialog/options.go new file mode 100644 index 000000000000..9a7a416481ae --- /dev/null +++ b/orbit/pkg/swiftdialog/options.go @@ -0,0 +1,313 @@ +package swiftdialog + +type SwiftDialogOptions struct { + // Set the Dialog title + Title string `json:"title,omitempty"` + // Text to use as subtitle when sending a system notification + Subtitle string `json:"subtitle,omitempty"` + // Set the dialog message + Message string `json:"message,omitempty"` + // Configure a pre-set window style + Style Style `json:"style,omitempty"` + // Set the message alignment + MessageAlignment Alignment `json:"messagealignment,omitempty"` + // Set the message position + MessagePosition Position `json:"messageposition,omitempty"` + // Enable help button with content + HelpMessage string `json:"helpmessage,omitempty"` + // Set the dialog icon, accepts file path, url, or builtin + // See https://github.com/swiftDialog/swiftDialog/wiki/Customising-the-Icon + Icon string `json:"icon"` + // Set the dialog icon size + IconSize uint `json:"iconsize,omitempty"` + // Set the dialog icon transparancy + IconAlpha uint `json:"iconalpha,omitempty"` + // Set an image to display as an overlay to Icon, accepts file path or url + OverlayIcon string + // Enable banner image, accepts file path or url + BannerImage string `json:"bannerimage,omitempty"` + // Enable title within banner area + BannerTitle string `json:"bannertitle,omitempty"` + // Set text to display in banner area + BannerText string `json:"bannertext,omitempty"` + // Set the label for Button1 + Button1Text string `json:"button1text,omitempty"` + // Set the Button1 action, accepts url + Button1Action string `json:"button1action,omitempty"` + // Displays Button2 with text + Button2Text string `json:"button2text,omitempty"` + // Custom Actions For Button 2 Is Not Implemented + Button2Action string `json:"button2action,omitempty"` + // Displays info button with text + InfoButtonText string `json:"infobuttontext,omitempty"` + // Set the info button action, accepts URL + InfoButtonAction string `json:"infobuttonaction,omitempty"` + // Configure how the button area is displayed + ButtonStyle ButtonStyle `json:"buttonstyle,omitempty"` + // Select Lists and Radio Buttons + SelectItems []SelectItems `json:"selectitems,omitempty"` + // Lets you modify the title text of the dialog + TitleFont string `json:"titlefont,omitempty"` + // Set the message font of the dialog + MessageFont string `json:"messagefont,omitempty"` + // Enable a textfield with the specified label + TextField []TextField `json:"textfield,omitempty"` + // Enable a checkbox with the specified label + Checkbox []Checkbox `json:"checkbox,omitempty"` + // Change the appearance of checkboxes + CheckboxStyle CheckboxStyle `json:"checkboxstyle,omitempty"` + // Enable countdown timer (in seconds) + Timer uint `json:"timer,omitempty"` + // Enable interactive progress bar + Progress uint `json:"progress,omitempty"` + // Enable the progress text + ProgressText string `json:"progresstext,omitempty"` + // Display an image + Image []Image `json:"image,omitempty"` + // Set dialog window width + Width uint `json:"width,omitempty"` + // Set dialog window height + Height string `json:"height,omitempty"` + // Set a dialog background image, accepts file path + Background string `json:"background,omitempty"` + // Set background image transparancy + BackgroundAlpha uint `json:"bgalpha,omitempty"` + // Set background image position + BackgroundPosition FullPosition `json:"bgposition,omitempty"` + // Set background image fill type + BackgroundFill BackgroundFill `json:"bgfill,omitempty"` + // Enable background image scaling + BackgroundScale BackgroundFill `json:"bgscale,omitempty"` + // Set dialog window position + Position FullPosition `json:"position,omitempty"` + // Set dialog window position offset + PositionOffset uint `json:"positionoffset,omitempty"` + // Display a video, accepts file path or url + Video string `json:"video,omitempty"` + // Display a caption underneath a video + VideoCaption string `json:"videocaption,omitempty"` + // Enable a list item with the specified label + ListItem []ListItem `json:"listitem,omitempty"` + // Set list style + ListStyle ListStyle `json:"liststyle,omitempty"` + // Display in place of info button + InfoText string `json:"infotext,omitempty"` + // Display in info box + InfoBox string `json:"infobox,omitempty"` + // Set dialog quit key + QuitKey string `json:"quitkey,omitempty"` + // Display a web page, accepts url + WebContent string `json:"webcontent,omitempty"` + // Use the specified authentication key to allow dialog to launch + Key string `json:"key,omitempty"` + // Generate a SHA256 value + Checksum string `json:"checksum,omitempty"` + // Open a file and display the contents as it is being written, accepts file path + DisplayLog string `json:"displaylog,omitempty"` + // Change the order in which some items are displayed, comma separated list + ViewOrder string `json:"vieworder,omitempty"` + // Set the preferred window appearance + Appearance Appearance `json:"appearance,omitempty"` + // Disable Button1 + Button1Disabled bool `json:"button1disabled,omitempty"` + // Disable Button2 + Button2Disabled bool `json:"button2disabled,omitempty"` + // Displays Button2 + Button2 bool `json:"button2,omitempty"` + // Displays info button + InfoButton bool `json:"infobutton,omitempty"` + // Print version string + Version string `json:"version,omitempty"` + // Hides the icon from view + HideIcon bool `json:"hideicon,omitempty"` + // Set icon to be in the centre + CentreIcon bool `json:"centreicon,omitempty"` + // Hide countdown timer if enabled + HideTimerBar bool `json:"hidetimerbar,omitempty"` + // Enable video autoplay + Autoplay bool `json:"autoplay,omitempty"` + // Blur screen content behind dialog window + BlurScreen bool `json:"blurscreen,omitempty"` + // Send a system notification + Notification string `json:"notification,omitempty"` + // Enable dialog to be moveable + Moveable bool `json:"moveable,omitempty"` + // Enable dialog to be always positioned on top of other windows + OnTop bool `json:"ontop,omitempty"` + // Enable 25% decrease in default window size + Small bool `json:"small,omitempty"` + // Enable 25% increase in default window size + Big bool `json:"big,omitempty"` + // Enable full screen view + Fullscreen bool `json:"fullscreen,omitempty"` + // Quit when info button is selected + QuitonInfo bool `json:"quitoninfo,omitempty"` + // Enable mini mode + Mini bool `json:"mini,omitempty"` + // Enable presentation mode + Presentation bool `json:"presentation,omitempty"` + // Enables window buttons [close,min,max] + WindowButtons string `json:"windowbuttons,omitempty"` + // Enable the dialog window to be resizable + Resizable *bool `json:"resizable,omitempty"` + // Enable the dialog window to appear on all screens + ShowOnAllScreens *bool `json:"showonallscreens,omitempty"` + // Enable the dialog window to be shown at login + LoginWindow bool `json:"loginwindow,omitempty"` + // Hides the default behaviour of Return ↵ and Esc ⎋ keys + HideDefaultKeyboardAction bool `json:"hidedefaultkeyboardaction,omitempty"` +} + +type Style string + +const ( + StylePresentation Style = "presentation" + StyleMini Style = "mini" + StyleCentered Style = "centered" + StyleAlert Style = "alert" + StyleCaution Style = "caution" + StyleWarning Style = "warning" +) + +type Alignment string + +const ( + AlignmentLeft Alignment = "left" + AlignmentCenter Alignment = "center" + AlignmentRight Alignment = "right" +) + +type Position string + +const ( + PositionTop Position = "top" + PositionCenter Position = "center" + PositionBottom Position = "bottom" +) + +type ButtonStyle string + +const ( + ButtonStyleCenter ButtonStyle = "center" + ButtonStyleStack ButtonStyle = "stack" +) + +type Checkbox struct { + Label string `json:"label"` + Checked bool `json:"checked"` + Disabled bool `json:"disabled"` + Icon string `json:"icon,omitempty"` + EnableButton1 bool `json:"enableButton1,omitempty"` +} + +type Image struct { + // ImageName is a file path or url + ImageName string `json:"imagename"` + Caption string `json:"caption"` +} + +type FullPosition string + +const ( + FullPositionTopLeft FullPosition = "topleft" + FullPositionLeft FullPosition = "left" + FullPositionBottomLeft FullPosition = "bottomleft" + FullPositionTop FullPosition = "top" + FullPositionCenter FullPosition = "center" + FullPositionBottom FullPosition = "bottom" + FullPositionTopRight FullPosition = "topright" + FullPositionRight FullPosition = "right" + FullPositionBottomRight FullPosition = "bottomright" +) + +type BackgroundFill string + +const ( + BackgroundFillFill BackgroundFill = "fill" + BackgroundFillFit BackgroundFill = "fit" +) + +type ListStyle string + +const ( + ListStyleExpanded ListStyle = "expanded" + ListStyleCompact ListStyle = "compact" +) + +type Appearance string + +const ( + AppearanceDark Appearance = "dark" + AppearanceLight Appearance = "light" +) + +type ListItem struct { + Title string `json:"title"` + Icon string `json:"icon,omitempty"` + Status Status `json:"status,omitempty"` + StatusText string `json:"statustext,omitempty"` +} + +type Status string + +const ( + StatusNone Status = "" + StatusWait Status = "wait" + StatusSuccess Status = "success" + StatusFail Status = "fail" + StatusError Status = "error" + StatusPending Status = "pending" + StatusProgress Status = "progress" +) + +type TextField struct { + Title string `json:"title"` + Confirm bool `json:"confirm,omitempty"` + Editor bool `json:"editor,omitempty"` + FileSelect bool `json:"fileselect,omitempty"` + FileType string `json:"filetype,omitempty"` + Name string `json:"name,omitempty"` + Prompt string `json:"prompt,omitempty"` + Regex string `json:"regex,omitempty"` + RegexError string `json:"regexerror,omitempty"` + Required bool `json:"required,omitempty"` + Secure bool `json:"secure,omitempty"` + Value string `json:"value,omitempty"` +} + +type CheckboxStyle struct { + Style string `json:"style"` + Size CheckboxStyleSize `json:"size"` +} + +type CheckboxStyleStyle string + +const ( + CheckboxDefault CheckboxStyleStyle = "default" + CheckboxCheckbox CheckboxStyleStyle = "checkbox" + CheckboxSwitch CheckboxStyleStyle = "switch" +) + +type CheckboxStyleSize string + +const ( + CheckboxMini CheckboxStyleSize = "mini" + CheckboxSmall CheckboxStyleSize = "small" + CheckboxRegular CheckboxStyleSize = "regular" + CheckboxLarge CheckboxStyleSize = "large" +) + +type SelectItems struct { + Title string `json:"title"` + Values []string `json:"values"` + Default string `json:"default,omitempty"` + Style SelectItemsStyle `json:"style,omitempty"` + Required bool `json:"required,omitempty"` +} + +type SelectItemsStyle string + +const ( + SelectItemsStyleDropdown SelectItemsStyle = "" + SelectItemsStyleRadio SelectItemsStyle = "radio" +) diff --git a/orbit/pkg/swiftdialog/run.go b/orbit/pkg/swiftdialog/run.go new file mode 100644 index 000000000000..c8a698c50db0 --- /dev/null +++ b/orbit/pkg/swiftdialog/run.go @@ -0,0 +1,458 @@ +package swiftdialog + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io/fs" + "os" + "os/exec" + "strings" +) + +// SwiftDialog really wants the command file to be mode 666 for some reason +// https://github.com/swiftDialog/swiftDialog/wiki/Gotchas +var CommandFilePerms = fs.FileMode(0666) + +var ErrKilled = errors.New("process killed") +var ErrWindowClosed = errors.New("window closed") + +type SwiftDialog struct { + cancel context.CancelCauseFunc + cmd *exec.Cmd + commandFile *os.File + context context.Context + output *bytes.Buffer + exitCode ExitCode + exitErr error + done chan struct{} +} + +type SwiftDialogExit struct { + ExitCode ExitCode + Output map[string]any +} + +type ExitCode int + +const ( + ExitButton1 ExitCode = 0 + ExitButton2 ExitCode = 2 + ExitInfoButton ExitCode = 3 + ExitTimer ExitCode = 4 + ExitQuitCommand ExitCode = 5 + ExitQuitKey ExitCode = 10 + ExitKeyAuthFailed ExitCode = 30 + ExitImageResourceNotFound ExitCode = 201 + ExitFileNotFound ExitCode = 202 +) + +func Create(ctx context.Context, swiftDialogBin string, options *SwiftDialogOptions) (*SwiftDialog, error) { + commandFile, err := os.CreateTemp("", "swiftDialogCommand") + if err != nil { + return nil, err + } + + jsonBytes, err := json.Marshal(options) + if err != nil { + return nil, err + } + + ctx, cancel := context.WithCancelCause(ctx) + + if err := commandFile.Chmod(CommandFilePerms); err != nil { + commandFile.Close() + os.Remove(commandFile.Name()) + cancel(errors.New("could not create command file")) + return nil, err + } + + cmd := exec.CommandContext( //nolint:gosec + ctx, + swiftDialogBin, + "--jsonstring", string(jsonBytes), + "--commandfile", commandFile.Name(), + "--json", + ) + + outBuf := &bytes.Buffer{} + cmd.Stdout = outBuf + + err = cmd.Start() + if err != nil { + cancel(errors.New("could not start swiftDialog")) + return nil, err + } + + sd := &SwiftDialog{ + cancel: cancel, + cmd: cmd, + commandFile: commandFile, + context: ctx, + done: make(chan struct{}), + output: outBuf, + } + + go func() { + if err := cmd.Wait(); err != nil { + errExit := &exec.ExitError{} + if errors.As(err, &errExit) && strings.Contains(errExit.Error(), "exit status") { + sd.exitCode = ExitCode(errExit.ExitCode()) + } else { + sd.exitErr = fmt.Errorf("waiting for swiftDialog: %w", err) + } + } + close(sd.done) + cancel(ErrWindowClosed) + }() + + return sd, nil +} + +func (s *SwiftDialog) finished() { + <-s.done +} + +func (s *SwiftDialog) Kill() error { + s.cancel(ErrKilled) + s.finished() + if err := s.cleanup(); err != nil { + return fmt.Errorf("Close cleaning up after swiftDialog: %w", err) + } + + return nil +} + +func (s *SwiftDialog) cleanup() error { + s.cancel(nil) + cmdFileName := s.commandFile.Name() + err := s.commandFile.Close() + if err != nil { + return fmt.Errorf("closing swiftDialog command file: %w", err) + } + err = os.Remove(cmdFileName) + if err != nil { + return fmt.Errorf("removing swiftDialog command file: %w", err) + } + + return nil +} + +func (s *SwiftDialog) Wait() (*SwiftDialogExit, error) { + s.finished() + + parsed := map[string]any{} + if s.output.Len() != 0 { + if err := json.Unmarshal(s.output.Bytes(), &parsed); err != nil { + return nil, fmt.Errorf("parsing swiftDialog output: %w", err) + } + } + + if err := s.cleanup(); err != nil { + return nil, fmt.Errorf("Wait cleaning up after swiftDialog: %w", err) + } + + return &SwiftDialogExit{ + ExitCode: s.exitCode, + Output: parsed, + }, s.exitErr +} + +func (s *SwiftDialog) sendCommand(command, arg string) error { + if err := s.context.Err(); err != nil { + return fmt.Errorf("could not send command: %w", context.Cause(s.context)) + } + // For some reason swiftDialog needs us to open and close the file + // to detect a new command, just writing to the file doesn't cause + // a change + commandFile, err := os.OpenFile(s.commandFile.Name(), os.O_APPEND|os.O_WRONLY|os.O_CREATE, CommandFilePerms) + if err != nil { + return fmt.Errorf("opening command file for writing: %w", err) + } + + fullCommand := fmt.Sprintf("%s: %s", command, arg) + + _, err = fmt.Fprintf(commandFile, "%s\n", fullCommand) + if err != nil { + return fmt.Errorf("writing command to file: %w", err) + } + + err = commandFile.Close() + if err != nil { + return fmt.Errorf("closing command file: %w", err) + } + + return nil +} + +/////////// +// Title // +/////////// + +// Updates the dialog title +func (s *SwiftDialog) UpdateTitle(title string) error { + return s.sendCommand("title", title) +} + +// Hides the title area +func (s *SwiftDialog) HideTitle() error { + return s.sendCommand("title", "none") +} + +///////////// +// Message // +///////////// + +// Set the dialog messsage +func (s *SwiftDialog) SetMessage(text string) error { + return s.sendCommand("message", sanitize(text)) +} + +// Append to the dialog message +func (s *SwiftDialog) AppendMessage(text string) error { + return s.sendCommand("message", fmt.Sprintf("+ %s", sanitize(text))) +} + +/////////// +// Image // +/////////// + +// Displays the selected image +func (s *SwiftDialog) Image(pathOrUrl string) error { + return s.sendCommand("image", pathOrUrl) +} + +// Displays the specified text underneath any displayed image +func (s *SwiftDialog) SetImageCaption(caption string) error { + return s.sendCommand("imagecaption", caption) +} + +////////////// +// Progress // +////////////// + +// When Dialog is initiated with the Progress option, this will update the progress value +func (s *SwiftDialog) UpdateProgress(progress uint) error { + return s.sendCommand("progress", fmt.Sprintf("%d", progress)) +} + +// Increments the progress by one +func (s *SwiftDialog) IncrementProgress() error { + return s.sendCommand("progress", "increment") +} + +// Resets the progress bar to 0 +func (s *SwiftDialog) ResetProgress() error { + return s.sendCommand("progress", "reset") +} + +// Maxes out the progress bar +func (s *SwiftDialog) CompleteProgress() error { + return s.sendCommand("progress", "complete") +} + +// Hide the progress bar +func (s *SwiftDialog) HideProgress() error { + return s.sendCommand("progress", "hide") +} + +// Show the progress bar +func (s *SwiftDialog) ShowProgress() error { + return s.sendCommand("progress", "show") +} + +// Will update the label associated with the progress bar +func (s *SwiftDialog) UpdateProgressText(text string) error { + return s.sendCommand("progresstext", text) +} + +/////////// +// Lists // +/////////// + +// Create a list +func (s *SwiftDialog) SetList(items []string) error { + return s.sendCommand("list", strings.Join(items, ",")) +} + +// Clears the list and removes it from display +func (s *SwiftDialog) ClearList() error { + return s.sendCommand("list", "clear") +} + +// Add a new item to the end of the current list +func (s *SwiftDialog) AddListItem(item ListItem) error { + arg := fmt.Sprintf("add, title: %s", item.Title) + if item.Status != "" { + arg = fmt.Sprintf("%s, status: %s", arg, item.Status) + } + if item.StatusText != "" { + arg = fmt.Sprintf("%s, statustext: %s", arg, item.StatusText) + } + return s.sendCommand("listitem", arg) +} + +// Delete an item by name +func (s *SwiftDialog) DeleteListItemByTitle(title string) error { + return s.sendCommand("listitem", fmt.Sprintf("delete, title: %s", title)) +} + +// Delete an item by index number (starting at 0) +func (s *SwiftDialog) DeleteListItemByIndex(index uint) error { + return s.sendCommand("listitem", fmt.Sprintf("delete, index: %d", index)) +} + +// Update a list item by name +func (s *SwiftDialog) UpdateListItemByTitle(title, statusText string, status Status, progressPercent ...uint) error { + argStatus := string(status) + if len(progressPercent) == 1 && status == StatusProgress { + argStatus = fmt.Sprintf("progress, progress: %d", progressPercent[0]) + } + arg := fmt.Sprintf("title: %s, status: %s, statustext: %s", title, argStatus, statusText) + return s.sendCommand("listitem", arg) +} + +// Update a list item by index number (starting at 0) +func (s *SwiftDialog) UpdateListItemByIndex(index uint, statusText string, status Status, progressPercent ...uint) error { + argStatus := string(status) + if len(progressPercent) == 1 && status == StatusProgress { + argStatus = fmt.Sprintf("progress, progress: %d", progressPercent[0]) + } + arg := fmt.Sprintf("index: %d, status: %s, statustext: %s", index, argStatus, statusText) + return s.sendCommand("listitem", arg) +} + +///////////// +// Buttons // +///////////// + +// Enable or disable button 1 +func (s *SwiftDialog) EnableButton1(enable bool) error { + arg := "disable" + if enable { + arg = "enable" + } + return s.sendCommand("button1", arg) +} + +// Enable or disable button 2 +func (s *SwiftDialog) EnableButton2(enable bool) error { + arg := "disable" + if enable { + arg = "enable" + } + return s.sendCommand("button2", arg) +} + +// Changes the button 1 label +func (s *SwiftDialog) SetButton1Text(text string) error { + return s.sendCommand("button1text", text) +} + +// Changes the button 2 label +func (s *SwiftDialog) SetButton2Text(text string) error { + return s.sendCommand("button2text", text) +} + +// Changes the info button label +func (s *SwiftDialog) SetInfoButtonText(text string) error { + return s.sendCommand("infobuttontext", text) +} + +////////////// +// Info box // +////////////// + +// Update the content in the info box +func (s *SwiftDialog) SetInfoBoxText(text string) error { + return s.sendCommand("infobox", sanitize(text)) +} + +// Append to the conteit in the info box +func (s *SwiftDialog) AppendInfoBoxText(text string) error { + return s.sendCommand("infobox", fmt.Sprintf("+ %s", sanitize(text))) +} + +////////// +// Icon // +////////// + +// Changes the displayed icon +// See https://github.com/swiftDialog/swiftDialog/wiki/Customising-the-Icon +func (s *SwiftDialog) SetIconLocation(location string) error { + return s.sendCommand("icon", location) +} + +// Moves the icon being shown +func (s *SwiftDialog) SetIconAlignment(alignment Alignment) error { + return s.sendCommand("icon", string(alignment)) +} + +// Hide the icon +func (s *SwiftDialog) HideIcon() error { + return s.sendCommand("icon", "hide") +} + +// Changes the size of the displayed icon +func (s *SwiftDialog) SetIconSize(size uint) error { + return s.sendCommand("icon", fmt.Sprintf("size: %d", size)) +} + +//////////// +// Window // +//////////// + +// Changes the width of the window maintaining the current position +func (s *SwiftDialog) SetWindowWidth(width uint) error { + return s.sendCommand("width", fmt.Sprintf("%d", width)) +} + +// Changes the height of the window maintaining the current position +func (s *SwiftDialog) SetWindowHeight(width uint) error { + return s.sendCommand("height", fmt.Sprintf("%d", width)) +} + +// Changes the window position +func (s *SwiftDialog) SetWindowPosition(position FullPosition) error { + return s.sendCommand("position", string(position)) +} + +// Display content from the specified URL +func (s *SwiftDialog) SetWebContent(url string) error { + return s.sendCommand("webcontent", url) +} + +// Hide web content +func (s *SwiftDialog) HideWebContent() error { + return s.sendCommand("webcontent", "none") +} + +// Display a video from the specified path or URL +func (s *SwiftDialog) SetVideo(location string) error { + return s.sendCommand("video", location) +} + +// Enables or disables the blur window layer +func (s *SwiftDialog) BlurScreen(enable bool) error { + blur := "disable" + if enable { + blur = "enable" + } + return s.sendCommand("blurscreen", blur) +} + +// Activates the dialog window and brings it to the forground +func (s *SwiftDialog) Activate() error { + return s.sendCommand("activate", "") +} + +// Quits dialog with exit code 5 (ExitQuitCommand) +func (s *SwiftDialog) Quit() error { + return s.sendCommand("quit", "") +} + +func sanitize(text string) string { + return strings.ReplaceAll(text, "\n", "\\n") +} From 467c5c4c44f1b8efc2c932b230da621e4a35b6e6 Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Wed, 2 Oct 2024 17:07:46 -0400 Subject: [PATCH 045/385] DDM passcode cleanup (#22574) - Remove configuration profile (.mobileconfig) now that Fleet is using the DDM profile - Remove duplicate DDM profile: passcode-settings-ddm.json --- .../macos-password.mobileconfig | 55 ------------------- .../passcode-settings-ddm.json | 10 ---- it-and-security/teams/workstations-canary.yml | 3 +- it-and-security/teams/workstations.yml | 1 - 4 files changed, 1 insertion(+), 68 deletions(-) delete mode 100644 it-and-security/lib/configuration-profiles/macos-password.mobileconfig delete mode 100644 it-and-security/lib/configuration-profiles/passcode-settings-ddm.json diff --git a/it-and-security/lib/configuration-profiles/macos-password.mobileconfig b/it-and-security/lib/configuration-profiles/macos-password.mobileconfig deleted file mode 100644 index 7850555d74f5..000000000000 --- a/it-and-security/lib/configuration-profiles/macos-password.mobileconfig +++ /dev/null @@ -1,55 +0,0 @@ - - - - - PayloadContent - - - PayloadDescription - Configures Passcode settings - PayloadDisplayName - Passcode - PayloadIdentifier - com.github.erikberglund.ProfileCreator.F7CF282E-D91B-44E9-922F-A719634F9C8E.com.apple.mobiledevice.passwordpolicy.231DFC90-D5A7-41B8-9246-564056048AC5 - PayloadOrganization - - PayloadType - com.apple.mobiledevice.passwordpolicy - PayloadUUID - 231DFC90-D5A7-41B8-9246-564056048AC5 - PayloadVersion - 1 - allowSimple - - forcePIN - - maxFailedAttempts - 11 - maxGracePeriod - 1 - maxInactivity - 15 - minLength - 10 - requireAlphanumeric - - - - PayloadDescription - Configures our Macs to require passwords that are 10 character long - PayloadDisplayName - Enforce password length (10 characters) - PayloadIdentifier - com.github.erikberglund.ProfileCreator.F7CF282E-D91B-44E9-922F-A719634F9C8E - PayloadOrganization - FleetDM - PayloadScope - System - PayloadType - Configuration - PayloadUUID - F7CF282E-D91B-44E9-922F-A719634F9C8E - PayloadVersion - 1 - - diff --git a/it-and-security/lib/configuration-profiles/passcode-settings-ddm.json b/it-and-security/lib/configuration-profiles/passcode-settings-ddm.json deleted file mode 100644 index f4811f765bee..000000000000 --- a/it-and-security/lib/configuration-profiles/passcode-settings-ddm.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Type": "com.apple.configuration.passcode.settings", - "Identifier": "956e0d14-6019-479b-a6f9-a69ef77668c5", - "Payload": { - "MaximumFailedAttempts": 5, - "MaximumInactivityInMinutes ": 5, - "MinimumLength ": 12, - "MinimumComplexCharacters": 3 - } -} diff --git a/it-and-security/teams/workstations-canary.yml b/it-and-security/teams/workstations-canary.yml index 1d7ea397107c..cf1782a6241b 100644 --- a/it-and-security/teams/workstations-canary.yml +++ b/it-and-security/teams/workstations-canary.yml @@ -82,12 +82,11 @@ controls: - path: ../lib/configuration-profiles/macos-full-disk-access-for-fleetd.mobileconfig - path: ../lib/configuration-profiles/macos-limit-ad-tracking.mobileconfig - path: ../lib/configuration-profiles/macos-misc.mobileconfig - - path: ../lib/configuration-profiles/macos-password.mobileconfig - path: ../lib/configuration-profiles/macos-prevent-autologon.mobileconfig - path: ../lib/configuration-profiles/macos-secure-terminal-keyboard.mobileconfig - path: ../lib/configuration-profiles/macos-disable-update-notifications.mobileconfig - - path: ../lib/configuration-profiles/passcode-settings-ddm.json - path: ../lib/configuration-profiles/macos-ensure-show-status-bar-is-enabled.mobileconfig + - path: ../lib/configuration-profiles/macos-passcode-settings.json macos_setup: bootstrap_package: "" enable_end_user_authentication: true diff --git a/it-and-security/teams/workstations.yml b/it-and-security/teams/workstations.yml index f586b2b5d724..096fbeaa6fea 100644 --- a/it-and-security/teams/workstations.yml +++ b/it-and-security/teams/workstations.yml @@ -35,7 +35,6 @@ controls: - path: ../lib/configuration-profiles/macos-full-disk-access-for-fleetd.mobileconfig - path: ../lib/configuration-profiles/macos-limit-ad-tracking.mobileconfig - path: ../lib/configuration-profiles/macos-misc.mobileconfig - - path: ../lib/configuration-profiles/macos-password.mobileconfig - path: ../lib/configuration-profiles/macos-prevent-autologon.mobileconfig - path: ../lib/configuration-profiles/macos-secure-terminal-keyboard.mobileconfig - path: ../lib/configuration-profiles/macos-passcode-settings.json From 4de7eb9f1c5181f4e6f9a45f2d5b83852e2c679a Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Wed, 2 Oct 2024 16:20:35 -0500 Subject: [PATCH 046/385] Linux disk encryption :: Update standard-query-library.yml (#22498) Credit: @jbilling --- .../standard-query-library/standard-query-library.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/01-Using-Fleet/standard-query-library/standard-query-library.yml b/docs/01-Using-Fleet/standard-query-library/standard-query-library.yml index e9c073fa0cfc..1abff8a2fc66 100644 --- a/docs/01-Using-Fleet/standard-query-library/standard-query-library.yml +++ b/docs/01-Using-Fleet/standard-query-library/standard-query-library.yml @@ -514,12 +514,12 @@ apiVersion: v1 kind: policy spec: name: Full disk encryption enabled (Linux) - query: SELECT 1 FROM disk_encryption WHERE encrypted=1 AND name LIKE '/dev/dm-1'; + query: SELECT 1 FROM mounts m, disk_encryption d WHERE m.device_alias = d.name AND d.encrypted = 1 AND m.path = '/'; description: Checks if the root drive is encrypted. There are many ways to encrypt Linux systems. This is the default on distributions such as Ubuntu. resolution: "Ensure the image deployed to your Linux workstation includes full disk encryption." platform: linux tags: compliance, hardening, built-in, critical - contributors: GuillaumeRoss + contributors: jbilling,GuillaumeRoss --- apiVersion: v1 kind: policy From 46ade66c0fff6f7b663fcfc987f8a53280e47cd8 Mon Sep 17 00:00:00 2001 From: Tim Lee Date: Wed, 2 Oct 2024 15:43:19 -0600 Subject: [PATCH 047/385] Align battery health reporting (#22569) --- changes/19619-align-battery-health | 1 + .../Contributing/Understanding-host-vitals.md | 13 +- server/service/hosts.go | 12 -- server/service/hosts_test.go | 6 +- server/service/integration_core_test.go | 8 +- server/service/integration_desktop_test.go | 2 +- server/service/osquery_test.go | 2 +- server/service/osquery_utils/queries.go | 96 ++++++------- server/service/osquery_utils/queries_test.go | 126 +++++++++++------- 9 files changed, 138 insertions(+), 128 deletions(-) create mode 100644 changes/19619-align-battery-health diff --git a/changes/19619-align-battery-health b/changes/19619-align-battery-health new file mode 100644 index 000000000000..6bbe536bcf60 --- /dev/null +++ b/changes/19619-align-battery-health @@ -0,0 +1 @@ +- battery health definitions now defined as cycle counts greater than 1000 or max capacity falling under 80% of designed capacity for macOS and Windows \ No newline at end of file diff --git a/docs/Contributing/Understanding-host-vitals.md b/docs/Contributing/Understanding-host-vitals.md index 3aa2078574f1..c1c6d2b0c9d1 100644 --- a/docs/Contributing/Understanding-host-vitals.md +++ b/docs/Contributing/Understanding-host-vitals.md @@ -3,18 +3,9 @@ Following is a summary of the detail queries hardcoded in Fleet used to populate the device details: -## battery_macos +## battery -- Platforms: darwin - -- Query: -```sql -SELECT serial_number, cycle_count, health FROM battery; -``` - -## battery_windows - -- Platforms: windows +- Platforms: windows, darwin - Discovery query: ```sql diff --git a/server/service/hosts.go b/server/service/hosts.go index a4db5b73dc0a..eef4cb65f236 100644 --- a/server/service/hosts.go +++ b/server/service/hosts.go @@ -1152,18 +1152,6 @@ func (svc *Service) getHostDetails(ctx context.Context, host *fleet.Host, opts f nextMw.StartsAt = nextMw.StartsAt.In(gCalLoc) } - // Due to a known osquery issue with M1 Macs, we are ignoring the stored value in the db - // and replacing it at the service layer with custom values determined by the cycle count. - // See https://github.com/fleetdm/fleet/issues/6763. - // TODO: Update once the underlying osquery issue has been resolved. - for _, b := range bats { - if b.CycleCount < 1000 { - b.Health = "Normal" - } else { - b.Health = "Replacement recommended" - } - } - var policies *[]*fleet.HostPolicy if opts.IncludePolicies { hp, err := svc.ds.ListPoliciesForHost(ctx, host) diff --git a/server/service/hosts_test.go b/server/service/hosts_test.go index 26611cb8299c..59e1cc9413d7 100644 --- a/server/service/hosts_test.go +++ b/server/service/hosts_test.go @@ -65,7 +65,7 @@ func TestHostDetails(t *testing.T) { ds.ListPoliciesForHostFunc = func(ctx context.Context, host *fleet.Host) ([]*fleet.HostPolicy, error) { return nil, nil } - dsBats := []*fleet.HostBattery{{HostID: host.ID, SerialNumber: "a", CycleCount: 999, Health: "Check Battery"}, {HostID: host.ID, SerialNumber: "b", CycleCount: 1001, Health: "Good"}} + dsBats := []*fleet.HostBattery{{HostID: host.ID, SerialNumber: "a", CycleCount: 999, Health: "Normal"}, {HostID: host.ID, SerialNumber: "b", CycleCount: 1001, Health: "Service recommended"}} ds.ListHostBatteriesFunc = func(ctx context.Context, hostID uint) ([]*fleet.HostBattery, error) { return dsBats, nil } @@ -75,8 +75,6 @@ func TestHostDetails(t *testing.T) { ds.GetHostLockWipeStatusFunc = func(ctx context.Context, host *fleet.Host) (*fleet.HostLockWipeStatus, error) { return &fleet.HostLockWipeStatus{}, nil } - // Health should be replaced at the service layer with custom values determined by the cycle count. See https://github.com/fleetdm/fleet/issues/6763. - expectedBats := []*fleet.HostBattery{{HostID: host.ID, SerialNumber: "a", CycleCount: 999, Health: "Normal"}, {HostID: host.ID, SerialNumber: "b", CycleCount: 1001, Health: "Replacement recommended"}} opts := fleet.HostDetailOptions{ IncludeCVEScores: false, @@ -87,7 +85,7 @@ func TestHostDetails(t *testing.T) { assert.Equal(t, expectedLabels, hostDetail.Labels) assert.Equal(t, expectedPacks, hostDetail.Packs) require.NotNil(t, hostDetail.Batteries) - assert.Equal(t, expectedBats, *hostDetail.Batteries) + assert.Equal(t, dsBats, *hostDetail.Batteries) require.Nil(t, hostDetail.MDM.MacOSSettings) } diff --git a/server/service/integration_core_test.go b/server/service/integration_core_test.go index 8332e65eaf94..a1367e85c622 100644 --- a/server/service/integration_core_test.go +++ b/server/service/integration_core_test.go @@ -8327,8 +8327,8 @@ func (s *integrationTestSuite) TestGetHostBatteries() { require.NoError(t, err) bats := []*fleet.HostBattery{ - {HostID: host.ID, SerialNumber: "a", CycleCount: 1, Health: "Good"}, - {HostID: host.ID, SerialNumber: "b", CycleCount: 1002, Health: "Poor"}, + {HostID: host.ID, SerialNumber: "a", CycleCount: 1, Health: "Normal"}, + {HostID: host.ID, SerialNumber: "b", CycleCount: 1002, Health: "Service recommended"}, } require.NoError(t, s.ds.ReplaceHostBatteries(context.Background(), host.ID, bats)) @@ -8338,7 +8338,7 @@ func (s *integrationTestSuite) TestGetHostBatteries() { // only cycle count and health are returned require.ElementsMatch(t, []*fleet.HostBattery{ {CycleCount: 1, Health: "Normal"}, - {CycleCount: 1002, Health: "Replacement recommended"}, + {CycleCount: 1002, Health: "Service recommended"}, }, *getHostResp.Host.Batteries) // same for get host by identifier @@ -8347,7 +8347,7 @@ func (s *integrationTestSuite) TestGetHostBatteries() { // only cycle count and health are returned require.ElementsMatch(t, []*fleet.HostBattery{ {CycleCount: 1, Health: "Normal"}, - {CycleCount: 1002, Health: "Replacement recommended"}, + {CycleCount: 1002, Health: "Service recommended"}, }, *getHostResp.Host.Batteries) } diff --git a/server/service/integration_desktop_test.go b/server/service/integration_desktop_test.go index cd82faa191f7..ca3056e3a890 100644 --- a/server/service/integration_desktop_test.go +++ b/server/service/integration_desktop_test.go @@ -38,7 +38,7 @@ func (s *integrationTestSuite) TestDeviceAuthenticatedEndpoints() { require.NoError(t, s.ds.SetOrUpdateMunkiInfo(context.Background(), hosts[0].ID, "1.3.0", nil, nil)) // create a battery for hosts[0] require.NoError(t, s.ds.ReplaceHostBatteries(context.Background(), hosts[0].ID, []*fleet.HostBattery{ - {HostID: hosts[0].ID, SerialNumber: "a", CycleCount: 1, Health: "Good"}, + {HostID: hosts[0].ID, SerialNumber: "a", CycleCount: 1, Health: "Normal"}, })) // create an auth token for hosts[0] diff --git a/server/service/osquery_test.go b/server/service/osquery_test.go index ff913ac46f30..d5fc4a219a3d 100644 --- a/server/service/osquery_test.go +++ b/server/service/osquery_test.go @@ -1062,7 +1062,7 @@ func verifyDiscovery(t *testing.T, queries, discovery map[string]string) { hostDetailQueryPrefix + "orbit_info": {}, hostDetailQueryPrefix + "software_vscode_extensions": {}, hostDetailQueryPrefix + "software_macos_firefox": {}, - hostDetailQueryPrefix + "battery_windows": {}, + hostDetailQueryPrefix + "battery": {}, } for name := range queries { require.NotEmpty(t, discovery[name]) diff --git a/server/service/osquery_utils/queries.go b/server/service/osquery_utils/queries.go index 5c93b5d2d680..3858915f9ee5 100644 --- a/server/service/osquery_utils/queries.go +++ b/server/service/osquery_utils/queries.go @@ -555,17 +555,13 @@ var extraDetailQueries = map[string]DetailQuery{ DirectIngestFunc: directIngestChromeProfiles, Discovery: discoveryTable("google_chrome_profiles"), }, - "battery_macos": { - Query: `SELECT serial_number, cycle_count, health FROM battery;`, - Platforms: []string{"darwin"}, - DirectIngestFunc: directIngestBattery, - // the "battery" table doesn't need a Discovery query as it is an official - // osquery table on darwin (https://osquery.io/schema/5.3.0#battery), it is - // always present. - }, - "battery_windows": { + "battery": { + // This query is used to determine battery health of macOS and Windows hosts + // based on the cycle count, designed capacity, and max capacity of the battery. + // The `health` column is ommitted due to a known osquery issue with M1 Macs + // (https://github.com/fleetdm/fleet/issues/6763) and its absence on Windows. Query: `SELECT serial_number, cycle_count, designed_capacity, max_capacity FROM battery`, - Platforms: []string{"windows"}, + Platforms: []string{"windows", "darwin"}, DirectIngestFunc: directIngestBattery, Discovery: discoveryTable("battery"), // added to Windows in v5.12.1 (https://github.com/osquery/osquery/releases/tag/5.12.1) }, @@ -1300,71 +1296,79 @@ func directIngestChromeProfiles(ctx context.Context, logger log.Logger, host *fl return ds.ReplaceHostDeviceMapping(ctx, host.ID, mapping, fleet.DeviceMappingGoogleChromeProfiles) } +// directIngestBattery ingests battery data from a host on a Windows or macOS platform +// and calculates the battery health based on cycle count and capacity. +// Due to a known osquery issue with M1 Macs (https://github.com/fleetdm/fleet/issues/6763) +// and the ommission of the `health` column on Windows, we are not leveraging the `health` +// column in the query and instead aligning the definition of battery health between +// macOS and Windows. func directIngestBattery(ctx context.Context, logger log.Logger, host *fleet.Host, ds fleet.Datastore, rows []map[string]string) error { mapping := make([]*fleet.HostBattery, 0, len(rows)) + for _, row := range rows { - cycleCount, err := strconv.Atoi(EmptyToZero(row["cycle_count"])) + health, cycleCount, err := generateBatteryHealth(row, logger) if err != nil { - return err + level.Error(logger).Log("op", "directIngestBattery", "hostID", host.ID, "err", err) } - switch host.Platform { - case "darwin": - mapping = append(mapping, &fleet.HostBattery{ - HostID: host.ID, - SerialNumber: row["serial_number"], - CycleCount: cycleCount, - // database type is VARCHAR(40) and since there isn't a - // canonical list of strings we can get for health, we - // truncate the value just in case. - Health: fmt.Sprintf("%.40s", row["health"]), - }) - case "windows": - health, err := generateWindowsBatteryHealth(row["designed_capacity"], row["max_capacity"]) - if err != nil { - level.Error(logger).Log("op", "directIngestBattery", "hostID", host.ID, "err", err) - } - - mapping = append(mapping, &fleet.HostBattery{ - HostID: host.ID, - SerialNumber: row["serial_number"], - CycleCount: cycleCount, - Health: health, - }) - } + mapping = append(mapping, &fleet.HostBattery{ + HostID: host.ID, + SerialNumber: row["serial_number"], + CycleCount: cycleCount, + Health: health, + }) } + return ds.ReplaceHostBatteries(ctx, host.ID, mapping) } const ( - batteryStatusUnknown = "Unknown" - batteryStatusDegraded = "Check Battery" - batteryStatusGood = "Good" - batteryDegradedThreshold = 80 + batteryStatusUnknown = "Unknown" + batteryStatusDegraded = "Service recommended" + batteryStatusGood = "Normal" + batteryDegradedThreshold = 80 + batteryDegradedCycleCount = 1000 ) -func generateWindowsBatteryHealth(designedCapacity, maxCapacity string) (string, error) { +// generateBatteryHealth calculates the battery health based on the cycle count and capacity. +func generateBatteryHealth(row map[string]string, logger log.Logger) (string, int, error) { + designedCapacity := row["designed_capacity"] + maxCapacity := row["max_capacity"] + cycleCount := row["cycle_count"] + + count, err := strconv.Atoi(EmptyToZero(cycleCount)) + if err != nil { + level.Error(logger).Log("op", "generateBatteryHealth", "err", err) + // If we can't parse the cycle count, we'll assume it's 0 + // and continue with the rest of the battery health check. + count = 0 + } + + if count >= batteryDegradedCycleCount { + return batteryStatusDegraded, count, nil + } + if designedCapacity == "" || maxCapacity == "" { - return batteryStatusUnknown, fmt.Errorf("missing battery capacity values, designed: %s, max: %s", designedCapacity, maxCapacity) + return batteryStatusUnknown, count, fmt.Errorf("missing battery capacity values, designed: %s, max: %s", designedCapacity, maxCapacity) } designed, err := strconv.ParseInt(designedCapacity, 10, 64) if err != nil { - return batteryStatusUnknown, err + return batteryStatusUnknown, count, fmt.Errorf("failed to parse designed capacity: %s", designedCapacity) } max, err := strconv.ParseInt(maxCapacity, 10, 64) if err != nil { - return batteryStatusUnknown, err + return batteryStatusUnknown, count, fmt.Errorf("failed to parse max capacity: %s", maxCapacity) } health := float64(max) / float64(designed) * 100 if health < batteryDegradedThreshold { - return batteryStatusDegraded, nil + return batteryStatusDegraded, count, nil } - return batteryStatusGood, nil + return batteryStatusGood, count, nil } func directIngestWindowsUpdateHistory( diff --git a/server/service/osquery_utils/queries_test.go b/server/service/osquery_utils/queries_test.go index 3593ff20f1a5..512a30208e2d 100644 --- a/server/service/osquery_utils/queries_test.go +++ b/server/service/osquery_utils/queries_test.go @@ -279,8 +279,7 @@ func TestGetDetailQueries(t *testing.T) { "mdm_windows", "munki_info", "google_chrome_profiles", - "battery_macos", - "battery_windows", + "battery", "os_windows", "os_unix_like", "os_chrome", @@ -297,7 +296,7 @@ func TestGetDetailQueries(t *testing.T) { sortedKeysCompare(t, queriesNoConfig, baseQueries) queriesWithoutWinOSVuln := GetDetailQueries(context.Background(), config.FleetConfig{Vulnerabilities: config.VulnerabilitiesConfig{DisableWinOSVulnerabilities: true}}, nil, nil) - require.Len(t, queriesWithoutWinOSVuln, 26) + require.Len(t, queriesWithoutWinOSVuln, 25) queriesWithUsers := GetDetailQueries(context.Background(), config.FleetConfig{App: config.AppConfig{EnableScheduledQueryStats: true}}, nil, &fleet.Features{EnableHostUsers: true}) qs := append(baseQueries, "users", "users_chrome", "scheduled_query_stats") @@ -975,58 +974,87 @@ func TestDirectIngestChromeProfiles(t *testing.T) { } func TestDirectIngestBattery(t *testing.T) { - ds := new(mock.Store) - ds.ReplaceHostBatteriesFunc = func(ctx context.Context, id uint, mappings []*fleet.HostBattery) error { - require.Equal(t, mappings, []*fleet.HostBattery{ - {HostID: uint(1), SerialNumber: "a", CycleCount: 2, Health: "Good"}, - {HostID: uint(1), SerialNumber: "c", CycleCount: 3, Health: strings.Repeat("z", 40)}, - }) - return nil - } - - host := fleet.Host{ - ID: 1, - Platform: "darwin", + tests := []struct { + name string + input map[string]string + expectedBattery *fleet.HostBattery + }{ + { + name: "max_capacity >= 80%, cycleCount < 1000", + input: map[string]string{"serial_number": "a", "cycle_count": "2", "designed_capacity": "3000", "max_capacity": "2400"}, + expectedBattery: &fleet.HostBattery{HostID: uint(1), SerialNumber: "a", CycleCount: 2, Health: batteryStatusGood}, + }, + { + name: "max_capacity < 50%", + input: map[string]string{"serial_number": "b", "cycle_count": "3", "designed_capacity": "3000", "max_capacity": "2399"}, + expectedBattery: &fleet.HostBattery{HostID: uint(1), SerialNumber: "b", CycleCount: 3, Health: batteryStatusDegraded}, + }, + { + name: "missing max_capacity", + input: map[string]string{"serial_number": "c", "cycle_count": "4", "designed_capacity": "3000", "max_capacity": ""}, + expectedBattery: &fleet.HostBattery{HostID: uint(1), SerialNumber: "c", CycleCount: 4, Health: batteryStatusUnknown}, + }, + { + name: "missing designed_capacity and max_capacity", + input: map[string]string{"serial_number": "d", "cycle_count": "5", "designed_capacity": "", "max_capacity": ""}, + expectedBattery: &fleet.HostBattery{HostID: uint(1), SerialNumber: "d", CycleCount: 5, Health: batteryStatusUnknown}, + }, + { + name: "missing designed_capacity", + input: map[string]string{"serial_number": "e", "cycle_count": "6", "designed_capacity": "", "max_capacity": "2000"}, + expectedBattery: &fleet.HostBattery{HostID: uint(1), SerialNumber: "e", CycleCount: 6, Health: batteryStatusUnknown}, + }, + { + name: "invalid designed_capacity and max_capacity", + input: map[string]string{"serial_number": "f", "cycle_count": "7", "designed_capacity": "foo", "max_capacity": "bar"}, + expectedBattery: &fleet.HostBattery{HostID: uint(1), SerialNumber: "f", CycleCount: 7, Health: batteryStatusUnknown}, + }, + { + name: "cycleCount >= 1000", + input: map[string]string{"serial_number": "g", "cycle_count": "1000", "designed_capacity": "3000", "max_capacity": "2400"}, + expectedBattery: &fleet.HostBattery{HostID: uint(1), SerialNumber: "g", CycleCount: 1000, Health: batteryStatusDegraded}, + }, + { + name: "cycleCount >= 1000 with degraded health", + input: map[string]string{"serial_number": "h", "cycle_count": "1001", "designed_capacity": "3000", "max_capacity": "2399"}, + expectedBattery: &fleet.HostBattery{HostID: uint(1), SerialNumber: "h", CycleCount: 1001, Health: batteryStatusDegraded}, + }, + { + name: "missing cycle_count", + input: map[string]string{"serial_number": "i", "cycle_count": "", "designed_capacity": "3000", "max_capacity": "2400"}, + expectedBattery: &fleet.HostBattery{HostID: uint(1), SerialNumber: "i", CycleCount: 0, Health: batteryStatusGood}, + }, + { + name: "missing cycle_count with degraded health", + input: map[string]string{"serial_number": "j", "cycle_count": "", "designed_capacity": "3000", "max_capacity": "2399"}, + expectedBattery: &fleet.HostBattery{HostID: uint(1), SerialNumber: "j", CycleCount: 0, Health: batteryStatusDegraded}, + }, + { + name: "invalid cycle_count", + input: map[string]string{"serial_number": "k", "cycle_count": "foo", "designed_capacity": "3000", "max_capacity": "2400"}, + expectedBattery: &fleet.HostBattery{HostID: uint(1), SerialNumber: "k", CycleCount: 0, Health: batteryStatusGood}, + }, } - err := directIngestBattery(context.Background(), log.NewNopLogger(), &host, ds, []map[string]string{ - {"serial_number": "a", "cycle_count": "2", "health": "Good"}, - {"serial_number": "c", "cycle_count": "3", "health": strings.Repeat("z", 100)}, - }) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ds := new(mock.Store) - require.NoError(t, err) - require.True(t, ds.ReplaceHostBatteriesFuncInvoked) - - ds.ReplaceHostBatteriesFunc = func(ctx context.Context, id uint, mappings []*fleet.HostBattery) error { - require.Equal(t, mappings, []*fleet.HostBattery{ - {HostID: uint(2), SerialNumber: "a", CycleCount: 2, Health: batteryStatusGood}, - {HostID: uint(2), SerialNumber: "b", CycleCount: 3, Health: batteryStatusDegraded}, - {HostID: uint(2), SerialNumber: "c", CycleCount: 4, Health: batteryStatusUnknown}, - {HostID: uint(2), SerialNumber: "d", CycleCount: 5, Health: batteryStatusUnknown}, - {HostID: uint(2), SerialNumber: "e", CycleCount: 6, Health: batteryStatusUnknown}, - {HostID: uint(2), SerialNumber: "f", CycleCount: 7, Health: batteryStatusUnknown}, - }) - return nil - } + ds.ReplaceHostBatteriesFunc = func(ctx context.Context, id uint, mappings []*fleet.HostBattery) error { + require.Len(t, mappings, 1) + require.Equal(t, tt.expectedBattery, mappings[0]) + return nil + } - // reset the ds flag - ds.ReplaceHostBatteriesFuncInvoked = false + host := fleet.Host{ + ID: 1, + } - host = fleet.Host{ - ID: 2, - Platform: "windows", + err := directIngestBattery(context.Background(), log.NewNopLogger(), &host, ds, []map[string]string{tt.input}) + require.NoError(t, err) + require.True(t, ds.ReplaceHostBatteriesFuncInvoked) + }) } - - err = directIngestBattery(context.Background(), log.NewNopLogger(), &host, ds, []map[string]string{ - {"serial_number": "a", "cycle_count": "2", "designed_capacity": "3000", "max_capacity": "2400"}, // max_capacity >= 80% - {"serial_number": "b", "cycle_count": "3", "designed_capacity": "3000", "max_capacity": "2399"}, // max_capacity < 50% - {"serial_number": "c", "cycle_count": "4", "designed_capacity": "3000", "max_capacity": ""}, // missing max_capacity - {"serial_number": "d", "cycle_count": "5", "designed_capacity": "", "max_capacity": ""}, // missing designed_capacity and max_capacity - {"serial_number": "e", "cycle_count": "6", "designed_capacity": "", "max_capacity": "2000"}, // missing designed_capacity - {"serial_number": "f", "cycle_count": "7", "designed_capacity": "foo", "max_capacity": "bar"}, // invalid designed_capacity and max_capacity - }) - require.NoError(t, err) - require.True(t, ds.ReplaceHostBatteriesFuncInvoked) } func TestDirectIngestOSWindows(t *testing.T) { From d8b67807baff61b1de7ce27d6de6b94788086a66 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Rodriguez Date: Wed, 2 Oct 2024 19:21:06 -0300 Subject: [PATCH 048/385] Release fleetd 1.34.0 (#22602) --- .github/workflows/generate-desktop-targets.yml | 2 +- orbit/CHANGELOG.md | 4 ++++ orbit/changes/22558-fix-stuck-windows-installers | 1 - 3 files changed, 5 insertions(+), 2 deletions(-) delete mode 100644 orbit/changes/22558-fix-stuck-windows-installers diff --git a/.github/workflows/generate-desktop-targets.yml b/.github/workflows/generate-desktop-targets.yml index 93e5a30fcec3..ccd39af30fbb 100644 --- a/.github/workflows/generate-desktop-targets.yml +++ b/.github/workflows/generate-desktop-targets.yml @@ -19,7 +19,7 @@ defaults: shell: bash env: - FLEET_DESKTOP_VERSION: 1.33.0 + FLEET_DESKTOP_VERSION: 1.34.0 permissions: contents: read diff --git a/orbit/CHANGELOG.md b/orbit/CHANGELOG.md index fa9efe36efeb..89228ab17ced 100644 --- a/orbit/CHANGELOG.md +++ b/orbit/CHANGELOG.md @@ -1,3 +1,7 @@ +## Orbit 1.34.0 (Oct 02, 2024) + +* Added a timeout to all script executions during software installs to prevent having install requests stuck in pending state forever. + ## Orbit 1.33.0 (Sep 20, 2024) * Added support to run the configured uninstall script when installer's post-install script fails. diff --git a/orbit/changes/22558-fix-stuck-windows-installers b/orbit/changes/22558-fix-stuck-windows-installers deleted file mode 100644 index 029ec41c32d6..000000000000 --- a/orbit/changes/22558-fix-stuck-windows-installers +++ /dev/null @@ -1 +0,0 @@ -* Added a timeout to all script executions during software installs to prevent having install requests stuck in pending state forever. From ea62bb4c7ac6fb09e942061c3c065d481aba2643 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 2 Oct 2024 17:28:38 -0500 Subject: [PATCH 049/385] Website: Update contact form validation and slack message (#22587) Closes: #22522 Changes: - Updated the contact form to not have a personal email address filter. - Updated the message posted in Slack when users submit contact form messages. --- .../api/controllers/deliver-contact-form-message.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/website/api/controllers/deliver-contact-form-message.js b/website/api/controllers/deliver-contact-form-message.js index 37e5693f5c0e..3a5116528fe4 100644 --- a/website/api/controllers/deliver-contact-form-message.js +++ b/website/api/controllers/deliver-contact-form-message.js @@ -41,10 +41,6 @@ module.exports = { exits: { - invalidEmailDomain: { - description: 'This email address is on a denylist of domains and was not delivered.', - responseType: 'badRequest' - }, success: { description: 'The message was sent successfully.' } @@ -61,13 +57,8 @@ module.exports = { ); } - let emailDomain = emailAddress.split('@')[1]; - if(_.includes(sails.config.custom.bannedEmailDomainsForWebsiteSubmissions, emailDomain.toLowerCase())){ - throw 'invalidEmailDomain'; - } - await sails.helpers.http.post(sails.config.custom.slackWebhookUrlForContactForm, { - text: `New contact form message: (Remember: we have to email back; can't just reply to this thread.)`+ + text: `New contact form message: (cc: <@U05CS07KASK>) (Remember: we have to email back; can't just reply to this thread.)`+ `Name: ${firstName + ' ' + lastName}, Email: ${emailAddress}, Message: ${message ? message : 'No message.'}` }); From 7f01e20c0aa5275ef027ec19e1fdbeb7e6c9c2bf Mon Sep 17 00:00:00 2001 From: Lucas Manuel Rodriguez Date: Wed, 2 Oct 2024 20:01:57 -0300 Subject: [PATCH 050/385] Rearrange migration released in v4.57.2 (#22605) #22571 `20241002104104_UpdateUninstallScript.go` will be released in v4.57.2, thus I'm moving the unreleased migrations in main to run after it. --- ...nLabel.go => 20241002104105_CreateFedoraBuiltinLabel.go} | 6 +++--- ...t.go => 20241002104105_CreateFedoraBuiltinLabel_test.go} | 2 +- ...dex.go => 20241002104106_AddScheduleAutomationsIndex.go} | 6 +++--- ...o => 20241002104106_AddScheduleAutomationsIndex_test.go} | 2 +- server/datastore/mysql/schema.sql | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) rename server/datastore/mysql/migrations/tables/{20240927081858_CreateFedoraBuiltinLabel.go => 20241002104105_CreateFedoraBuiltinLabel.go} (87%) rename server/datastore/mysql/migrations/tables/{20240927081858_CreateFedoraBuiltinLabel_test.go => 20241002104105_CreateFedoraBuiltinLabel_test.go} (86%) rename server/datastore/mysql/migrations/tables/{20240930171917_AddScheduleAutomationsIndex.go => 20241002104106_AddScheduleAutomationsIndex.go} (78%) rename server/datastore/mysql/migrations/tables/{20240930171917_AddScheduleAutomationsIndex_test.go => 20241002104106_AddScheduleAutomationsIndex_test.go} (98%) diff --git a/server/datastore/mysql/migrations/tables/20240927081858_CreateFedoraBuiltinLabel.go b/server/datastore/mysql/migrations/tables/20241002104105_CreateFedoraBuiltinLabel.go similarity index 87% rename from server/datastore/mysql/migrations/tables/20240927081858_CreateFedoraBuiltinLabel.go rename to server/datastore/mysql/migrations/tables/20241002104105_CreateFedoraBuiltinLabel.go index f69faa0a7b8f..64631b584212 100644 --- a/server/datastore/mysql/migrations/tables/20240927081858_CreateFedoraBuiltinLabel.go +++ b/server/datastore/mysql/migrations/tables/20241002104105_CreateFedoraBuiltinLabel.go @@ -11,10 +11,10 @@ import ( ) func init() { - MigrationClient.AddMigration(Up_20240927081858, Down_20240927081858) + MigrationClient.AddMigration(Up_20241002104105, Down_20241002104105) } -func Up_20240927081858(tx *sql.Tx) error { +func Up_20241002104105(tx *sql.Tx) error { const stmt = ` INSERT INTO labels ( name, @@ -52,6 +52,6 @@ func Up_20240927081858(tx *sql.Tx) error { return nil } -func Down_20240927081858(tx *sql.Tx) error { +func Down_20241002104105(tx *sql.Tx) error { return nil } diff --git a/server/datastore/mysql/migrations/tables/20240927081858_CreateFedoraBuiltinLabel_test.go b/server/datastore/mysql/migrations/tables/20241002104105_CreateFedoraBuiltinLabel_test.go similarity index 86% rename from server/datastore/mysql/migrations/tables/20240927081858_CreateFedoraBuiltinLabel_test.go rename to server/datastore/mysql/migrations/tables/20241002104105_CreateFedoraBuiltinLabel_test.go index 1610b0f0b149..f0f2b83c7612 100644 --- a/server/datastore/mysql/migrations/tables/20240927081858_CreateFedoraBuiltinLabel_test.go +++ b/server/datastore/mysql/migrations/tables/20241002104105_CreateFedoraBuiltinLabel_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestUp_20240927081858(t *testing.T) { +func TestUp_20241002104105(t *testing.T) { db := applyUpToPrev(t) applyNext(t, db) diff --git a/server/datastore/mysql/migrations/tables/20240930171917_AddScheduleAutomationsIndex.go b/server/datastore/mysql/migrations/tables/20241002104106_AddScheduleAutomationsIndex.go similarity index 78% rename from server/datastore/mysql/migrations/tables/20240930171917_AddScheduleAutomationsIndex.go rename to server/datastore/mysql/migrations/tables/20241002104106_AddScheduleAutomationsIndex.go index 592e8349df32..b8eff9a350ca 100644 --- a/server/datastore/mysql/migrations/tables/20240930171917_AddScheduleAutomationsIndex.go +++ b/server/datastore/mysql/migrations/tables/20241002104106_AddScheduleAutomationsIndex.go @@ -6,10 +6,10 @@ import ( ) func init() { - MigrationClient.AddMigration(Up_20240930171917, Down_20240930171917) + MigrationClient.AddMigration(Up_20241002104106, Down_20241002104106) } -func Up_20240930171917(tx *sql.Tx) error { +func Up_20241002104106(tx *sql.Tx) error { _, err := tx.Exec(` ALTER TABLE queries ADD COLUMN is_scheduled BOOLEAN GENERATED ALWAYS AS (schedule_interval > 0) STORED NOT NULL @@ -28,6 +28,6 @@ func Up_20240930171917(tx *sql.Tx) error { return nil } -func Down_20240930171917(tx *sql.Tx) error { +func Down_20241002104106(tx *sql.Tx) error { return nil } diff --git a/server/datastore/mysql/migrations/tables/20240930171917_AddScheduleAutomationsIndex_test.go b/server/datastore/mysql/migrations/tables/20241002104106_AddScheduleAutomationsIndex_test.go similarity index 98% rename from server/datastore/mysql/migrations/tables/20240930171917_AddScheduleAutomationsIndex_test.go rename to server/datastore/mysql/migrations/tables/20241002104106_AddScheduleAutomationsIndex_test.go index 27f7d814c52b..a96d68e9feb4 100644 --- a/server/datastore/mysql/migrations/tables/20240930171917_AddScheduleAutomationsIndex_test.go +++ b/server/datastore/mysql/migrations/tables/20241002104106_AddScheduleAutomationsIndex_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestUp_20240930171917(t *testing.T) { +func TestUp_20241002104106(t *testing.T) { db := applyUpToPrev(t) // diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql index 7165df8a9e10..5755e67d0f0a 100644 --- a/server/datastore/mysql/schema.sql +++ b/server/datastore/mysql/schema.sql @@ -1040,7 +1040,7 @@ CREATE TABLE `migration_status_tables` ( UNIQUE KEY `id` (`id`) ) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=316 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; -INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20240927081858,1,'2020-01-01 01:01:01'),(314,20240930171917,1,'2020-01-01 01:01:01'),(315,20241002104104,1,'2020-01-01 01:01:01'); +INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20241002104104,1,'2020-01-01 01:01:01'),(314,20241002104105,1,'2020-01-01 01:01:01'),(315,20241002104106,1,'2020-01-01 01:01:01'); /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `mobile_device_management_solutions` ( From 54f209d33890cbf3dc9b92b129a85018037d25b9 Mon Sep 17 00:00:00 2001 From: Robert Fairburn <8029478+rfairburn@users.noreply.github.com> Date: Wed, 2 Oct 2024 18:51:48 -0500 Subject: [PATCH 051/385] Replace all lb idle timeouts with 605s (#22597) --- infrastructure/dogfood/terraform/aws-tf-module/free.tf | 2 +- infrastructure/dogfood/terraform/aws-tf-module/main.tf | 2 +- infrastructure/dogfood/terraform/aws/ecs.tf | 2 +- infrastructure/dogfood/terraform/aws/percona/percona.tf | 4 ++-- terraform/example/main.tf | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/infrastructure/dogfood/terraform/aws-tf-module/free.tf b/infrastructure/dogfood/terraform/aws-tf-module/free.tf index 37eb03f37e33..079c29a5e7e7 100644 --- a/infrastructure/dogfood/terraform/aws-tf-module/free.tf +++ b/infrastructure/dogfood/terraform/aws-tf-module/free.tf @@ -88,7 +88,7 @@ module "free" { prefix = local.customer_free enabled = true } - idle_timeout = 300 + idle_timeout = 605 } } diff --git a/infrastructure/dogfood/terraform/aws-tf-module/main.tf b/infrastructure/dogfood/terraform/aws-tf-module/main.tf index 2bede111ec00..2d7f3e82bcb6 100644 --- a/infrastructure/dogfood/terraform/aws-tf-module/main.tf +++ b/infrastructure/dogfood/terraform/aws-tf-module/main.tf @@ -214,7 +214,7 @@ module "main" { prefix = local.customer enabled = true } - idle_timeout = 300 + idle_timeout = 605 # extra_target_groups = [ # { # name = module.saml_auth_proxy.name diff --git a/infrastructure/dogfood/terraform/aws/ecs.tf b/infrastructure/dogfood/terraform/aws/ecs.tf index 5503e8a40940..b506d813f1ae 100644 --- a/infrastructure/dogfood/terraform/aws/ecs.tf +++ b/infrastructure/dogfood/terraform/aws/ecs.tf @@ -16,7 +16,7 @@ resource "aws_alb" "main" { internal = false #tfsec:ignore:aws-elb-alb-not-public security_groups = [aws_security_group.lb.id, aws_security_group.backend.id] subnets = module.vpc.public_subnets - idle_timeout = 120 + idle_timeout = 605 name = "fleetdm" drop_invalid_header_fields = true } diff --git a/infrastructure/dogfood/terraform/aws/percona/percona.tf b/infrastructure/dogfood/terraform/aws/percona/percona.tf index 64d0d5e89152..c9769b90c120 100644 --- a/infrastructure/dogfood/terraform/aws/percona/percona.tf +++ b/infrastructure/dogfood/terraform/aws/percona/percona.tf @@ -27,7 +27,7 @@ resource "aws_lb" "main" { internal = false #tfsec:ignore:aws-elb-alb-not-public security_groups = [aws_security_group.lb.id, aws_security_group.backend.id] subnets = var.public_subnets - idle_timeout = 120 + idle_timeout = 605 drop_invalid_header_fields = true } @@ -134,4 +134,4 @@ EOF resource "aws_iam_role_policy_attachment" "test-attach" { role = aws_iam_role.role.name policy_arn = aws_iam_policy.policy.arn -} \ No newline at end of file +} diff --git a/terraform/example/main.tf b/terraform/example/main.tf index bf8f569a36b8..25500a59597b 100644 --- a/terraform/example/main.tf +++ b/terraform/example/main.tf @@ -108,7 +108,7 @@ module "fleet" { alb_config = { # Script execution can run for up to 300s plus overhead. # Ensure the load balancer does not 5XX before we have results. - idle_timeout = 305 + idle_timeout = 605 } } From 9869ae5ff3609de19af52e09ca4567c31154d67b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 3 Oct 2024 04:37:12 -0300 Subject: [PATCH 052/385] Update versions of fleetd components in Fleet's TUF [automated] (#22607) Automated change from [GitHub action](https://github.com/fleetdm/fleet/actions/workflows/fleetd-tuf.yml). Co-authored-by: lucasmrod --- orbit/TUF.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/orbit/TUF.md b/orbit/TUF.md index c022aa4a6527..09678dec337d 100644 --- a/orbit/TUF.md +++ b/orbit/TUF.md @@ -18,8 +18,8 @@ Following are the currently deployed versions of fleetd components on the `stabl | Component\OS | macOS | Linux | Windows | Linux (arm64) | |--------------|--------|--------|---------|---------------| -| orbit | 1.33.0 | 1.33.0 | 1.33.0 | 1.33.0 | -| desktop | 1.33.0 | 1.33.0 | 1.33.0 | 1.33.0 | +| orbit | 1.34.0 | 1.34.0 | 1.34.0 | 1.34.0 | +| desktop | 1.34.0 | 1.34.0 | 1.34.0 | 1.34.0 | | osqueryd | 5.13.1 | 5.13.1 | 5.13.1 | 5.13.1 | | nudge | - | - | - | - | | swiftDialog | - | - | - | - | From 463afc970f3904004b8db9a7718602bfdb22d492 Mon Sep 17 00:00:00 2001 From: Gabriel Hernandez Date: Thu, 3 Oct 2024 12:29:15 +0100 Subject: [PATCH 053/385] new 1 password software icon (#22568) change the 1 password svg icon to remove the jank --- .../components/icons/OnePassword.tsx | 339 ++++-------------- 1 file changed, 67 insertions(+), 272 deletions(-) diff --git a/frontend/pages/SoftwarePage/components/icons/OnePassword.tsx b/frontend/pages/SoftwarePage/components/icons/OnePassword.tsx index 5da10b15830c..55fe4c0655a6 100644 --- a/frontend/pages/SoftwarePage/components/icons/OnePassword.tsx +++ b/frontend/pages/SoftwarePage/components/icons/OnePassword.tsx @@ -4,283 +4,78 @@ import type { SVGProps } from "react"; const OnePassword = (props: SVGProps) => ( - - - - - + - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); export default OnePassword; From 3d43aeb563ae3ca569dfd7bd19475d965ae5530e Mon Sep 17 00:00:00 2001 From: RachelElysia <71795832+RachelElysia@users.noreply.github.com> Date: Thu, 3 Oct 2024 08:05:36 -0700 Subject: [PATCH 054/385] Fleet UI: Add tooltips to battery condition (#22550) --- changes/19619-win-battery | 3 ++- .../pages/hosts/details/cards/About/About.tsx | 12 +++++++++-- frontend/utilities/constants.tsx | 21 +++++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/changes/19619-win-battery b/changes/19619-win-battery index 124e58114048..4ea9aca94fb5 100644 --- a/changes/19619-win-battery +++ b/changes/19619-win-battery @@ -1 +1,2 @@ -- Windows host details now include battery status \ No newline at end of file +- Windows host details now include battery status +- UI includes information on how battery health is defined diff --git a/frontend/pages/hosts/details/cards/About/About.tsx b/frontend/pages/hosts/details/cards/About/About.tsx index f1667f890059..67a54f977705 100644 --- a/frontend/pages/hosts/details/cards/About/About.tsx +++ b/frontend/pages/hosts/details/cards/About/About.tsx @@ -13,6 +13,7 @@ import { import { DEFAULT_EMPTY_CELL_VALUE, MDM_STATUS_TOOLTIP, + BATTERY_TOOLTIP, } from "utilities/constants"; import DataSet from "components/DataSet"; @@ -173,14 +174,21 @@ const About = ({ const renderBattery = () => { if ( aboutData.batteries === null || - typeof aboutData.batteries !== "object" + typeof aboutData.batteries !== "object" || + aboutData.batteries?.[0]?.health === "Unknown" ) { return null; } return ( + {aboutData.batteries?.[0]?.health} + + } /> ); }; diff --git a/frontend/utilities/constants.tsx b/frontend/utilities/constants.tsx index 4b780aebc98c..513e6dacf840 100644 --- a/frontend/utilities/constants.tsx +++ b/frontend/utilities/constants.tsx @@ -336,6 +336,27 @@ export const MDM_STATUS_TOOLTIP: Record = { ), }; +export const BATTERY_TOOLTIP: Record = { + Normal: ( + + Current maximum capacity is at least +
+ 80% of its designed capacity and the +
+ cycle count is below 1000. +
+ ), + "Service recommended": ( + + Current maximum capacity has fallen +
+ below 80% of its designed capacity +
+ or the cycle count has reached 1000. +
+ ), +}; + export const DEFAULT_CREATE_USER_ERRORS = { email: "", name: "", From fee21ae2ebe36ef197c65956754b60fae52f1c64 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Rodriguez Date: Thu, 3 Oct 2024 14:21:23 -0300 Subject: [PATCH 055/385] Use `pkgutil` approach to be more effective at uninstalling (#22618) #22571 - [X] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files) for more information. - [X] Added/updated tests - [X] If database migrations are included, checked table schema to confirm autoupdate - For database migrations: - [X] Checked schema for all modified table for columns that will auto-update timestamps during migration. - [X] Confirmed that updating the timestamps is acceptable, and will not cause unwanted side effects. - [X] Ensured the correct collation is explicitly set for character columns (`COLLATE utf8mb4_unicode_ci`). - [X] Manual QA for all new/changed functionality --- ee/server/service/software_installers.go | 12 +++-- ee/server/service/software_installers_test.go | 44 +++++++++++++------ pkg/file/scripts/uninstall_pkg.sh | 34 +++++++------- .../testdata/scripts/uninstall_pkg.sh.golden | 34 +++++++------- .../20241002104104_UpdateUninstallScript.go | 16 +++---- .../migrations/tables/data/uninstall_pkg.sh | 34 +++++++------- server/service/integration_enterprise_test.go | 2 +- 7 files changed, 104 insertions(+), 72 deletions(-) diff --git a/ee/server/service/software_installers.go b/ee/server/service/software_installers.go index 52865afa25ba..a2dea22138bb 100644 --- a/ee/server/service/software_installers.go +++ b/ee/server/service/software_installers.go @@ -105,13 +105,17 @@ func preProcessUninstallScript(payload *fleet.UploadSoftwareInstallerPayload) { var packageID string switch payload.Extension { case "pkg": - // For pkgs we've decided to use the app's name instead of relying on pkgutil. - // See https://github.com/fleetdm/fleet/issues/22571. - // In future iterations we may start using the stored package_ids. - packageID = fmt.Sprintf("\"%s\"", payload.Title) + var sb strings.Builder + _, _ = sb.WriteString("(\n") + for _, pkgID := range payload.PackageIDs { + _, _ = sb.WriteString(fmt.Sprintf(" \"%s\"\n", pkgID)) + } + _, _ = sb.WriteString(")") // no ending newline + packageID = sb.String() default: packageID = fmt.Sprintf("\"%s\"", payload.PackageIDs[0]) } + payload.UninstallScript = packageIDRegex.ReplaceAllString(payload.UninstallScript, fmt.Sprintf("%s${suffix}", packageID)) } diff --git a/ee/server/service/software_installers_test.go b/ee/server/service/software_installers_test.go index 54c7712d83e0..6abd95085eb5 100644 --- a/ee/server/service/software_installers_test.go +++ b/ee/server/service/software_installers_test.go @@ -15,7 +15,7 @@ import ( func TestPreProcessUninstallScript(t *testing.T) { t.Parallel() - input := ` + var input = ` blah$PACKAGE_IDS pkgids=$PACKAGE_ID they are $PACKAGE_ID, right $MY_SECRET? @@ -42,7 +42,6 @@ quotes and braces for "com.foo" assert.Equal(t, expected, payload.UninstallScript) payload = fleet.UploadSoftwareInstallerPayload{ - Title: "Foo bar", Extension: "pkg", UninstallScript: input, PackageIDs: []string{"com.foo", "com.bar"}, @@ -50,13 +49,32 @@ quotes and braces for "com.foo" preProcessUninstallScript(&payload) expected = ` blah$PACKAGE_IDS -pkgids="Foo bar" -they are "Foo bar", right $MY_SECRET? -quotes for "Foo bar" -blah"Foo bar"withConcat -quotes and braces for "Foo bar" -"Foo bar"` +pkgids=( + "com.foo" + "com.bar" +) +they are ( + "com.foo" + "com.bar" +), right $MY_SECRET? +quotes for ( + "com.foo" + "com.bar" +) +blah( + "com.foo" + "com.bar" +)withConcat +quotes and braces for ( + "com.foo" + "com.bar" +) +( + "com.foo" + "com.bar" +)` assert.Equal(t, expected, payload.UninstallScript) + } func TestInstallUninstallAuth(t *testing.T) { @@ -75,8 +93,7 @@ func TestInstallUninstallAuth(t *testing.T) { }, nil } ds.GetSoftwareInstallerMetadataByTeamAndTitleIDFunc = func(ctx context.Context, teamID *uint, titleID uint, - withScriptContents bool, - ) (*fleet.SoftwareInstaller, error) { + withScriptContents bool) (*fleet.SoftwareInstaller, error) { return &fleet.SoftwareInstaller{ Name: "installer.pkg", Platform: "darwin", @@ -87,16 +104,14 @@ func TestInstallUninstallAuth(t *testing.T) { return nil, nil } ds.InsertSoftwareInstallRequestFunc = func(ctx context.Context, hostID uint, softwareInstallerID uint, selfService bool) (string, - error, - ) { + error) { return "request_id", nil } ds.GetAnyScriptContentsFunc = func(ctx context.Context, id uint) ([]byte, error) { return []byte("script"), nil } ds.NewHostScriptExecutionRequestFunc = func(ctx context.Context, request *fleet.HostScriptRequestPayload) (*fleet.HostScriptResult, - error, - ) { + error) { return &fleet.HostScriptResult{ ExecutionID: "execution_id", }, nil @@ -182,6 +197,7 @@ func TestUninstallSoftwareTitle(t *testing.T) { // Host scripts disabled host.ScriptsEnabled = ptr.Bool(false) require.ErrorContains(t, svc.UninstallSoftwareTitle(context.Background(), 1, 10), fleet.RunScriptsOrbitDisabledErrMsg) + } func checkAuthErr(t *testing.T, shouldFail bool, err error) { diff --git a/pkg/file/scripts/uninstall_pkg.sh b/pkg/file/scripts/uninstall_pkg.sh index 3bddaaf57fd7..0096d3f48da8 100644 --- a/pkg/file/scripts/uninstall_pkg.sh +++ b/pkg/file/scripts/uninstall_pkg.sh @@ -1,18 +1,22 @@ #!/bin/sh -package_app_name="$PACKAGE_ID" +# Fleet extracts and saves package IDs. +pkg_ids=$PACKAGE_ID -# Make sure PACKAGE_ID is not empty. -if [[ -z "$package_app_name" ]]; then - echo "Empty PACKAGE_ID variable." - exit 1 -fi - -# Make sure the PACKAGE_ID doesn't have "../" or is "." -if [[ "$package_app_name" == *".."* || "$package_app_name" == "." ]]; then - echo "Invalid PACKAGE_ID value." - exit 1 -fi - -echo "Removing \"/Applications/$package_app_name\"..." -rm -rf "/Applications/$package_app_name" \ No newline at end of file +# For each package id, get all .app folders associated with the package and remove them. +for pkg_id in "${pkg_ids[@]}" +do + # Get volume and location of the package. + volume=$(pkgutil --pkg-info "$pkg_id" | grep -i "volume" | awk '{if (NF>1) print $NF}') + location=$(pkgutil --pkg-info "$pkg_id" | grep -i "location" | awk '{if (NF>1) print $NF}') + # Check if this package id corresponds to a valid/installed package + if [[ ! -z "$volume" ]]; then + # Remove individual directories that end with ".app" belonging to the package. + # Only process directories that end with ".app" to prevent Fleet from removing top level directories. + pkgutil --only-dirs --files "$pkg_id" | grep "\.app$" | sed -e 's@^@'"$volume""$location"'/@' | tr '\n' '\0' | xargs -n 1 -0 rm -rf + # Remove receipts + pkgutil --forget "$pkg_id" + else + echo "WARNING: volume is empty for package ID $pkg_id" + fi +done diff --git a/pkg/file/testdata/scripts/uninstall_pkg.sh.golden b/pkg/file/testdata/scripts/uninstall_pkg.sh.golden index 3bddaaf57fd7..0096d3f48da8 100644 --- a/pkg/file/testdata/scripts/uninstall_pkg.sh.golden +++ b/pkg/file/testdata/scripts/uninstall_pkg.sh.golden @@ -1,18 +1,22 @@ #!/bin/sh -package_app_name="$PACKAGE_ID" +# Fleet extracts and saves package IDs. +pkg_ids=$PACKAGE_ID -# Make sure PACKAGE_ID is not empty. -if [[ -z "$package_app_name" ]]; then - echo "Empty PACKAGE_ID variable." - exit 1 -fi - -# Make sure the PACKAGE_ID doesn't have "../" or is "." -if [[ "$package_app_name" == *".."* || "$package_app_name" == "." ]]; then - echo "Invalid PACKAGE_ID value." - exit 1 -fi - -echo "Removing \"/Applications/$package_app_name\"..." -rm -rf "/Applications/$package_app_name" \ No newline at end of file +# For each package id, get all .app folders associated with the package and remove them. +for pkg_id in "${pkg_ids[@]}" +do + # Get volume and location of the package. + volume=$(pkgutil --pkg-info "$pkg_id" | grep -i "volume" | awk '{if (NF>1) print $NF}') + location=$(pkgutil --pkg-info "$pkg_id" | grep -i "location" | awk '{if (NF>1) print $NF}') + # Check if this package id corresponds to a valid/installed package + if [[ ! -z "$volume" ]]; then + # Remove individual directories that end with ".app" belonging to the package. + # Only process directories that end with ".app" to prevent Fleet from removing top level directories. + pkgutil --only-dirs --files "$pkg_id" | grep "\.app$" | sed -e 's@^@'"$volume""$location"'/@' | tr '\n' '\0' | xargs -n 1 -0 rm -rf + # Remove receipts + pkgutil --forget "$pkg_id" + else + echo "WARNING: volume is empty for package ID $pkg_id" + fi +done diff --git a/server/datastore/mysql/migrations/tables/20241002104104_UpdateUninstallScript.go b/server/datastore/mysql/migrations/tables/20241002104104_UpdateUninstallScript.go index 6f2833751e37..99b6dac45ec9 100644 --- a/server/datastore/mysql/migrations/tables/20241002104104_UpdateUninstallScript.go +++ b/server/datastore/mysql/migrations/tables/20241002104104_UpdateUninstallScript.go @@ -44,15 +44,13 @@ func Up_20241002104104(tx *sql.Tx) error { // Get script ids for uninstall scripts from software_installers platform = "darwin" and extension = "pkg" getUninstallScriptIDs := ` - SELECT si.id, si.uninstall_script_content_id, st.name - FROM software_installers si - JOIN software_titles st ON si.title_id = st.id - WHERE si.platform = "darwin" AND si.extension = "pkg" + SELECT id, uninstall_script_content_id + FROM software_installers + WHERE platform = "darwin" AND extension = "pkg" ` type scripts struct { - ID uint `db:"id"` - ScriptContentID uint `db:"uninstall_script_content_id"` - AppName string `db:"name"` + ID uint `db:"id"` + ScriptContentID uint `db:"uninstall_script_content_id"` } var uninstallScripts []scripts @@ -102,8 +100,10 @@ ON DUPLICATE KEY UPDATE // Check if script contents match the regex matches := existingUninstallScript.FindStringSubmatch(contents) if matches != nil { + packageIDs := matches[existingUninstallScript.SubexpIndex("packageIDs")] + // Prepare new script - newContents := strings.ReplaceAll(newScript, "$PACKAGE_ID", script.AppName) + newContents := strings.ReplaceAll(newScript, "$PACKAGE_ID", fmt.Sprintf("(%s)", packageIDs)) // Write new script newID, err := insertScriptContents(newContents) if err != nil { diff --git a/server/datastore/mysql/migrations/tables/data/uninstall_pkg.sh b/server/datastore/mysql/migrations/tables/data/uninstall_pkg.sh index 3bddaaf57fd7..0096d3f48da8 100644 --- a/server/datastore/mysql/migrations/tables/data/uninstall_pkg.sh +++ b/server/datastore/mysql/migrations/tables/data/uninstall_pkg.sh @@ -1,18 +1,22 @@ #!/bin/sh -package_app_name="$PACKAGE_ID" +# Fleet extracts and saves package IDs. +pkg_ids=$PACKAGE_ID -# Make sure PACKAGE_ID is not empty. -if [[ -z "$package_app_name" ]]; then - echo "Empty PACKAGE_ID variable." - exit 1 -fi - -# Make sure the PACKAGE_ID doesn't have "../" or is "." -if [[ "$package_app_name" == *".."* || "$package_app_name" == "." ]]; then - echo "Invalid PACKAGE_ID value." - exit 1 -fi - -echo "Removing \"/Applications/$package_app_name\"..." -rm -rf "/Applications/$package_app_name" \ No newline at end of file +# For each package id, get all .app folders associated with the package and remove them. +for pkg_id in "${pkg_ids[@]}" +do + # Get volume and location of the package. + volume=$(pkgutil --pkg-info "$pkg_id" | grep -i "volume" | awk '{if (NF>1) print $NF}') + location=$(pkgutil --pkg-info "$pkg_id" | grep -i "location" | awk '{if (NF>1) print $NF}') + # Check if this package id corresponds to a valid/installed package + if [[ ! -z "$volume" ]]; then + # Remove individual directories that end with ".app" belonging to the package. + # Only process directories that end with ".app" to prevent Fleet from removing top level directories. + pkgutil --only-dirs --files "$pkg_id" | grep "\.app$" | sed -e 's@^@'"$volume""$location"'/@' | tr '\n' '\0' | xargs -n 1 -0 rm -rf + # Remove receipts + pkgutil --forget "$pkg_id" + else + echo "WARNING: volume is empty for package ID $pkg_id" + fi +done diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go index ec695540ca1b..79f11cdbeb9e 100644 --- a/server/service/integration_enterprise_test.go +++ b/server/service/integration_enterprise_test.go @@ -11593,7 +11593,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerHostRequests() { assert.NotEmpty(t, respTitle.SoftwareTitle.SoftwarePackage.InstallScript) assert.NotEmpty(t, respTitle.SoftwareTitle.SoftwarePackage.UninstallScript) assert.NotContains(t, respTitle.SoftwareTitle.SoftwarePackage.UninstallScript, "$PACKAGE_ID") - assert.Contains(t, respTitle.SoftwareTitle.SoftwarePackage.UninstallScript, "\"DummyApp.app\"") + assert.Contains(t, respTitle.SoftwareTitle.SoftwarePackage.UninstallScript, "com.example.dummy") // install/uninstall request fails for the wrong platform s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/software/%d/install", h.ID, pkgTitleID), nil, http.StatusBadRequest, &resp) From a81775788f29b65977406a6ee23f5e6b2fd7f83c Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 3 Oct 2024 12:43:20 -0500 Subject: [PATCH 056/385] Website: revert changes to `.sailsrc` (#22620) Changes: - Reverted the changes to the `.sailsrc` file in the website folder. I will make a follow-up PR to add myself as a codeowner to make sure no changes to this file are committed to the repo. --- website/.sailsrc | 17395 --------------------------------------------- 1 file changed, 17395 deletions(-) diff --git a/website/.sailsrc b/website/.sailsrc index 0cd59c071452..391fdf869cce 100644 --- a/website/.sailsrc +++ b/website/.sailsrc @@ -7,17400 +7,5 @@ "_generatedWith": { "sails": "1.2.5", "sails-generate": "2.0.0" - }, - "builtStaticContent": { - "queries": [ - { - "name": "Get OpenSSL versions", - "platform": "linux", - "description": "Retrieves the OpenSSL version.", - "query": "SELECT name AS name, version AS version, 'deb_packages' AS source FROM deb_packages WHERE name LIKE 'openssl%' UNION SELECT name AS name, version AS version, 'apt_sources' AS source FROM apt_sources WHERE name LIKE 'openssl%' UNION SELECT name AS name, version AS version, 'rpm_packages' AS source FROM rpm_packages WHERE name LIKE 'openssl%';", - "purpose": "Informational", - "tags": [ - "inventory" - ], - "contributors": [ - { - "name": "zwass", - "handle": "zwass", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/zwass" - } - ], - "kind": "query", - "slug": "get-open-ssl-versions", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get authorized SSH keys", - "platform": "darwin, linux", - "description": "Presence of authorized SSH keys may be unusual on laptops. Could be completely normal on servers, but may be worth auditing for unusual keys and/or changes.", - "query": "SELECT username, authorized_keys. * FROM users CROSS JOIN authorized_keys USING (uid);", - "purpose": "Informational", - "remediation": "Check out the linked table (https://github.com/fleetdm/fleet/blob/32b4d53e7f1428ce43b0f9fa52838cbe7b413eed/handbook/queries/detect-hosts-with-high-severity-vulnerable-versions-of-openssl.md#table-of-vulnerable-openssl-versions) to determine if the installed version is a high severity vulnerability and view the corresponding CVE(s)", - "tags": [ - "built-in", - "ssh" - ], - "contributors": [ - { - "name": "mike-j-thomas", - "handle": "mike-j-thomas", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/mike-j-thomas" - } - ], - "kind": "query", - "slug": "get-authorized-ssh-keys", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get authorized keys for Domain Joined Accounts", - "platform": "darwin, linux", - "description": "List authorized_keys for each user on the system.", - "query": "SELECT * FROM users CROSS JOIN authorized_keys USING(uid) WHERE username IN (SELECT distinct(username) FROM last);", - "purpose": "Informational", - "tags": [ - "active directory", - "ssh" - ], - "contributors": [ - { - "name": "anelshaer", - "handle": "anelshaer", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/anelshaer" - } - ], - "kind": "query", - "slug": "get-authorized-keys-for-domain-joined-accounts", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get crashes", - "platform": "darwin", - "description": "Retrieve application, system, and mobile app crash logs.", - "query": "SELECT uid, datetime, responsible, exception_type, identifier, version, crash_path FROM users CROSS JOIN crashes USING (uid);", - "purpose": "Informational", - "tags": [ - "troubleshooting" - ], - "contributors": [ - { - "name": "zwass", - "handle": "zwass", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/zwass" - } - ], - "kind": "query", - "slug": "get-crashes", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get installed Chrome Extensions", - "platform": "darwin, linux, windows", - "description": "List installed Chrome Extensions for all users.", - "query": "SELECT * FROM users CROSS JOIN chrome_extensions USING (uid);", - "purpose": "Informational", - "tags": [ - "browser", - "built-in", - "inventory" - ], - "contributors": [ - { - "name": "zwass", - "handle": "zwass", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/zwass" - } - ], - "kind": "query", - "slug": "get-installed-chrome-extensions", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get installed Linux software", - "platform": "linux", - "description": "Get all software installed on a Linux computer, including browser plugins and installed packages. Note that this does not include other running processes in the processes table.", - "query": "SELECT name AS name, version AS version, 'Package (APT)' AS type, 'apt_sources' AS source FROM apt_sources UNION SELECT name AS name, version AS version, 'Package (deb)' AS type, 'deb_packages' AS source FROM deb_packages UNION SELECT package AS name, version AS version, 'Package (Portage)' AS type, 'portage_packages' AS source FROM portage_packages UNION SELECT name AS name, version AS version, 'Package (RPM)' AS type, 'rpm_packages' AS source FROM rpm_packages UNION SELECT name AS name, '' AS version, 'Package (YUM)' AS type, 'yum_sources' AS source FROM yum_sources UNION SELECT name AS name, version AS version, 'Package (NPM)' AS type, 'npm_packages' AS source FROM npm_packages UNION SELECT name AS name, version AS version, 'Package (Python)' AS type, 'python_packages' AS source FROM python_packages;", - "purpose": "Informational", - "tags": [ - "inventory", - "built-in" - ], - "contributors": [ - { - "name": "zwass", - "handle": "zwass", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/zwass" - } - ], - "kind": "query", - "slug": "get-installed-linux-software", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get installed macOS software", - "platform": "darwin", - "description": "Get all software installed on a macOS computer, including apps, browser plugins, and installed packages. Note that this does not include other running processes in the processes table.", - "query": "SELECT name AS name, bundle_short_version AS version, 'Application (macOS)' AS type, 'apps' AS source FROM apps UNION SELECT name AS name, version AS version, 'Package (Python)' AS type, 'python_packages' AS source FROM python_packages UNION SELECT name AS name, version AS version, 'Browser plugin (Chrome)' AS type, 'chrome_extensions' AS source FROM chrome_extensions UNION SELECT name AS name, version AS version, 'Browser plugin (Firefox)' AS type, 'firefox_addons' AS source FROM firefox_addons UNION SELECT name As name, version AS version, 'Browser plugin (Safari)' AS type, 'safari_extensions' AS source FROM safari_extensions UNION SELECT name AS name, version AS version, 'Package (Homebrew)' AS type, 'homebrew_packages' AS source FROM homebrew_packages;", - "purpose": "Informational", - "tags": [ - "inventory", - "built-in" - ], - "contributors": [ - { - "name": "zwass", - "handle": "zwass", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/zwass" - } - ], - "kind": "query", - "slug": "get-installed-mac-os-software", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get installed Safari extensions", - "platform": "darwin", - "description": "Retrieves the list of installed Safari Extensions for all users in the target system.", - "query": "SELECT safari_extensions.* FROM users join safari_extensions USING (uid);", - "purpose": "Informational", - "tags": [ - "browser", - "built-in", - "inventory" - ], - "contributors": [ - { - "name": "zwass", - "handle": "zwass", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/zwass" - } - ], - "kind": "query", - "slug": "get-installed-safari-extensions", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get installed Windows software", - "platform": "windows", - "description": "Get all software installed on a Windows computer, including programs, browser plugins, and installed packages. Note that this does not include other running processes in the processes table.", - "query": "SELECT name AS name, version AS version, 'Program (Windows)' AS type, 'programs' AS source FROM programs UNION SELECT name AS name, version AS version, 'Package (Python)' AS type, 'python_packages' AS source FROM python_packages UNION SELECT name AS name, version AS version, 'Browser plugin (IE)' AS type, 'ie_extensions' AS source FROM ie_extensions UNION SELECT name AS name, version AS version, 'Browser plugin (Chrome)' AS type, 'chrome_extensions' AS source FROM chrome_extensions UNION SELECT name AS name, version AS version, 'Browser plugin (Firefox)' AS type, 'firefox_addons' AS source FROM firefox_addons UNION SELECT name AS name, version AS version, 'Package (Chocolatey)' AS type, 'chocolatey_packages' AS source FROM chocolatey_packages;", - "purpose": "Informational", - "tags": [ - "inventory", - "built-in" - ], - "contributors": [ - { - "name": "zwass", - "handle": "zwass", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/zwass" - } - ], - "kind": "query", - "slug": "get-installed-windows-software", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get laptops with failing batteries", - "platform": "darwin", - "description": "Lists all laptops with under-performing or failing batteries.", - "query": "SELECT * FROM battery WHERE health != 'Good' AND condition NOT IN ('', 'Normal');", - "purpose": "Informational", - "tags": [ - "troubleshooting", - "hardware", - "inventory" - ], - "contributors": [ - { - "name": "zwass", - "handle": "zwass", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/zwass" - } - ], - "kind": "query", - "slug": "get-laptops-with-failing-batteries", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get current users with active shell/console on the system", - "platform": "darwin, linux, windows", - "description": "Get current users with active shell/console on the system and associated process", - "query": "SELECT user,host,time, p.name, p.cmdline, p.cwd, p.root FROM logged_in_users liu, processes p WHERE liu.pid = p.pid and liu.type='user' and liu.user <> '' ORDER BY time;", - "purpose": "Informational", - "tags": [ - "hunting", - "built-in" - ], - "contributors": [ - { - "name": "anelshaer", - "handle": "anelshaer", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/anelshaer" - } - ], - "kind": "query", - "slug": "get-current-users-with-active-shell-console-on-the-system", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get unencrypted SSH keys for local accounts", - "platform": "darwin, linux, windows", - "description": "Identify SSH keys created without a passphrase which can be used in Lateral Movement (MITRE. TA0008)", - "query": "SELECT uid, username, description, path, encrypted FROM users CROSS JOIN user_ssh_keys using (uid) WHERE encrypted=0;", - "purpose": "Informational", - "tags": [ - "inventory", - "compliance", - "ssh", - "built-in" - ], - "remediation": "First, make the user aware about the impact of SSH keys. Then rotate the unencrypted keys detected.", - "contributors": [ - { - "name": "anelshaer", - "handle": "anelshaer", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/anelshaer" - } - ], - "kind": "query", - "slug": "get-unencrypted-ssh-keys-for-local-accounts", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get unencrypted SSH keys for domain-joined accounts", - "platform": "darwin, linux, windows", - "description": "Identify SSH keys created without a passphrase which can be used in Lateral Movement (MITRE. TA0008)", - "query": "SELECT uid, username, description, path, encrypted FROM users CROSS JOIN user_ssh_keys using (uid) WHERE encrypted=0 and username in (SELECT distinct(username) FROM last);", - "purpose": "Informational", - "tags": [ - "inventory", - "compliance", - "ssh", - "active directory" - ], - "remediation": "First, make the user aware about the impact of SSH keys. Then rotate the unencrypted keys detected.", - "contributors": [ - { - "name": "anelshaer", - "handle": "anelshaer", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/anelshaer" - } - ], - "kind": "query", - "slug": "get-unencrypted-ssh-keys-for-domain-joined-accounts", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get dynamic linker hijacking on Linux (MITRE. T1574.006)", - "platform": "linux", - "description": "Detect any processes that run with LD_PRELOAD environment variable", - "query": "SELECT env.pid, env.key, env.value, p.name,p.path, p.cmdline, p.cwd FROM process_envs env join processes p USING (pid) WHERE key='LD_PRELOAD';", - "purpose": "Informational", - "tags": [ - "hunting", - "attack", - "t1574" - ], - "remediation": "Identify the process/binary detected and confirm with the system's owner.", - "contributors": [ - { - "name": "anelshaer", - "handle": "anelshaer", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/anelshaer" - } - ], - "kind": "query", - "slug": "get-dynamic-linker-hijacking-on-linux-mitre-t-1574-006", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get dynamic linker hijacking on macOS (MITRE. T1574.006)", - "platform": "darwin", - "description": "Detect any processes that run with DYLD_INSERT_LIBRARIES environment variable", - "query": "SELECT env.pid, env.key, env.value, p.name,p.path, p.cmdline, p.cwd FROM process_envs env join processes p USING (pid) WHERE key='DYLD_INSERT_LIBRARIES';", - "purpose": "Informational", - "tags": [ - "hunting", - "attack", - "t1574" - ], - "remediation": "Identify the process/binary detected and confirm with the system's owner.", - "contributors": [ - { - "name": "anelshaer", - "handle": "anelshaer", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/anelshaer" - } - ], - "kind": "query", - "slug": "get-dynamic-linker-hijacking-on-mac-os-mitre-t-1574-006", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get etc hosts entries", - "platform": "darwin, linux", - "description": "Line-parsed /etc/hosts", - "query": "SELECT * FROM etc_hosts WHERE address not in ('127.0.0.1', '::1');", - "purpose": "informational", - "tags": [ - "hunting", - "inventory" - ], - "contributors": [ - { - "name": "anelshaer", - "handle": "anelshaer", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/anelshaer" - } - ], - "kind": "query", - "slug": "get-etc-hosts-entries", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get network interfaces", - "platform": "darwin, linux, windows", - "description": "Network interfaces MAC address", - "query": "SELECT a.interface, a.address, d.mac FROM interface_addresses a JOIN interface_details d USING (interface) WHERE address not in ('127.0.0.1', '::1');", - "purpose": "informational", - "tags": [ - "hunting", - "inventory" - ], - "contributors": [ - { - "name": "anelshaer", - "handle": "anelshaer", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/anelshaer" - } - ], - "kind": "query", - "slug": "get-network-interfaces", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get local user accounts", - "platform": "darwin, linux, windows", - "description": "Local user accounts (including domain accounts that have logged on locally (Windows)).", - "query": "SELECT uid, gid, username, description, directory, shell FROM users;", - "purpose": "informational", - "tags": [ - "hunting", - "inventory" - ], - "contributors": [ - { - "name": "anelshaer", - "handle": "anelshaer", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/anelshaer" - } - ], - "kind": "query", - "slug": "get-local-user-accounts", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get active user accounts on servers", - "platform": "linux", - "description": "Domain Joined environments normally have root or other service only accounts and users are SSH-ing using their Domain Accounts.", - "query": "SELECT * FROM shadow WHERE password_status='active' and username!='root';", - "purpose": "informational", - "tags": [ - "hunting", - "inventory", - "active directory" - ], - "contributors": [ - { - "name": "anelshaer", - "handle": "anelshaer", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/anelshaer" - } - ], - "kind": "query", - "slug": "get-active-user-accounts-on-servers", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get Nmap scanner", - "platform": "darwin, linux, windows", - "description": "Get Nmap scanner process, as well as its user, parent, and process details.", - "query": "SELECT p.pid, name, p.path, cmdline, cwd, start_time, parent, (SELECT name FROM processes WHERE pid=p.parent) AS parent_name, (SELECT username FROM users WHERE uid=p.uid) AS username FROM processes as p WHERE cmdline like 'nmap%';", - "purpose": "Informational", - "tags": [ - "hunting", - "attack", - "t1046" - ], - "contributors": [ - { - "name": "anelshaer", - "handle": "anelshaer", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/anelshaer" - } - ], - "kind": "query", - "slug": "get-nmap-scanner", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get Docker contained processes on a system", - "platform": "darwin, linux", - "description": "Docker containers Processes, can be used on normal systems or a kubenode.", - "query": "SELECT c.id, c.name, c.image, c.image_id, c.command, c.created, c.state, c.status, p.cmdline FROM docker_containers c CROSS JOIN docker_container_processes p using(id);", - "purpose": "Informational", - "tags": [ - "built-in", - "containers", - "inventory" - ], - "contributors": [ - { - "name": "anelshaer", - "handle": "anelshaer", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/anelshaer" - } - ], - "kind": "query", - "slug": "get-docker-contained-processes-on-a-system", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get Windows print spooler remote code execution vulnerability", - "platform": "windows", - "description": "Detects devices that are potentially vulnerable to CVE-2021-1675 because the print spooler service is not disabled.", - "query": "SELECT CASE cnt WHEN 2 THEN \"TRUE\" ELSE \"FALSE\" END \"Vulnerable\" FROM (SELECT name start_type, COUNT(name) AS cnt FROM services WHERE name = 'NTDS' or (name = 'Spooler' and start_type <> 'DISABLED')) WHERE cnt = 2;", - "purpose": "Informational", - "tags": [ - "vulnerability" - ], - "contributors": [ - { - "name": "maravedi", - "handle": "maravedi", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/maravedi" - } - ], - "kind": "query", - "slug": "get-windows-print-spooler-remote-code-execution-vulnerability", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get local users and their privileges", - "platform": "darwin, linux, windows", - "description": "Collects the local user accounts and their respective user group.", - "query": "SELECT uid, username, type, groupname FROM users u JOIN groups g ON g.gid = u.gid;", - "purpose": "informational", - "tags": [ - "inventory" - ], - "contributors": [ - { - "name": "noahtalerman", - "handle": "noahtalerman", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/noahtalerman" - } - ], - "kind": "query", - "slug": "get-local-users-and-their-privileges", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get processes that no longer exist on disk", - "platform": "linux, darwin, windows", - "description": "Lists all processes of which the binary which launched them no longer exists on disk. Attackers often delete files from disk after launching a process to mask presence.", - "query": "SELECT name, path, pid FROM processes WHERE on_disk = 0;", - "purpose": "Incident response", - "tags": [ - "hunting", - "built-in" - ], - "contributors": [ - { - "name": "alphabrevity", - "handle": "alphabrevity", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/alphabrevity" - } - ], - "kind": "query", - "slug": "get-processes-that-no-longer-exist-on-disk", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get user files matching a specific hash", - "platform": "darwin, linux", - "description": "Looks for specific hash in the Users/ directories for files that are less than 50MB (osquery file size limitation.)", - "query": "SELECT path, sha256 FROM hash WHERE path IN (SELECT path FROM file WHERE size < 50000000 AND path LIKE '/Users/%/Documents/%%') AND sha256 = '16d28cd1d78b823c4f961a6da78d67a8975d66cde68581798778ed1f98a56d75';", - "purpose": "Informational", - "tags": [ - "hunting", - "built-in" - ], - "contributors": [ - { - "name": "alphabrevity", - "handle": "alphabrevity", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/alphabrevity" - } - ], - "kind": "query", - "slug": "get-user-files-matching-a-specific-hash", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get local administrator accounts on macOS", - "platform": "darwin", - "description": "The query allows you to check macOS systems for local administrator accounts.", - "query": "SELECT uid, username, type FROM users u JOIN groups g ON g.gid = u.gid;", - "purpose": "Informational", - "tags": [ - "hunting", - "inventory" - ], - "contributors": [ - { - "name": "alphabrevity", - "handle": "alphabrevity", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/alphabrevity" - } - ], - "kind": "query", - "slug": "get-local-administrator-accounts-on-mac-os", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get all listening ports, by process", - "platform": "linux, darwin, windows", - "description": "List ports that are listening on all interfaces, along with the process to which they are attached.", - "query": "SELECT lp.address, lp.pid, lp.port, lp.protocol, p.name, p.path, p.cmdline FROM listening_ports lp JOIN processes p ON lp.pid = p.pid WHERE lp.address = \"0.0.0.0\";", - "purpose": "Informational", - "tags": [ - "hunting", - "network" - ], - "contributors": [ - { - "name": "alphabrevity", - "handle": "alphabrevity", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/alphabrevity" - } - ], - "kind": "query", - "slug": "get-all-listening-ports-by-process", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get whether TeamViewer is installed/running", - "platform": "windows", - "description": "Looks for the TeamViewer service running on machines. This is often used when attackers gain access to a machine, running TeamViewer to allow them to access a machine.", - "query": "SELECT display_name,status,s.pid,p.path FROM services AS s JOIN processes AS p USING(pid) WHERE s.name LIKE \"%teamviewer%\";", - "purpose": "Informational", - "tags": [ - "hunting", - "inventory" - ], - "contributors": [ - { - "name": "alphabrevity", - "handle": "alphabrevity", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/alphabrevity" - } - ], - "kind": "query", - "slug": "get-whether-team-viewer-is-installed-running", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get malicious Python backdoors", - "platform": "darwin, linux, windows", - "description": "Watches for the backdoored Python packages installed on the system. See (http://www.nbu.gov.sk/skcsirt-sa-20170909-pypi/index.html)", - "query": "SELECT CASE cnt WHEN 0 THEN \"NONE_INSTALLED\" ELSE \"INSTALLED\" END AS \"Malicious Python Packages\", package_name, package_version FROM (SELECT COUNT(name) AS cnt, name AS package_name, version AS package_version, path AS package_path FROM python_packages WHERE package_name IN ('acquisition', 'apidev-coop', 'bzip', 'crypt', 'django-server', 'pwd', 'setup-tools', 'telnet', 'urlib3', 'urllib'));", - "purpose": "Informational", - "tags": [ - "hunting", - "inventory", - "malware" - ], - "contributors": [ - { - "name": "alphabrevity", - "handle": "alphabrevity", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/alphabrevity" - } - ], - "kind": "query", - "slug": "get-malicious-python-backdoors", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Check for artifacts of the Floxif trojan", - "platform": "windows", - "description": "Checks for artifacts from the Floxif trojan on Windows machines.", - "query": "SELECT * FROM registry WHERE path LIKE 'HKEY_LOCAL_MACHINE\\\\SOFTWARE\\\\Piriform\\\\Agomo%';", - "purpose": "Informational", - "tags": [ - "hunting", - "malware" - ], - "contributors": [ - { - "name": "micheal-o", - "handle": "micheal-o", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/micheal-o" - } - ], - "kind": "query", - "slug": "check-for-artifacts-of-the-floxif-trojan", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get Shimcache table", - "platform": "windows", - "description": "Returns forensic data showing evidence of likely file execution, in addition to the last modified timestamp of the file, order of execution, full file path order of execution, and the order in which files were executed.", - "query": "select * from Shimcache", - "purpose": "Informational", - "tags": [ - "hunting" - ], - "contributors": [ - { - "name": "puffyCid", - "handle": "puffyCid", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/puffyCid" - } - ], - "kind": "query", - "slug": "get-shimcache-table", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get running docker containers", - "platform": "darwin, linux", - "description": "Returns the running Docker containers", - "query": "SELECT id, name, image, image_id, state, status FROM docker_containers WHERE state = \"running\";", - "purpose": "Informational", - "tags": [ - "containers", - "inventory" - ], - "contributors": [ - { - "name": "DominusKelvin", - "handle": "DominusKelvin", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/DominusKelvin" - } - ], - "kind": "query", - "slug": "get-running-docker-containers", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get applications hogging memory", - "platform": "darwin, linux, windows", - "description": "Returns top 10 applications or processes hogging memory the most.", - "query": "SELECT pid, name, ROUND((total_size * '10e-7'), 2) AS memory_used FROM processes ORDER BY total_size DESC LIMIT 10;", - "purpose": "Informational", - "tags": [ - "troubleshooting" - ], - "contributors": [ - { - "name": "DominusKelvin", - "handle": "DominusKelvin", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/DominusKelvin" - } - ], - "kind": "query", - "slug": "get-applications-hogging-memory", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get servers with root login in the last 24 hours", - "platform": "darwin, linux, windows", - "description": "Returns servers with root login in the last 24 hours and the time the users were logged in.", - "query": "SELECT * FROM last WHERE username = \"root\" AND time > (( SELECT unix_time FROM time ) - 86400 );", - "purpose": "Informational", - "tags": [ - "hunting" - ], - "contributors": [ - { - "name": "DominusKelvin", - "handle": "DominusKelvin", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/DominusKelvin" - } - ], - "kind": "query", - "slug": "get-servers-with-root-login-in-the-last-24-hours", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Detect active processes with Log4j running", - "platform": "darwin, linux", - "description": "Returns a list of active processes and the Jar paths which are using Log4j. Version numbers are usually within the Jar filename. Note: This query is resource intensive and has caused problems on systems with limited swap space. Test on some systems before running this widely.", - "query": "WITH target_jars AS (\n SELECT DISTINCT path\n FROM (\n WITH split(word, str) AS(\n SELECT '', cmdline || ' '\n FROM processes\n UNION ALL\n SELECT substr(str, 0, instr(str, ' ')), substr(str, instr(str, ' ') + 1)\n FROM split\n WHERE str != '')\n SELECT word AS path\n FROM split\n WHERE word LIKE '%.jar'\n UNION ALL\n SELECT path\n FROM process_open_files\n WHERE path LIKE '%.jar'\n )\n)\nSELECT path, matches\nFROM yara\nWHERE path IN (SELECT path FROM target_jars)\n AND count > 0\n AND sigrule IN (\n 'rule log4jJndiLookup {\n strings:\n $jndilookup = \"JndiLookup\"\n condition:\n $jndilookup\n }',\n 'rule log4jJavaClass {\n strings:\n $javaclass = \"org/apache/logging/log4j\"\n condition:\n $javaclass\n }'\n );\n", - "purpose": "Detection", - "tags": [ - "vulnerability" - ], - "contributors": [ - { - "name": "zwass", - "handle": "zwass", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/zwass" - }, - { - "name": "tgauda", - "handle": "tgauda", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/tgauda" - } - ], - "kind": "query", - "slug": "detect-active-processes-with-log-4-j-running", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get applications that were opened within the last 24 hours", - "platform": "darwin", - "description": "Returns applications that were opened within the last 24 hours starting with the last opened application.", - "query": "SELECT * FROM apps WHERE last_opened_time > (( SELECT unix_time FROM time ) - 86400 ) ORDER BY last_opened_time DESC;", - "purpose": "Informational", - "tags": [ - "inventory" - ], - "contributors": [ - { - "name": "DominusKelvin", - "handle": "DominusKelvin", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/DominusKelvin" - } - ], - "kind": "query", - "slug": "get-applications-that-were-opened-within-the-last-24-hours", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get applications that are not in the Applications directory", - "platform": "darwin", - "description": "Returns applications that are not in the `/Applications` directory", - "query": "SELECT * FROM apps WHERE path NOT LIKE '/Applications/%';", - "purpose": "Informational", - "tags": [ - "hunting", - "inventory" - ], - "contributors": [ - { - "name": "DominusKelvin", - "handle": "DominusKelvin", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/DominusKelvin" - } - ], - "kind": "query", - "slug": "get-applications-that-are-not-in-the-applications-directory", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get subscription-based applications that have not been opened for the last 30 days", - "platform": "darwin", - "description": "Returns applications that are subscription-based and have not been opened for the last 30 days. You can replace the list of applications with those specific to your use case.", - "query": "SELECT * FROM apps WHERE path LIKE '/Applications/%' AND name IN (\"Photoshop.app\", \"Adobe XD.app\", \"Sketch.app\", \"Illustrator.app\") AND last_opened_time < (( SELECT unix_time FROM time ) - 2592000000000 );", - "purpose": "Informational", - "tags": [ - "inventory" - ], - "contributors": [ - { - "name": "DominusKelvin", - "handle": "DominusKelvin", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/DominusKelvin" - } - ], - "kind": "query", - "slug": "get-subscription-based-applications-that-have-not-been-opened-for-the-last-30-days", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get operating system information", - "platform": "darwin, windows, linux", - "description": "Returns the operating system name and version on the device.", - "query": "SELECT name, version FROM os_version;", - "purpose": "Informational", - "tags": [ - "inventory", - "built-in" - ], - "contributors": [ - { - "name": "noahtalerman", - "handle": "noahtalerman", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/noahtalerman" - } - ], - "kind": "query", - "slug": "get-operating-system-information", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Gatekeeper enabled (macOS)", - "query": "SELECT 1 FROM gatekeeper WHERE assessments_enabled = 1;", - "description": "Checks to make sure that the Gatekeeper feature is enabled on macOS devices. Gatekeeper tries to ensure only trusted software is run on a mac machine.", - "resolution": "To enable Gatekeeper, on the failing device, run the following command in the Terminal app: /usr/sbin/spctl --master-enable.", - "tags": [ - "compliance", - "hardening", - "built-in", - "cis", - "cis2.5.2.1" - ], - "platform": "darwin", - "contributors": [ - { - "name": "groob", - "handle": "groob", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/groob" - } - ], - "kind": "policy", - "slug": "gatekeeper-enabled-mac-os", - "requiresMdm": false, - "critical": true - }, - { - "name": "Full disk encryption enabled (Windows)", - "query": "SELECT 1 FROM bitlocker_info WHERE drive_letter='C:' AND protection_status=1;", - "description": "Checks to make sure that full disk encryption is enabled on Windows devices.", - "resolution": "To get additional information, run the following osquery query on the failing device: SELECT * FROM bitlocker_info. In the query results, if protection_status is 2, then the status cannot be determined. If it is 0, it is considered unprotected. Use the additional results (percent_encrypted, conversion_status, etc.) to help narrow down the specific reason why Windows considers the volume unprotected.", - "platform": "windows", - "tags": [ - "compliance", - "hardening", - "built-in" - ], - "contributors": [ - { - "name": "defensivedepth", - "handle": "defensivedepth", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/defensivedepth" - } - ], - "kind": "policy", - "slug": "full-disk-encryption-enabled-windows", - "requiresMdm": false, - "critical": true - }, - { - "name": "Full disk encryption enabled (macOS)", - "query": "SELECT 1 FROM disk_encryption WHERE user_uuid IS NOT \"\" AND filevault_status = 'on' LIMIT 1;", - "description": "Checks to make sure that full disk encryption (FileVault) is enabled on macOS devices.", - "resolution": "To enable full disk encryption, on the failing device, select System Preferences > Security & Privacy > FileVault > Turn On FileVault.", - "tags": [ - "compliance", - "hardening", - "built-in", - "cis", - "cis2.5.1.1" - ], - "platform": "darwin", - "contributors": [ - { - "name": "groob", - "handle": "groob", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/groob" - } - ], - "kind": "policy", - "slug": "full-disk-encryption-enabled-mac-os", - "requiresMdm": false, - "critical": true - }, - { - "name": "Full disk encryption enabled (Linux)", - "query": "SELECT 1 FROM disk_encryption WHERE encrypted=1 AND name LIKE '/dev/dm-1';", - "description": "Checks if the root drive is encrypted. There are many ways to encrypt Linux systems. This is the default on distributions such as Ubuntu.", - "resolution": "Ensure the image deployed to your Linux workstation includes full disk encryption.", - "platform": "linux", - "tags": [ - "compliance", - "hardening", - "built-in" - ], - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "full-disk-encryption-enabled-linux", - "requiresMdm": false, - "critical": true - }, - { - "name": "System Integrity Protection enabled (macOS)", - "query": "SELECT 1 FROM sip_config WHERE config_flag = 'sip' AND enabled = 1;", - "description": "Checks to make sure that the System Integrity Protection feature is enabled.", - "resolution": "To enable System Integrity Protection, on the failing device, run the following command in the Terminal app: /usr/sbin/spctl --master-enable.", - "tags": [ - "compliance", - "malware", - "hardening", - "built-in", - "cis", - "cis5.1.2" - ], - "platform": "darwin", - "contributors": [ - { - "name": "groob", - "handle": "groob", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/groob" - } - ], - "kind": "policy", - "slug": "system-integrity-protection-enabled-mac-os", - "requiresMdm": false - }, - { - "name": "Automatic login disabled (macOS)", - "query": "SELECT 1 FROM managed_policies WHERE domain = 'com.apple.loginwindow' AND name = 'com.apple.login.mcx.DisableAutoLoginClient' AND value = 1 LIMIT 1;", - "description": "Checks that a mobile device management (MDM) solution configures the Mac to prevent login in without a password.", - "resolution": "Contact your IT administrator to ensure your Mac is receiving a profile that disables automatic login.", - "tags": [ - "compliance", - "hardening", - "built-in" - ], - "platform": "darwin", - "contributors": [ - { - "name": "groob", - "handle": "groob", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/groob" - } - ], - "kind": "policy", - "slug": "automatic-login-disabled-mac-os", - "requiresMdm": true, - "critical": true - }, - { - "name": "Secure keyboard entry for Terminal application enabled (macOS)", - "query": "SELECT 1 FROM managed_policies WHERE domain = 'com.apple.Terminal' AND name = 'SecureKeyboardEntry' AND value = 1 LIMIT 1;", - "description": "Checks that a mobile device management (MDM) solution configures the Mac to enabled secure keyboard entry for the Terminal application.", - "resolution": "Contact your IT administrator to ensure your Mac is receiving a profile that enables secure keyboard entry for the Terminal application.", - "tags": [ - "compliance", - "hardening", - "built-in" - ], - "platform": "darwin", - "contributors": [ - { - "name": "groob", - "handle": "groob", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/groob" - } - ], - "kind": "policy", - "slug": "secure-keyboard-entry-for-terminal-application-enabled-mac-os", - "requiresMdm": true - }, - { - "name": "Get built-in antivirus status on macOS", - "platform": "darwin", - "query": "SELECT path, value AS version FROM plist WHERE (key = 'CFBundleShortVersionString' AND path = '/Library/Apple/System/Library/CoreServices/MRT.app/Contents/Info.plist') OR (key = 'CFBundleShortVersionString' AND path = '/Library/Apple/System/Library/CoreServices/XProtect.bundle/Contents/Info.plist');", - "description": "Reads the version numbers from the Malware Removal Tool (MRT) and built-in antivirus (XProtect) plists", - "purpose": "Informational", - "tags": [ - "compliance", - "malware", - "hardening", - "built-in" - ], - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "query", - "slug": "get-built-in-antivirus-status-on-mac-os", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get antivirus status from the Windows Security Center", - "platform": "windows", - "query": "SELECT antivirus, signatures_up_to_date from windows_security_center CROSS JOIN windows_security_products WHERE type = 'Antivirus';", - "description": "Selects the antivirus and signatures status from Windows Security Center.", - "purpose": "Informational", - "tags": [ - "compliance", - "malware", - "hardening", - "built-in" - ], - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "query", - "slug": "get-antivirus-status-from-the-windows-security-center", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get antivirus (ClamAV/clamd) and updater (freshclam) process status", - "platform": "linux", - "query": "SELECT pid, state, cmdline, name FROM processes WHERE name='clamd' OR name='freshclam';", - "description": "Selects the clamd and freshclam processes to ensure AV and its updater are running", - "purpose": "Informational", - "tags": [ - "compliance", - "malware", - "hardening", - "built-in" - ], - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "query", - "slug": "get-antivirus-clam-av-clamd-and-updater-freshclam-process-status", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Antivirus healthy (macOS)", - "query": "SELECT score FROM (SELECT case when COUNT(*) = 2 then 1 ELSE 0 END AS score FROM plist WHERE (key = 'CFBundleShortVersionString' AND path = '/Library/Apple/System/Library/CoreServices/XProtect.bundle/Contents/Info.plist' AND value>=2162) OR (key = 'CFBundleShortVersionString' AND path = '/Library/Apple/System/Library/CoreServices/MRT.app/Contents/Info.plist' and value>=1.93)) WHERE score == 1;", - "description": "Checks the version of Malware Removal Tool (MRT) and the built-in macOS AV (Xprotect). Replace version numbers with the latest version regularly.", - "resolution": "To enable automatic security definition updates, on the failing device, select System Preferences > Software Update > Advanced > Turn on Install system data files and security updates.", - "tags": [ - "compliance", - "malware", - "hardening", - "built-in", - "template" - ], - "platform": "darwin", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "antivirus-healthy-mac-os", - "requiresMdm": false - }, - { - "name": "Antivirus healthy (Windows)", - "query": "SELECT 1 from windows_security_center wsc CROSS JOIN windows_security_products wsp WHERE antivirus = 'Good' AND type = 'Antivirus' AND signatures_up_to_date=1;", - "description": "Checks the status of antivirus and signature updates from the Windows Security Center.", - "resolution": "Ensure Windows Defender or your third-party antivirus is running, up to date, and visible in the Windows Security Center.", - "tags": [ - "compliance", - "malware", - "hardening", - "built-in" - ], - "platform": "windows", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "antivirus-healthy-windows", - "requiresMdm": false - }, - { - "name": "Antivirus healthy (Linux)", - "query": "SELECT score FROM (SELECT case when COUNT(*) = 2 then 1 ELSE 0 END AS score FROM processes WHERE (name = 'clamd') OR (name = 'freshclam')) WHERE score == 1;", - "description": "Checks that both ClamAV's daemon and its updater service (freshclam) are running.", - "resolution": "Ensure ClamAV and Freshclam are installed and running.", - "tags": [ - "compliance", - "malware", - "hardening", - "built-in" - ], - "platform": "linux", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "antivirus-healthy-linux", - "requiresMdm": false - }, - { - "name": "MDM enrolled (macOS)", - "query": "SELECT 1 from mdm WHERE enrolled='true';", - "description": "Required: osquery deployed with Orbit, or manual installation of macadmins/osquery-extension. Checks that a mac is enrolled to MDM. Add a AND on identity_certificate_uuid to check for a specific MDM.", - "resolution": "Enroll device to MDM", - "tags": [ - "compliance", - "hardening", - "built-in" - ], - "platform": "darwin", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "mdm-enrolled-mac-os", - "requiresMdm": false, - "critical": true - }, - { - "name": "Docker application is up to date or not present (macOS)", - "query": "SELECT 1 WHERE EXISTS (SELECT 1 FROM apps a1 WHERE a1.bundle_identifier = 'com.electron.dockerdesktop' AND a1.bundle_short_version>='4.6.1') OR NOT EXISTS (SELECT 1 FROM apps a2 WHERE a2.bundle_identifier = 'com.electron.dockerdesktop');", - "description": "Checks if the application (Docker Desktop example) is installed and up to date, or not installed. Fails if the application is installed and on a lower version. You can copy this query and replace the bundle_identifier and bundle_version values to apply the same type of policy to other applications.", - "resolution": "Update Docker or remove it if not used.", - "tags": [ - "inventory", - "vulnerability", - "built-in" - ], - "platform": "darwin", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "docker-application-is-up-to-date-or-not-present-mac-os", - "requiresMdm": false - }, - { - "name": "SSH keys encrypted", - "query": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM users CROSS JOIN user_ssh_keys USING (uid) WHERE encrypted='0');", - "description": "Required: osquery must have Full Disk Access. Policy passes if all keys are encrypted, including if no keys are present.", - "resolution": "Use this command to encrypt existing SSH keys by providing the path to the file: ssh-keygen -o -p -f /path/to/file", - "tags": [ - "compliance", - "ssh", - "built-in" - ], - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "platform": "darwin,linux,windows", - "kind": "policy", - "slug": "ssh-keys-encrypted", - "requiresMdm": false - }, - { - "name": "Suspicious autostart (Windows)", - "query": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM startup_items WHERE path = \"regsvr32\" AND args LIKE \"%http%\");", - "description": "Checks for an autostart that is attempting to load a dynamic link library (DLL) from the internet.", - "resolution": "Remove the suspicious startup entry.", - "tags": [ - "malware", - "hunting" - ], - "platform": "windows", - "contributors": [ - { - "name": "kswagler-rh", - "handle": "kswagler-rh", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/kswagler-rh" - } - ], - "kind": "policy", - "slug": "suspicious-autostart-windows", - "requiresMdm": false - }, - { - "name": "Firewall enabled (macOS)", - "query": "SELECT 1 FROM alf WHERE global_state >= 1;", - "description": "Checks if the firewall is enabled.", - "resolution": "In System Preferences, open Security & Privacy, navigate to the Firewall tab and click Turn On Firewall.", - "tags": [ - "hardening", - "compliance", - "built-in", - "cis", - "cis2.5.2.2" - ], - "platform": "darwin", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "firewall-enabled-mac-os", - "requiresMdm": false - }, - { - "name": "Screen lock enabled (macOS)", - "query": "SELECT 1 FROM managed_policies WHERE name='askForPassword' AND value='1';", - "description": "Checks that a mobile device management (MDM) solution configures the Mac to enable screen lock.", - "resolution": "Contact your IT administrator to ensure your Mac is receiving a profile that enables screen lock.", - "tags": [ - "compliance", - "hardening", - "built-in" - ], - "platform": "darwin", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "screen-lock-enabled-mac-os", - "requiresMdm": true - }, - { - "name": "Screen lock enabled (Windows)", - "query": "SELECT 1 FROM registry WHERE path = 'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System\\InactivityTimeoutSecs' AND CAST(data as INTEGER) <= 1800;", - "description": "Checks if the screen lock is enabled and configured to lock the system within 30 minutes or less.", - "resolution": "Contact your IT administrator to enable the Interactive Logon: Machine inactivity limit setting with a value of 1800 seconds or lower.", - "tags": [ - "compliance", - "hardening", - "built-in" - ], - "platform": "windows", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "screen-lock-enabled-windows", - "requiresMdm": false - }, - { - "name": "Password requires 10 or more characters (macOS)", - "query": "SELECT 1 FROM (SELECT cast(lengthtxt as integer(2)) minlength FROM (SELECT SUBSTRING(length, 1, 2) AS lengthtxt FROM (SELECT policy_description, policy_identifier, split(policy_content, '{', 1) AS length FROM password_policy WHERE policy_identifier LIKE '%minLength')) WHERE minlength >= 10);", - "description": "Checks that the password policy requires at least 10 characters. Requires osquery 5.4.0 or newer.", - "resolution": "Contact your IT administrator to make sure your Mac is receiving configuration profiles for password length.", - "platform": "darwin", - "tags": [ - "compliance", - "hardening", - "built-in", - "cis", - "cis5.2.2" - ], - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "password-requires-10-or-more-characters-mac-os", - "requiresMdm": false - }, - { - "name": "Operating system up to date (macOS)", - "query": "SELECT 1 FROM os_version WHERE version >= '14.1.1';", - "description": "Checks that the operating system is up to date.", - "resolution": "From the Apple menu () in the corner of your screen choose System Preferences. Then select Software Update and select Upgrade Now. You might be asked to restart or enter your password.", - "tags": [ - "compliance", - "cis", - "template", - "cis1.1" - ], - "platform": "darwin", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "operating-system-up-to-date-mac-os", - "requiresMdm": false, - "critical": true - }, - { - "name": "Automatic updates enabled (macOS)", - "query": "SELECT 1 FROM managed_policies WHERE domain='com.apple.SoftwareUpdate' AND name='AutomaticCheckEnabled' AND value=1 LIMIT 1;", - "description": "Checks that a mobile device management (MDM) solution configures the Mac to automatically check for updates.", - "resolution": "Contact your IT administrator to ensure your Mac is receiving a profile that enables automatic updates.", - "tags": [ - "compliance", - "cis", - "cis1.2" - ], - "platform": "darwin", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "automatic-updates-enabled-mac-os", - "requiresMdm": true - }, - { - "name": "Automatic update downloads enabled (macOS)", - "query": "SELECT 1 FROM managed_policies WHERE domain='com.apple.SoftwareUpdate' AND name='AutomaticDownload' AND value=1 LIMIT 1;", - "description": "Checks that a mobile device management (MDM) solution configures the Mac to automatically download updates.", - "resolution": "Contact your IT administrator to ensure your Mac is receiving a profile that enables automatic update downloads.", - "tags": [ - "compliance", - "cis", - "cis1.3" - ], - "platform": "darwin", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "automatic-update-downloads-enabled-mac-os", - "requiresMdm": true - }, - { - "name": "Automatic installation of application updates is enabled (macOS)", - "query": "SELECT 1 FROM managed_policies WHERE domain='com.apple.SoftwareUpdate' AND name='AutomaticallyInstallAppUpdates' AND value=1 LIMIT 1;", - "description": "Checks that a mobile device management (MDM) solution configures the Mac to automatically install updates to App Store applications.", - "resolution": "Contact your IT administrator to ensure your Mac is receiving a profile that enables automatic installation of application updates.", - "tags": [ - "compliance", - "cis", - "cis1.4" - ], - "platform": "darwin", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "automatic-installation-of-application-updates-is-enabled-mac-os", - "requiresMdm": true - }, - { - "name": "Automatic security and data file updates is enabled (macOS)", - "query": "SELECT 1 FROM managed_policies WHERE domain='com.apple.SoftwareUpdate' AND name='CriticalUpdateInstall' AND value=1 LIMIT 1;", - "description": "Checks that a mobile device management (MDM) solution configures the Mac to automatically download updates to built-in macOS security tools such as malware removal tools.", - "resolution": "Contact your IT administrator to ensure your Mac is receiving a profile that enables automatic security and data update installation.", - "tags": [ - "compliance", - "cis", - "cis1.5" - ], - "platform": "darwin", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "automatic-security-and-data-file-updates-is-enabled-mac-os", - "requiresMdm": true - }, - { - "name": "Automatic installation of operating system updates is enabled (macOS)", - "query": "SELECT 1 FROM managed_policies WHERE domain='com.apple.SoftwareUpdate' AND name='AutomaticallyInstallMacOSUpdates' AND value=1 LIMIT 1;", - "description": "Checks that a mobile device management (MDM) solution configures the Mac to automatically install operating system updates.", - "resolution": "Contact your IT administrator to ensure your Mac is receiving a profile that enables automatic installation of operating system updates.", - "tags": [ - "compliance", - "cis", - "cis1.6" - ], - "platform": "darwin", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "automatic-installation-of-operating-system-updates-is-enabled-mac-os", - "requiresMdm": true - }, - { - "name": "Time and date are configured to be updated automatically (macOS)", - "query": "SELECT 1 FROM managed_policies WHERE domain='com.apple.applicationaccess' AND name='forceAutomaticDateAndTime' AND value=1 LIMIT 1;", - "description": "Checks that a mobile device management (MDM) solution configures the Mac to automatically update the time and date.", - "resolution": "Contact your IT administrator to ensure your Mac is receiving a profile that enables automatic time and date configuration.", - "tags": [ - "compliance", - "cis", - "cis2.2.1" - ], - "platform": "darwin", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "time-and-date-are-configured-to-be-updated-automatically-mac-os", - "requiresMdm": true - }, - { - "name": "Lock screen after inactivity of 20 minutes or less (macOS)", - "query": "SELECT 1 WHERE EXISTS (SELECT CAST(value as integer(4)) valueint from managed_policies WHERE domain = 'com.apple.screensaver' AND name = 'askForPasswordDelay' AND valueint <= 60 LIMIT 1) AND EXISTS (SELECT CAST(value as integer(4)) valueint from managed_policies WHERE domain = 'com.apple.screensaver' AND name = 'idleTime' AND valueint <= 1140 LIMIT 1) AND EXISTS (SELECT 1 from managed_policies WHERE domain='com.apple.screensaver' AND name='askForPassword' AND value=1 LIMIT 1);", - "description": "Checks that a mobile device management (MDM) solution configures the Mac to lock the screen after 20 minutes or less.", - "resolution": "Contact your IT administrator to ensure your Mac is receiving a profile that enables the screen saver after inactivity of 20 minutes or less.", - "tags": [ - "compliance", - "cis", - "cis2.3.1", - "cis5.8" - ], - "platform": "darwin", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "lock-screen-after-inactivity-of-20-minutes-or-less-mac-os", - "requiresMdm": true - }, - { - "name": "Internet sharing is blocked (macOS)", - "query": "SELECT 1 FROM managed_policies WHERE domain='com.apple.MCX' AND name='forceInternetSharingOff' AND value='1' LIMIT 1;", - "description": "Checks that a mobile device management (MDM) solution configures the Mac to prevent Internet sharing.", - "resolution": "Contact your IT administrator to ensure your Mac is receiving a profile that prevents Internet sharing.", - "tags": [ - "compliance", - "cis", - "cis2.4.2" - ], - "platform": "darwin", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "internet-sharing-is-blocked-mac-os", - "requiresMdm": true - }, - { - "name": "Content caching is disabled (macOS)", - "query": "SELECT 1 FROM managed_policies WHERE domain='com.apple.applicationaccess' AND name='allowContentCaching' AND value='0' LIMIT 1;", - "description": "Checks that a mobile device management (MDM) solution configures the Mac to disable content caching.", - "resolution": "Contact your IT administrator to ensure your Mac is receiving a profile that disables content caching.", - "tags": [ - "compliance", - "cis", - "cis2.4.10" - ], - "platform": "darwin", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "content-caching-is-disabled-mac-os", - "requiresMdm": true - }, - { - "name": "Ad tracking is limited (macOS)", - "query": "SELECT 1 FROM managed_policies WHERE domain='com.apple.AdLib' AND name='forceLimitAdTracking' AND value='1' LIMIT 1;", - "description": "Checks that a mobile device management (MDM) solution configures the Mac to limit advertisement tracking.", - "resolution": "Contact your IT administrator to ensure your Mac is receiving a profile that disables advertisement tracking.", - "tags": [ - "compliance", - "cis", - "cis2.5.6" - ], - "platform": "darwin", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "ad-tracking-is-limited-mac-os", - "requiresMdm": true - }, - { - "name": "iCloud Desktop and Document sync is disabled (macOS)", - "query": "SELECT 1 FROM managed_policies WHERE domain='com.apple.icloud.managed' AND name='DisableCloudSync' AND value='1' LIMIT 1;", - "description": "Checks that a mobile device management (MDM) solution configures the Mac to prevent iCloud Desktop and Documents sync.", - "resolution": "Contact your IT administrator to ensure your Mac is receiving a profile to prevent iCloud Desktop and Documents sync.", - "tags": [ - "compliance", - "cis", - "cis2.6.1.4" - ], - "platform": "darwin", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "i-cloud-desktop-and-document-sync-is-disabled-mac-os", - "requiresMdm": true - }, - { - "name": "Firewall logging is enabled (macOS)", - "query": "SELECT 1 FROM managed_policies WHERE domain='com.apple.security.firewall' AND name='EnableLogging' AND value='1' LIMIT 1;", - "description": "Checks that a mobile device management (MDM) solution configures the Mac to log firewall activity.", - "resolution": "Contact your IT administrator to ensure your Mac is receiving a profile that enables firewall logging.", - "tags": [ - "compliance", - "cis", - "cis3.6" - ], - "platform": "darwin", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "firewall-logging-is-enabled-mac-os", - "requiresMdm": true - }, - { - "name": "Guest account disabled (macOS)", - "query": "SELECT 1 FROM managed_policies WHERE domain='com.apple.loginwindow' AND name='DisableGuestAccount' AND value='1' LIMIT 1;", - "description": "Checks that a mobile device management (MDM) solution configures the Mac to prevent the use of a guest account.", - "resolution": "Contact your IT administrator to ensure your Mac is receiving a profile that disables the guest account.", - "tags": [ - "compliance", - "cis", - "cis6.1.3" - ], - "platform": "darwin", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "guest-account-disabled-mac-os", - "requiresMdm": true - }, - { - "name": "Guest access to shared folders is disabled (macOS)", - "query": "SELECT 1 FROM managed_policies WHERE domain='com.apple.AppleFileServer' AND name='guestAccess' AND value='0' LIMIT 1;", - "description": "Checks that a mobile device management (MDM) solution configures the Mac to prevent guest access to shared folders.", - "resolution": "Contact your IT administrator to ensure your Mac is receiving a profile that prevents guest access to shared folders.", - "tags": [ - "compliance", - "cis", - "cis6.1.4" - ], - "platform": "darwin", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "guest-access-to-shared-folders-is-disabled-mac-os", - "requiresMdm": true - }, - { - "name": "No 1Password emergency kit stored in desktop, documents, or downloads folders (macOS)", - "query": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM file WHERE filename LIKE '%Emergency Kit%.pdf' AND (path LIKE '/Users/%/Desktop/%' OR path LIKE '/Users/%/Documents/%' OR path LIKE '/Users/%/Downloads/%' OR path LIKE '/Users/Shared/%'));", - "description": "Looks for PDF files with file names typically used by 1Password for emergency recovery kits. To protect the performance of your devices, the search is one level deep and limited to the Desktop, Documents, Downloads, and Shared folders.", - "resolution": "Delete 1Password emergency kits from your computer, and empty the trash. 1Password emergency kits should only be printed and stored in a physically secure location.", - "platform": "darwin", - "tags": [ - "compliance", - "built-in" - ], - "contributors": [ - { - "name": "nonpunctual", - "handle": "nonpunctual", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/nonpunctual" - } - ], - "kind": "policy", - "slug": "no-1-password-emergency-kit-stored-in-desktop-documents-or-downloads-folders-mac-os", - "requiresMdm": false - }, - { - "name": "Discover TLS certificates", - "platform": "linux, windows, darwin", - "description": "Retrieves metadata about TLS certificates for servers listening on the local machine. Enables mTLS adoption analysis and cert expiration notifications.", - "query": "SELECT * FROM curl_certificate WHERE hostname IN (SELECT DISTINCT 'localhost:'||port FROM listening_ports WHERE protocol=6 AND address!='127.0.0.1' AND address!='::1');", - "purpose": "Informational", - "tags": [ - "network", - "tls" - ], - "contributors": [ - { - "name": "nabilschear", - "handle": "nabilschear", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/nabilschear" - } - ], - "kind": "query", - "slug": "discover-tls-certificates", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Discover Python Packages from Running Python Interpreters", - "platform": "linux, darwin", - "description": "Attempt to discover Python environments (in cwd, path to the python binary, and process command line) from running python interpreters and collect Python packages from those environments.", - "query": "SELECT * FROM python_packages WHERE directory IN (SELECT DISTINCT directory FROM (SELECT SUBSTR(path,0,INSTR(path,'/bin/'))||'/lib' AS directory FROM processes WHERE path LIKE '%/bin/%' AND path LIKE '%python%' UNION SELECT SUBSTR(cmdline,0,INSTR(cmdline,'/bin/'))||'/lib' AS directory FROM processes WHERE cmdline LIKE '%python%' AND cmdline LIKE '%/bin/%' AND path LIKE '%python%' UNION SELECT cwd||'/lib' AS directory FROM processes WHERE path LIKE '%python%'));", - "purpose": "Informational", - "tags": [ - "compliance", - "hunting" - ], - "contributors": [ - { - "name": "nabilschear", - "handle": "nabilschear", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/nabilschear" - } - ], - "kind": "query", - "slug": "discover-python-packages-from-running-python-interpreters", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Identify the default mail, http and ftp applications", - "platforms": "macOS", - "platform": "darwin", - "description": "Lists the currently enabled applications configured to handle mailto, http and ftp schemes.", - "query": "SELECT * FROM app_schemes WHERE (scheme='mailto' OR scheme='http' OR scheme='ftp') AND enabled='1';", - "purpose": "Informational", - "tags": [ - "compliance", - "hunting" - ], - "contributors": [ - { - "name": "brunerd", - "handle": "brunerd", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/brunerd" - } - ], - "kind": "query", - "slug": "identify-the-default-mail-http-and-ftp-applications", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Firewall enabled, domain profile (Windows)", - "query": "SELECT 1 FROM registry WHERE path LIKE 'HKEY_LOCAL_MACHINE\\Software\\Policies\\Microsoft\\WindowsFirewall\\DomainProfile\\EnableFirewall' AND CAST(data as integer) = 1;", - "description": "Checks if a Group Policy configures the computer to enable the domain profile for Windows Firewall. The domain profile applies to networks where the host system can authenticate to a domain controller. Some auditors requires that this setting is configured by a Group Policy.", - "resolution": "Contact your IT administrator to ensure your computer is receiving a Group Policy that enables the domain profile for Windows Firewall.", - "platforms": "Windows", - "tags": [ - "compliance", - "cis", - "cis9.1.1" - ], - "platform": "windows", - "contributors": [ - { - "name": "defensivedepth", - "handle": "defensivedepth", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/defensivedepth" - } - ], - "kind": "policy", - "slug": "firewall-enabled-domain-profile-windows", - "requiresMdm": false - }, - { - "name": "Firewall enabled, private profile (Windows)", - "query": "SELECT 1 FROM registry WHERE path LIKE 'HKEY_LOCAL_MACHINE\\Software\\Policies\\Microsoft\\WindowsFirewall\\PrivateProfile\\EnableFirewall' AND CAST(data as integer) = 1;", - "description": "Checks if a Group Policy configures the computer to enable the private profile for Windows Firewall. The private profile applies to networks where the host system is connected to a private or home network. Some auditors requires that this setting is configured by a Group Policy.", - "resolution": "Contact your IT administrator to ensure your computer is receiving a Group Policy that enables the private profile for Windows Firewall.", - "platforms": "Windows", - "tags": [ - "compliance", - "cis", - "cis9.2.1" - ], - "platform": "windows", - "contributors": [ - { - "name": "defensivedepth", - "handle": "defensivedepth", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/defensivedepth" - } - ], - "kind": "policy", - "slug": "firewall-enabled-private-profile-windows", - "requiresMdm": false - }, - { - "name": "Firewall enabled, public profile (Windows)", - "query": "SELECT 1 FROM registry WHERE path LIKE 'HKEY_LOCAL_MACHINE\\Software\\Policies\\Microsoft\\WindowsFirewall\\PublicProfile\\EnableFirewall' AND CAST(data as integer) = 1;", - "description": "Checks if a Group Policy configures the computer to enable the public profile for Windows Firewall. The public profile applies to networks where the host system is connected to public networks such as Wi-Fi hotspots at coffee shops and airports. Some auditors requires that this setting is configured by a Group Policy.", - "resolution": "Contact your IT administrator to ensure your computer is receiving a Group Policy that enables the public profile for Windows Firewall.", - "platforms": "Windows", - "tags": [ - "compliance", - "cis", - "cis9.3.1" - ], - "platform": "windows", - "contributors": [ - { - "name": "defensivedepth", - "handle": "defensivedepth", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/defensivedepth" - } - ], - "kind": "policy", - "slug": "firewall-enabled-public-profile-windows", - "requiresMdm": false - }, - { - "name": "SMBv1 client driver disabled (Windows)", - "query": "SELECT 1 FROM windows_optional_features WHERE name = 'SMB1Protocol-Client' AND state != 1;", - "description": "Checks that the SMBv1 client is disabled.", - "resolution": "Contact your IT administrator to discuss disabling SMBv1 on your system.", - "platforms": "Windows", - "tags": [ - "compliance", - "cis", - "cis18.3.2", - "built-in" - ], - "platform": "windows", - "contributors": [ - { - "name": "defensivedepth", - "handle": "defensivedepth", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/defensivedepth" - } - ], - "kind": "policy", - "slug": "sm-bv-1-client-driver-disabled-windows", - "requiresMdm": false - }, - { - "name": "SMBv1 server disabled (Windows)", - "query": "SELECT 1 FROM windows_optional_features WHERE name = 'SMB1Protocol-Server' AND state != 1", - "description": "Checks that the SMBv1 server is disabled.", - "resolution": "Contact your IT administrator to discuss disabling SMBv1 on your system.", - "platforms": "Windows", - "tags": [ - "compliance", - "cis", - "cis18.3.3", - "built-in" - ], - "platform": "windows", - "contributors": [ - { - "name": "defensivedepth", - "handle": "defensivedepth", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/defensivedepth" - } - ], - "kind": "policy", - "slug": "sm-bv-1-server-disabled-windows", - "requiresMdm": false - }, - { - "name": "Link-Local Multicast Name Resolution (LLMNR) disabled (Windows)", - "query": "SELECT 1 FROM registry WHERE path LIKE 'HKEY_LOCAL_MACHINE\\SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient\\EnableMulticast' AND CAST(data as integer) = 0;", - "description": "Checks if a Group Policy configures the computer to disable LLMNR. Disabling LLMNR can prevent malicious actors from gaining access to the computer's credentials. Some auditors require that this setting is configured by a Group Policy.", - "resolution": "Contact your IT administrator to ensure your computer is receiving a Group Policy that disables LLMNR on your system.", - "platforms": "Windows", - "tags": [ - "compliance", - "cis", - "cis18.5.4.2" - ], - "platform": "windows", - "contributors": [ - { - "name": "defensivedepth", - "handle": "defensivedepth", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/defensivedepth" - } - ], - "kind": "policy", - "slug": "link-local-multicast-name-resolution-llmnr-disabled-windows", - "requiresMdm": false - }, - { - "name": "Automatic updates enabled (Windows)", - "query": "SELECT 1 FROM registry WHERE path LIKE 'HKEY_LOCAL_MACHINE\\Software\\Policies\\Microsoft\\Windows\\WindowsUpdate\\AU\\NoAutoUpdate' AND CAST(data as integer) = 0;", - "description": "Checks if a Group Policy configures the computer to enable Automatic Updates. When enabled, the computer downloads and installs security and other important updates automatically. Some auditors require that this setting is configured by a Group Policy.", - "resolution": "Contact your IT administrator to ensure your computer is receiving a Group policy that enables Automatic Updates.", - "platforms": "Windows", - "tags": [ - "compliance", - "cis", - "cis18.9.108.2.1" - ], - "platform": "windows", - "contributors": [ - { - "name": "defensivedepth", - "handle": "defensivedepth", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/defensivedepth" - } - ], - "kind": "policy", - "slug": "automatic-updates-enabled-windows", - "requiresMdm": false - }, - { - "name": "Identify Apple development secrets (macOS)", - "query": "SELECT * FROM keychain_items WHERE label LIKE '%ABCDEFG%';", - "description": "Identifies certificates associated with Apple development signing and notarization. Replace ABCDEFG with your company's identifier.", - "resolution": "Ensure your official Apple builds, signing and notarization happen on a centralized system, and remove these certificates from workstations.", - "tags": [ - "compliance", - "inventory", - "built-in" - ], - "platform": "darwin", - "contributors": [ - { - "name": "GuillaumeRoss", - "handle": "GuillaumeRoss", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/GuillaumeRoss" - } - ], - "kind": "policy", - "slug": "identify-apple-development-secrets-mac-os", - "requiresMdm": false - }, - { - "name": "Geolocate via ipapi.co", - "platform": "darwin, linux, windows", - "description": "Geolocate a host using the [ipapi.co](https://ipapi.co) in an emergency. Requires the curl table. [Learn more](https://fleetdm.com/guides/locate-assets-with-osquery).", - "query": "SELECT JSON_EXTRACT(result, '$.ip') AS ip, JSON_EXTRACT(result, '$.city') AS city, JSON_EXTRACT(result, '$.region') AS region, JSON_EXTRACT(result, '$.country') AS country, JSON_EXTRACT(result, '$.latitude') AS latitude, JSON_EXTRACT(result, '$.longitude') AS longitude FROM curl WHERE url = 'http://ipapi.co/json';", - "purpose": "inventory", - "tags": [ - "inventory" - ], - "contributors": [ - { - "name": "zwass", - "handle": "zwass", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/zwass" - } - ], - "kind": "query", - "slug": "geolocate-via-ipapi-co", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get Crowdstrike Falcon network content filter status", - "platform": "darwin", - "description": "Get the status of the Crowdstrike Falcon network content filter (as in \"System Settings\" > \"Network > \"Filters\").", - "query": "/* Load up the plist */ WITH extensions_plist AS (SELECT *, rowid FROM plist WHERE path = '/Library/Preferences/com.apple.networkextension.plist') /* Find the first \"Enabled\" key after the key indicating the crowdstrike app */ SELECT value AS enabled FROM extensions_plist WHERE subkey = 'Enabled' AND rowid > (SELECT rowid FROM extensions_plist WHERE value = 'com.crowdstrike.falcon.App') LIMIT 1;", - "purpose": "Informational", - "tags": [ - "crowdstrike", - "plist", - "network", - "content filter" - ], - "contributors": [ - { - "name": "zwass", - "handle": "zwass", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/zwass" - } - ], - "kind": "query", - "slug": "get-crowdstrike-falcon-network-content-filter-status", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "Get a list of Visual Studio Code extensions", - "platform": "darwin, linux, windows", - "description": "Get a list of installed VS Code extensions (requires osquery > 5.11.0).", - "query": "SELECT u.username, vs.* FROM users u CROSS JOIN vscode_extensions vs USING (uid);\n", - "purpose": "Informational", - "tags": [ - "inventory" - ], - "contributors": [ - { - "name": "lucasmrod", - "handle": "lucasmrod", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/lucasmrod" - }, - { - "name": "sharon-fdm", - "handle": "sharon-fdm", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/sharon-fdm" - }, - { - "name": "zwass", - "handle": "zwass", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/zwass" - } - ], - "kind": "query", - "slug": "get-a-list-of-visual-studio-code-extensions", - "resolution": "N/A", - "requiresMdm": false - }, - { - "name": "List osquery table names", - "platform": "darwin, linux, windows", - "description": "List all table names in the schema of the currently installed version of osquery", - "query": "SELECT DISTINCT name FROM osquery_registry;", - "purpose": "Informational", - "tags": [ - "fleet", - "osquery", - "table", - "schema" - ], - "contributors": [ - { - "name": "nonpunctual", - "handle": "nonpunctual", - "avatarUrl": "https://placekitten.com/200/200", - "htmlUrl": "https://github.com/nonpunctual" - } - ], - "kind": "query", - "slug": "list-osquery-table-names", - "resolution": "N/A", - "requiresMdm": false - } - ], - "queryLibraryYmlRepoPath": "docs/01-Using-Fleet/standard-query-library/standard-query-library.yml", - "pricingTable": [ - { - "industryName": "Managed cloud", - "description": "Have Fleet host it for you (currently only available for customers with 700+ hosts. PS. Wish we could host for you? We're working on it! Please let us know if you know of a good partner. In the meantime, join fleetdm.com/support and we're happy to help you deploy Fleet yourself.)", - "pricingTableCategories": [ - "Deployment" - ], - "productCategories": [ - "Endpoint operations", - "Device management", - "Vulnerability management" - ], - "tier": "Premium", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "name": "Managed cloud" - }, - { - "industryName": "Self-hosted", - "friendlyName": "Host it yourself", - "description": "Deploy Fleet anywhere and host it yourself, even in air-gapped environments except where technologically impossible.", - "pricingTableCategories": [ - "Deployment" - ], - "documentationUrl": "https://fleetdm.com/docs/deploy/introduction", - "productCategories": [ - "Endpoint operations", - "Device management", - "Vulnerability management" - ], - "tier": "Free", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "no", - "buzzwords": [ - "Self-hosted" - ], - "name": "Self-hosted" - }, - { - "industryName": "Multi-tenancy", - "description": "For managed service providers to use a single instance of Fleet for multiple customers.", - "documentationUrl": "https://github.com/fleetdm/fleet/issues/9956", - "productCategories": [ - "Device management" - ], - "pricingTableCategories": [ - "Deployment" - ], - "usualDepartment": "IT", - "buzzwords": [ - "OEM", - "Private label", - "House brand", - "Clear label", - "Multi-tenancy" - ], - "tier": "Premium", - "name": "Multi-tenancy" - }, - { - "industryName": "Deployment tools", - "description": "Pre-built Terraform modules and Helm charts to help you get up and running.", - "documentationUrl": "https://fleetdm.com/docs/deploy/introduction", - "usualDepartment": "IT", - "tier": "Free", - "jamfProHasFeature": "no", - "jamfProtectHasFeature": "no", - "productCategories": [ - "Endpoint operations" - ], - "pricingTableCategories": [ - "Deployment" - ], - "name": "Deployment tools" - }, - { - "industryName": "Private update registry", - "friendlyName": "Update agents from a secret URL", - "description": "Load agent code from a secret URL that you manage.", - "documentationUrl": "https://fleetdm.com/docs/using-fleet/update-agents", - "tier": "Premium", - "jamfProHasFeature": "no", - "jamfProtectHasFeature": "no", - "productCategories": [ - "Endpoint operations" - ], - "pricingTableCategories": [ - "Configuration" - ], - "usualDepartment": "Security", - "name": "Private update registry" - }, - { - "industryName": "Control agent versions", - "description": "Manage agents remotely by setting different versions per-baseline.", - "documentationUrl": "https://fleetdm.com/docs/configuration/agent-configuration#configure-fleetd-update-channels", - "tier": "Premium", - "jamfProHasFeature": "no", - "jamfProtectHasFeature": "no", - "productCategories": [ - "Endpoint operations" - ], - "pricingTableCategories": [ - "Configuration" - ], - "usualDepartment": "IT", - "waysToUse": [ - { - "description": "Supply-chain Levels for Software Artifacts (SLSA) attestations for the fleetd binary artifacts and server container image to enable verification that the binaries are built and uploaded using GitHub Actions from the Fleet repository at a particular commit SHA coming soon (2024-12-31)." - }, - { - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/20219" - } - ], - "name": "Control agent versions" - }, - { - "industryName": "Command line tool (CLI)", - "friendlyName": "fleetctl", - "documentationUrl": "https://fleetdm.com/docs/using-fleet/fleetctl-cli", - "productCategories": [ - "Endpoint operations", - "Device management" - ], - "pricingTableCategories": [ - "Configuration" - ], - "usualDepartment": "IT", - "tier": "Free", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "name": "Command line tool (CLI)" - }, - { - "industryName": "GitOps", - "friendlyName": "Manage endpoints in git", - "documentationUrl": "https://github.com/fleetdm/fleet-gitops", - "description": "Fork the best practices GitHub repo and use the included GitHub Actions to quickly automate Fleet console and configuration workflow management.", - "productCategories": [ - "Endpoint operations", - "Device management", - "Vulnerability management" - ], - "pricingTableCategories": [ - "Configuration" - ], - "usualDepartment": "IT", - "tier": "Free", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "demos": { - "description": "A top savings and investment company wanted workflows and automation so that one bad actor can't brick their fleet. This way, they have to make a pull request first.", - "quote": "I don't want one bad actor to brick my fleet. I want them to make a pull request first.", - "moreInfoUrl": "https://docs.google.com/document/d/1hAQL6P--Tt3syq1MTRONAxhQA_2Vjt3oOJJt_O4xbiE/edit?disco=AAABAVnYvns&usp_dm=true#heading=h.7en766pueek4" - }, - "name": "GitOps" - }, - { - "industryName": "Two-factor authentication", - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/5478", - "productCategories": [ - "Endpoint operations", - "Device management", - "Vulnerability management" - ], - "pricingTableCategories": [ - "Configuration" - ], - "usualDepartment": "IT", - "tier": "Free", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "waysToUse": [ - { - "description": "Enforce two-factor authentication when logging in to Fleet for added security." - } - ], - "comingSoonOn": "2024-12-31", - "name": "Two-factor authentication", - "comingSoon": true - }, - { - "industryName": "Role-based access control", - "documentationUrl": "https://fleetdm.com/docs/using-fleet/manage-access#manage-access", - "productCategories": [ - "Endpoint operations", - "Device management", - "Vulnerability management" - ], - "pricingTableCategories": [ - "Configuration" - ], - "usualDepartment": "IT", - "tier": "Premium", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "name": "Role-based access control" - }, - { - "industryName": "Audit logging", - "description": "Log all activity, including queries, scripts, access, etc.", - "documentationUrl": "https://fleetdm.com/docs/rest-api/rest-api#list-activities", - "productCategories": [ - "Endpoint operations", - "Device management" - ], - "pricingTableCategories": [ - "Configuration" - ], - "tier": "Premium", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "usualDepartment": "Security", - "waysToUse": [ - { - "description": "Export activity of Fleet admins to your SIEM or data lake" - } - ], - "name": "Audit logging" - }, - { - "industryName": "Scope transparency", - "description": "Let end users see the source code for exactly how they are being monitored, and set clear expectations about what is and isn’t acceptable use of work computers.", - "tier": "Free", - "documentationUrl": "https://fleetdm.com/transparency", - "productCategories": [ - "Endpoint operations" - ], - "pricingTableCategories": [ - "Configuration" - ], - "name": "Scope transparency" - }, - { - "industryName": "Cross-platform MDM support", - "description": "macOS, Windows, and Linux.", - "documentationUrl": "https://fleetdm.com/announcements/fleet-introduces-windows-mdm", - "tier": "Premium", - "jamfProHasFeature": "appleOnly", - "jamfProtectHasFeature": "no", - "usualDepartment": "IT", - "productCategories": [ - "Device management" - ], - "pricingTableCategories": [ - "Devices" - ], - "name": "Cross-platform MDM support" - }, - { - "industryName": "MDM migration", - "description": "Easily move your devices from your current MDM solution to Fleet.", - "tier": "Premium", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "no", - "documentationUrl": "https://fleetdm.com/docs/using-fleet/mdm-migration-guide", - "usualDepartment": "IT", - "productCategories": [ - "Device management" - ], - "pricingTableCategories": [ - "Devices" - ], - "name": "MDM migration" - }, - { - "industryName": "Zero-touch setup", - "description": "Zero-touch setup for macOS, iOS/iPadOS, and Windows.", - "documentationUrl": "https://fleetdm.com/docs/using-fleet/mdm-macos-setup-experience", - "tier": "Premium", - "jamfProHasFeature": "appleOnly", - "jamfProtectHasFeature": "no", - "usualDepartment": "IT", - "productCategories": [ - "Device management" - ], - "pricingTableCategories": [ - "Devices" - ], - "waysToUse": [ - { - "description": "Ship a macOS, iOS, or iPadOS device to the end user's home and have them automatically enroll to Fleet during out-of-the-box setup." - }, - { - "description": "Ship a Windows workstation to the end user's home and have them automatically enroll to Fleet during out-of-the-box setup." - }, - { - "description": "Customize the out-of-the-box setup experience for your end users." - }, - { - "description": "Install a bootstrap package to run custom scripts during the setup experience. Store the bootstrap package outside the Fleet database coming soon (2024-09-15)", - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/19037" - }, - { - "description": "Require end users to authenticate with your identity provider (IdP) and agree to an end user license agreement (EULA) before they can use their new workstation" - } - ], - "name": "Zero-touch setup" - }, - { - "industryName": "Bring your own device (BYOD) enrollment", - "description": "BYOD enrollment for macOS, iOS/iPadOS (coming soon), Windows, and Android (coming soon) devices.", - "documentationUrl": "https://fleetdm.com/guides/sysadmin-diaries-device-enrollment#byod-enrollment", - "tier": "Free", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "no", - "usualDepartment": "IT", - "productCategories": [ - "Device management" - ], - "pricingTableCategories": [ - "Devices" - ], - "waysToUse": [ - { - "description": "Support ACME as a protocol for MDM certificate generation. Coming soon (2024-12-31)", - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/15611" - } - ], - "name": "Bring your own device (BYOD) enrollment" - }, - { - "industryName": "User account sync", - "description": "Sync user accounts via Okta, AD, or any IDP.", - "documentationUrl": "https://fleetdm.com/docs/using-fleet/mdm-macos-setup-experience", - "productCategories": [ - "Endpoint operations", - "Device management", - "Vulnerability management" - ], - "pricingTableCategories": [ - "Devices" - ], - "usualDepartment": "IT", - "tier": "Premium", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "waysToUse": [ - { - "description": "Automatically set admin access to Fleet based on your IDP" - } - ], - "name": "User account sync" - }, - { - "industryName": "Human-endpoint mapping", - "friendlyName": "See who logs in on every computer", - "description": "Identify who logs in to any system, including login history and current sessions. Look up any host by the email address of the person using it.", - "documentationUrl": "https://fleetdm.com/docs/rest-api/rest-api#get-hosts-google-chrome-profiles", - "screenshotSrc": null, - "tier": "Free", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "productCategories": [ - "Endpoint operations" - ], - "pricingTableCategories": [ - "Devices" - ], - "usualDepartment": "IT", - "buzzwords": [ - "Device users", - "human-to-device mapping" - ], - "dri": "mikermcneil", - "demos": [ - { - "description": "Security engineers at a top gaming company wanted to get demographics off their macOS, Windows, and Linux machines about who the user is and who's logged in.", - "moreInfoUrl": "https://docs.google.com/document/d/1qFYtMoKh3zyERLhbErJOEOo2me6Bc7KOOkjKn482Sqc/edit" - }, - { - "description": "Data engineers at a top biotech corporation needed to know who is logged into their devices.", - "quote": "So we don't know exactly what's going on after we deploy the device, we know that they are compliant with the security because we are running these stuff, but we don't know certainly who is running, who is logging in the device?", - "moreInfoUrl": "https://docs.google.com/document/d/17MNI5ykzlFjdVmQ8SPMrT1oR_hY_vkYAJx31F7l7Pv8/edit#heading=h.7en766pueek4" - } - ], - "waysToUse": [ - { - "description": "Look up computer by ActiveDirectory account" - }, - { - "description": "Find device by Google Chrome user" - }, - { - "description": "Identify who logs in to any system, including login history and current sessions." - }, - { - "description": "Look up any host by the email address of the person using it." - }, - { - "description": "Check user login history", - "moreInfoUrl": "https://www.lepide.com/how-to/audit-who-logged-into-a-computer-and-when.html#:~:text=To%20find%20out%20the%20details,logs%20in%20%E2%80%9CWindows%20Logs%E2%80%9D." - }, - { - "description": "See currently logged in users", - "moreInfoUrl": "https://www.top-password.com/blog/see-currently-logged-in-users-in-windows/" - }, - { - "description": "Get demographics off of our machines about who the user is and who's logged in", - "moreInfoUrl": "https://docs.google.com/document/d/1qFYtMoKh3zyERLhbErJOEOo2me6Bc7KOOkjKn482Sqc/edit" - }, - { - "description": "See what servers someone is logged-in on", - "moreInfoUrl": "https://community.spiceworks.com/topic/138171-is-there-a-way-to-see-what-servers-someone-is-logged-in-on" - } - ], - "name": "Human-endpoint mapping" - }, - { - "industryName": "Device inventory", - "description": "Includes a list of all devices and all hardware and software attributes for each device.", - "documentationUrl": "https://fleetdm.com/docs/using-fleet/understanding-host-vitals", - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/14415", - "tier": "Free", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "usualDepartment": "IT", - "productCategories": [ - "Endpoint operations", - "Device management", - "Vulnerability management" - ], - "pricingTableCategories": [ - "Devices" - ], - "waysToUse": [ - { - "description": "Implement software inventory recommendations from the SANS 20 / CIS 18.", - "moreInfoUrl": "https://docs.google.com/document/d/1E6EQMMqrsRc6Z3YsR6Q33OaF9eAa8zLNaz4K2YzFdyo/edit#heading=h.7en766pueek4" - }, - { - "description": "View a list of all hardware attributes of a device.", - "moreInfoUrl": "https://fleetdm.com/tables/system_info" - }, - { - "description": "View a list of all software and their versions installed on all your hosts.", - "moreInfoUrl": "https://fleetdm.com/docs/get-started/anatomy#software-library" - }, - { - "description": "View a list of software rolled up by title.", - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/14674" - }, - { - "description": "Implement hardware and infrastructure inventory recommendations from the SANS 20 / CIS 18.", - "moreInfoUrl": "https://docs.google.com/document/d/1E6EQMMqrsRc6Z3YsR6Q33OaF9eAa8zLNaz4K2YzFdyo/edit#heading=h.7en766pueek4" - } - ], - "name": "Device inventory" - }, - { - "industryName": "Search inventory", - "description": "Search devices by IP, serial, hostname, and UUID.", - "documentationUrl": "https://fleetdm.com/docs/using-fleet/learn-how-to-use-fleet#how-to-ask-questions-about-your-device", - "productCategories": [ - "Endpoint operations", - "Device management" - ], - "pricingTableCategories": [ - "Devices" - ], - "usualDepartment": "IT", - "tier": "Free", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "name": "Search inventory" - }, - { - "industryName": "Targeted device scoping", - "description": "Organize devices with Teams and Labels.", - "documentationUrl": "https://fleetdm.com/guides/managing-labels-in-fleet", - "tier": "Premium", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "usualDepartment": "IT", - "productCategories": [ - "Device management" - ], - "pricingTableCategories": [ - "Devices" - ], - "name": "Targeted device scoping" - }, - { - "industryName": "Enforce disk encryption", - "description": "Encrypt system drives on macOS and Windows computers, manage escrowed encryption keys, and report on disk encryption status (FileVault, BitLocker).", - "documentationUrl": "https://fleetdm.com/docs/using-fleet/mdm-disk-encryption", - "friendlyName": "Ensure hard disks are encrypted", - "productCategories": [ - "Device management" - ], - "pricingTableCategories": [ - "Devices" - ], - "usualDepartment": "Security", - "tier": "Premium", - "jamfProHasFeature": "appleOnly", - "jamfProtectHasFeature": "no", - "waysToUse": [ - { - "description": "Report on disk encryption status" - }, - { - "description": "Encrypt hard disks on macOS with FileVault" - }, - { - "description": "Escrow FileVault keys on macOS" - }, - { - "description": "Encrypt hard disks on Windows with BitLocker." - } - ], - "name": "Enforce disk encryption" - }, - { - "industryName": "Enforce operating system (OS) updates", - "description": "Keep operating systems up to date for macOS, iOS/iPadOS, Windows, and Android (coming soon) devices.", - "documentationUrl": "https://fleetdm.com/docs/using-fleet/mdm-macos-updates", - "tier": "Premium", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "no", - "usualDepartment": "IT", - "productCategories": [ - "Device management", - "Vulnerability management" - ], - "pricingTableCategories": [ - "Devices" - ], - "waysToUse": [ - { - "description": "Enforce macOS updates via Nudge." - }, - { - "description": "Progressively enhance from Nudge to DDM-based OS updates.", - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/17295" - }, - { - "description": "Automatically update Windows after the end user reaches a deadline." - } - ], - "name": "Enforce operating system (OS) updates" - }, - { - "industryName": "Enforce OS settings", - "description": "MDM support for macOS, iOS/iPadOS, Windows, and Android (coming soon) devices. Management support for Linux.", - "documentationUrl": "https://fleetdm.com/docs/using-fleet/mdm-custom-os-settings", - "usualDepartment": "IT", - "tier": "Free", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "no", - "waysToUse": [ - { - "description": "Deploy configuration profiles on macOS and Windows and verify that they're installed.", - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/13281" - }, - { - "description": "Deploy custom declaration (DDM) profiles on macOS.", - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/14550" - }, - { - "description": "Target profiles to specific hosts using SQL.", - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/17315" - }, - { - "description": "Automatically re-deploy configuration profiles when they're not installed." - }, - { - "description": "Deploy configuration profiles on iOS/iPadOS." - }, - { - "description": "See a list of the upcoming MDM commands and scripts in unified queue. Coming soon (2024-07-15)", - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/15920" - }, - { - "description": "Send MDM commands to tell end users to update their OS.", - "moreInfoUrl": "https://developer.apple.com/documentation/devicemanagement/schedule_an_os_update" - }, - { - "description": "Configure agent options remotely, over the air. (Includes osquery config, and osquery startup flags.).", - "moreInfoUrl": "https://fleetdm.com/docs/configuration/agent-configuration" - } - ], - "productCategories": [ - "Device management" - ], - "pricingTableCategories": [ - "Devices" - ], - "name": "Enforce OS settings" - }, - { - "industryName": "Declarative Device Management (DDM) support for configuration profiles", - "description": "Full support for Apple DDM configuration profiles.", - "documentationUrl": "https://fleetdm.com/docs/using-fleet/mdm-os-updates#macos", - "tier": "Free", - "jamfProHasFeature": "cloudOnly", - "jamfProtectHasFeature": "cloudOnly", - "usualDepartment": "IT", - "productCategories": [ - "Device management" - ], - "pricingTableCategories": [ - "Devices" - ], - "name": "Declarative Device Management (DDM) support for configuration profiles" - }, - { - "industryName": "Device health", - "friendlyName": "Automate device health", - "description": "Automatically report system health issues using webhooks or integrations, to notify or quarantine outdated or misconfigured systems that are at higher risk of vulnerabilities or theft.", - "documentationUrl": "https://fleetdm.com/docs/using-fleet/automations#automations", - "screenshotSrc": null, - "tier": "Free", - "jamfProHasFeature": "no", - "jamfProtectHasFeature": "yes", - "productCategories": [ - "Device management", - "Endpoint operations" - ], - "pricingTableCategories": [ - "Devices" - ], - "usualDepartment": "IT", - "dri": "mikermcneil", - "demos": [ - { - "description": "A large tech company used the Fleet API to block access to corporate apps for outdated operating system versions with certain \"celebrity\" vulnerabilities.", - "quote": null, - "moreInfoUrl": "https://play.goconsensus.com/s4e490bb9" - } - ], - "buzzwords": [ - "Device trust", - "Zero trust", - "Layer 7 device trust", - "Beyondcorp", - "Device attestation", - "Conditional access" - ], - "waysToUse": [ - { - "description": "Automatically manage the behavior of endpoints that are at higher risk of vulnerabilities or data loss due to their configuration or patch level." - }, - { - "description": "Block access to corporate apps for users whose devices with unexpected settings, like disabled screen lock, passwords that are too short, unencrypted hard disks, and more" - }, - { - "description": "Quickly implement conditional access based on device health using osquery and a simple device health REST API.", - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/14920" - }, - { - "description": "Control and restore access to applications by automatically restricting access when devices do not meet particular security requirements.", - "moreInfoUrl": "https://duo.com/docs/device-health" - }, - { - "description": "Control which laptop and desktop devices can access corporate apps and websites based on what vulnerabilities it might be exposed to based on how the device is configured, whether it's up to date, its MDM enrollment status, and anything else you can build in a SQL query of Fleet's 300 data tables representing information about enrolled host systems. Coming soon (2024-09-30).", - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/16236" - }, - { - "description": "Implement multivariate device trust", - "moreInfoUrl": "https://youtu.be/5sFOdpMLXQg?feature=shared&t=1445" - }, - { - "description": "Implement your own version of Google's zero trust model (BeyondCorp)", - "moreInfoUrl": "https://cloud.google.com/beyondcorp" - }, - { - "description": "Get endpoint data into ServiceNow and make your asset management teams happy", - "moreInfoUrl": "https://www.youtube.com/watch?v=aVbU6_9JoM0" - }, - { - "description": "Monitor devices that don't meet your organization's custom security policies" - }, - { - "description": "Quickly report your posture and vulnerabilities to auditors, showing remediation status and timing." - }, - { - "description": "Keep your devices compliant with customizable baselines, or use common benchmarks like CIS." - }, - { - "description": "Discover security misconfigurations that increase attack surface." - }, - { - "description": "Detect suspcious services listening on open ports that should not be connected to the internet, such as Remote Desktop Protocol (RDP).", - "moreInfoUrl": "https://paraflare.com/articles/vulnerability-management-via-osquery/#:~:text=WHERE%20statename%20%3D%20%E2%80%9CEnabled%E2%80%9D-,OPEN%20SOCKETS,-Lastly%2C%20an%20examination" - }, - { - "description": "Discover potentially unwanted programs that increase attack surface.", - "moreInfoUrl": "https://paraflare.com/articles/vulnerability-management-via-osquery/" - }, - { - "description": "Detect self-signed certifcates" - }, - { - "description": "Detect legacy protocols with safer versions", - "moreInfoUrl": "https://paraflare.com/articles/vulnerability-management-via-osquery/#:~:text=WHERE%20self_signed%20%3D%201%3B-,LEGACY%20PROTOCOLS,-This%20section%20will" - }, - { - "description": "Detect exposed secrets on the command line", - "moreInfoUrl": "https://paraflare.com/articles/vulnerability-management-via-osquery/#:~:text=WDigest%20is%20disabled.-,EXPOSED%20SECRETS,-Often%2C%20to%20create" - }, - { - "description": "Detect and surface issues with devices" - }, - { - "description": "Share device health reports" - }, - { - "description": "Align endpoints with your security policies", - "moreInfoUrl": "https://www.axonius.com/use-cases/cmdb-reconciliation" - }, - { - "description": "Maximize security control coverage" - }, - { - "description": "Uncover gaps in security policies, configurations, and hygiene", - "moreInfoUrl": "https://www.axonius.com/use-cases/coverage-gap-discovery" - }, - { - "description": "Automatically apply security policies to protect endpoints against attack." - }, - { - "description": "Surface security issues in all your deployed endpoints even data centers and factories." - }, - { - "description": "Continually validate controls and policies" - }, - { - "description": "Block access to corporate apps if your end users are failing a specific number of critical policies.", - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/16206" - } - ], - "name": "Device health" - }, - { - "industryName": "Application deployment", - "description": "Deploy applications and security agents on macOS, iOS/iPadOS, Linux, Windows, and Android (coming soon) devices. Additionally, install macOS and iOS/iPadOS apps from the App Store (coming soon).", - "tier": "Premium", - "jamfProHasFeature": "appleOnly", - "jamfProtectHasFeature": "no", - "isExperimental": "yes", - "usualDepartment": "IT", - "productCategories": [ - "Device management" - ], - "pricingTableCategories": [ - "Devices" - ], - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/18867", - "waysToUse": [ - { - "description": "Easily configure and install SentinelOne, Crowdstrike, and other security tools.", - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/14921" - }, - { - "description": "Offer licenses for Photoshop and other App Sore apps for your end users." - }, - { - "description": "iOS/iPadOS coming soon (2024-08-11).", - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/14899" - } - ], - "name": "Application deployment" - }, - { - "industryName": "Self-service application installation", - "description": "Allow end users to install apps through Fleet Desktop for macOS, Linux, and Windows.", - "tier": "Premium", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "no", - "isExperimental": "yes", - "usualDepartment": "IT", - "productCategories": [ - "Device management" - ], - "pricingTableCategories": [ - "Devices" - ], - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/17587", - "waysToUse": [ - { - "description": "Build scripts for Ansible deployments", - "moreInfoUrl": "https://www.youtube.com/watch?v=qflUfLQCnwY&list=PL6-FgoWOoK2YUR4ADGsxTSL3onb-GzCnM&index=4" - }, - { - "description": "Deploy osquery to macOS via Jamf", - "moreInfoUrl": "https://www.youtube.com/watch?v=qflUfLQCnwY&list=PL6-FgoWOoK2YUR4ADGsxTSL3onb-GzCnM&index=4" - }, - { - "description": "Package osquery for Linux servers via Workspace One and Windows servers via group policies", - "moreInfoUrl": "https://www.youtube.com/watch?v=qflUfLQCnwY&list=PL6-FgoWOoK2YUR4ADGsxTSL3onb-GzCnM&index=4" - } - ], - "name": "Self-service application installation" - }, - { - "industryName": "Application management", - "description": "Manage updates and patches for apps on macOS, Windows, and Linux computers.", - "tier": "Premium", - "jamfProHasFeature": "appleOnly", - "jamfProtectHasFeature": "no", - "comingSoonOn": "2024-08-25", - "usualDepartment": "IT", - "productCategories": [ - "Device management" - ], - "pricingTableCategories": [ - "Devices" - ], - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/18865", - "name": "Application management", - "comingSoon": true - }, - { - "industryName": "Script execution", - "friendlyName": "Safely execute custom scripts (macOS, Windows, and Linux)", - "description": "Deploy and execute custom scripts using a REST API, and manage your library of scripts in the UI or a git repo.", - "documentationUrl": "https://fleetdm.com/docs/using-fleet/scripts", - "tier": "Free", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "no", - "dri": "mikermcneil", - "usualDepartment": "IT", - "productCategories": [ - "Endpoint operations", - "Device management" - ], - "pricingTableCategories": [ - "Devices" - ], - "demos": [ - { - "description": "A large tech company used scripts to fix issues with their security and compliance agents on workstations." - } - ], - "buzzwords": [ - "Remote script execution", - "PowerShell scripts", - "Bash scripts" - ], - "waysToUse": [ - { - "description": "Execute custom macOS scripts (client platform engineering)", - "moreInfoUrl": "https://www.hexnode.com/blogs/executing-custom-mac-scripts-via-mdm/" - }, - { - "description": "Execute custom Windows scripts (client platform engineering)", - "moreInfoUrl": "https://www.hexnode.com/blogs/executing-custom-windows-scripts-via-mdm/" - }, - { - "description": "Use PowerShell scripts on Windows devices", - "moreInfoUrl": "https://learn.microsoft.com/en-us/mem/intune/apps/intune-management-extension" - }, - { - "description": "Run PowerShell scripts for remediations (security engineering)", - "moreInfoUrl": "https://learn.microsoft.com/en-us/mem/intune/fundamentals/powershell-scripts-remediation" - }, - { - "description": "Download and run remediation scripts", - "moreInfoUrl": "https://help.zscaler.com/deception/downloading-and-running-remediation-script" - }, - { - "description": "Deploy custom scripts", - "moreInfoUrl": "https://scalefusion.com/custom-scripting" - }, - { - "description": "Run scripts on online/offline hosts", - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/15529" - }, - { - "description": "Only maintainers and admins can run scripts.", - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/19055" - } - ], - "name": "Script execution" - }, - { - "industryName": "Device remediation", - "description": "Use Fleet Policies to detect the device state. Automate remediations for issues or allow users to remediate problems in self-service.", - "documentationUrl": "https://fleetdm.com/securing/end-user-self-remediation", - "tier": "Premium", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "no", - "usualDepartment": "IT", - "productCategories": [ - "Device management", - "Vulnerability management" - ], - "pricingTableCategories": [ - "Devices" - ], - "waysToUse": [ - { - "description": "Send software vulnerability emails to end users to encourage self-remediation." - } - ], - "name": "Device remediation" - }, - { - "industryName": "Maintenance windows", - "friendlyName": "Fleet in your calendar", - "description": "Create a calendar event to auto-remediate failing policies when your end users are free.", - "documentationUrl": "https://github.com/fleetdm/fleet/issues/17230", - "tier": "Premium", - "jamfProHasFeature": "no", - "jamfProtectHasFeature": "no", - "isExperimental": "yes", - "productCategories": [ - "Device management", - "Endpoint operations" - ], - "pricingTableCategories": [ - "Devices" - ], - "name": "Maintenance windows" - }, - { - "industryName": "Send lock and wipe commands", - "description": "Secure your devices with remote lock and wipe commands if lost or stolen.", - "documentationUrl": "https://fleetdm.com/docs/using-fleet/mdm-commands", - "waysToUse": [ - { - "description": "High-level remote lock for macOS, Windows, and Linux.", - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/9949" - }, - { - "description": "High-level remote wipe for macOS, Windows, and Linux.", - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/9951" - } - ], - "tier": "Premium", - "jamfProHasFeature": "appleOnly", - "jamfProtectHasFeature": "no", - "usualDepartment": "IT", - "productCategories": [ - "Device management" - ], - "pricingTableCategories": [ - "Devices" - ], - "name": "Send lock and wipe commands" - }, - { - "industryName": "Queries", - "description": "Scheduled or saved queries with optional AI-generated descriptions, and, live queries for real-time data collection.", - "documentationUrl": "https://fleetdm.com/docs/using-fleet/fleet-ui", - "tier": "Free", - "jamfProHasFeature": "no", - "jamfProtectHasFeature": "no", - "productCategories": [ - "Endpoint operations", - "Device management", - "Vulnerability management" - ], - "pricingTableCategories": [ - "Devices" - ], - "usualDepartment": "IT", - "demos": [ - { - "description": "A top financial services company needed to set up rolling deployments for changes to osquery agents running on their production servers.", - "moreInfoUrl": "https://docs.google.com/document/d/1UdzZMyBLbs9SUXfSXN2x2wZQCbjZZUetYlNWH6-ryqQ/edit#heading=h.2lh6ehprpvl6" - } - ], - "name": "Queries" - }, - { - "industryName": "Query performance monitoring", - "documentationUrl": "https://fleetdm.com/docs/get-started/faq#will-fleet-slow-down-my-servers-what-about-my-employee-laptops", - "tier": "Free", - "jamfProHasFeature": "no", - "jamfProtectHasFeature": "no", - "productCategories": [ - "Endpoint operations" - ], - "pricingTableCategories": [ - "Devices" - ], - "demos": [ - { - "description": "A top software company needed to understand the performance impact of osquery queries before running them on all of their production Linux servers.", - "moreInfoUrl": "https://docs.google.com/document/d/1WzMc8GJCRU6tTBb6gLsSTzFysqtXO8CtP2sXMPKgYSk/edit?disco=AAAA6xuVxGg" - }, - { - "description": "A top software company wanted to detect regressions when adding/changing queries and fail builds if queries were too expensive.", - "moreInfoUrl": "https://docs.google.com/document/d/1WzMc8GJCRU6tTBb6gLsSTzFysqtXO8CtP2sXMPKgYSk/edit?disco=AAAA6xuVxGg" - } - ], - "waysToUse": [ - { - "description": "Monitor performance for automated queries." - }, - { - "description": "Monitor performance for live queries.", - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/467" - } - ], - "name": "Query performance monitoring" - }, - { - "industryName": "Custom tables", - "friendlyName": "Add tables to osquery with extensions", - "description": "Create your own osquery tables, extensions & automatic table configurations or disable existing tables to maintain PII or privacy.", - "documentationUrl": "https://fleetdm.com/docs/configuration/agent-configuration#extensions", - "moreInfoUrl": "https://github.com/trailofbits/osquery-extensions/blob/3df2b72ad78549e25344c79dbc9bce6808c4d92a/README.md#extensions", - "tier": "Premium", - "jamfProHasFeature": "no", - "jamfProtectHasFeature": "no", - "productCategories": [ - "Endpoint operations" - ], - "pricingTableCategories": [ - "Devices" - ], - "usualDepartment": "IT", - "name": "Custom tables" - }, - { - "industryName": "Remote settings", - "description": "Configure agent options remotely, over the air. (Includes osquery config, and osquery startup flags.).", - "documentationUrl": "https://fleetdm.com/docs/configuration/agent-configuration", - "moreInfoUrl": "https://github.com/fleetdm/fleet/issues/13825", - "tier": "Free", - "jamfProHasFeature": "no", - "jamfProtectHasFeature": "no", - "productCategories": [ - "Endpoint operations" - ], - "pricingTableCategories": [ - "Devices" - ], - "usualDepartment": "Security", - "name": "Remote settings" - }, - { - "industryName": "Teams", - "friendlyName": "Manage different endpoints differently", - "documentationUrl": "https://fleetdm.com/docs/using-fleet/segment-hosts", - "description": "Teams are what Fleet calls baselines, kinda like security groups or images. Every host in a team matches the same baseline, with minor exceptions. This makes it faster and less risky to maintain computers, leading to faster timelines and fewer tickets.", - "tier": "Premium", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "productCategories": [ - "Endpoint operations", - "Device management", - "Vulnerability management" - ], - "pricingTableCategories": [ - "Devices" - ], - "waysToUse": [ - { - "description": "Automate remediation for different applications with different security postures (cloud security engineering)" - } - ], - "name": "Teams" - }, - { - "industryName": "Labels", - "documentationUrl": "https://fleetdm.com/docs/rest-api/rest-api#add-label", - "friendlyName": "Filter hosts using SQL", - "productCategories": [ - "Endpoint operations", - "Device management", - "Vulnerability management" - ], - "pricingTableCategories": [ - "Devices" - ], - "usualDepartment": "IT", - "tier": "Free", - "jamfProHasFeature": "no", - "jamfProtectHasFeature": "no", - "name": "Labels" - }, - { - "industryName": "Policies", - "description": "A policy is a specific “yes” or “no” query. Use policies to manage security compliance in your organization.", - "documentationUrl": "https://fleetdm.com/docs/using-fleet/fleet-ui", - "tier": "Free", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "no", - "productCategories": [ - "Endpoint operations", - "Device management", - "Vulnerability management" - ], - "pricingTableCategories": [ - "Devices" - ], - "usualDepartment": "IT", - "demos": [ - { - "description": "A top financial services company needed to set up rolling deployments for changes to osquery agents running on their production servers.", - "moreInfoUrl": "https://docs.google.com/document/d/1UdzZMyBLbs9SUXfSXN2x2wZQCbjZZUetYlNWH6-ryqQ/edit#heading=h.2lh6ehprpvl6" - } - ], - "waysToUse": [ - { - "description": "Trigger a workflow based on a failing policy", - "moreInfoUrl": "https://fleetdm.com/docs/using-fleet/automations#policy-automations" - } - ], - "name": "Policies" - }, - { - "industryName": "File integrity monitoring (FIM)", - "friendlyName": "Detect changes to critical files", - "description": "Specify files to monitor for changes or deletions, then log those events to your SIEM or data lake, including key information such as filepath and checksum.", - "documentationUrl": "https://fleetdm.com/guides/osquery-evented-tables-overview#file-integrity-monitoring-fim", - "screenshotSrc": "", - "tier": "Free", - "jamfProHasFeature": "no", - "jamfProtectHasFeature": "yes", - "usualDepartment": "Security", - "productCategories": [ - "Endpoint operations" - ], - "pricingTableCategories": [ - "Devices" - ], - "dri": "mikermcneil", - "demos": [ - { - "description": "A top gaming company needed a way to monitor critical files on production Debian servers.", - "quote": "The FIM features are kind of a top priority.", - "moreInfoUrl": "https://docs.google.com/document/d/1pE9U-1E4YDiy6h4TorszrTOiFAauFiORikSUFUqW7Pk/edit" - } - ], - "buzzwords": [ - "File integrity monitoring (FIM)", - "Host-based intrusion detection system (HIDS)", - "Anomaly detection" - ], - "waysToUse": [ - { - "description": "Monitor critical files on production Debian servers" - }, - { - "description": "Detect anomalous filesystem activity", - "moreInfoUrl": "https://www.beyondtrust.com/resources/glossary/file-integrity-monitoring" - }, - { - "description": "Detect unintended changes", - "moreInfoUrl": "https://www.beyondtrust.com/resources/glossary/file-integrity-monitoring" - }, - { - "description": "Verify update status and monitor system health", - "moreInfoUrl": "https://www.beyondtrust.com/resources/glossary/file-integrity-monitoring" - }, - { - "description": "Meet compliance mandates", - "moreInfoUrl": "https://www.beyondtrust.com/resources/glossary/file-integrity-monitoring" - } - ], - "name": "File integrity monitoring (FIM)" - }, - { - "industryName": "File carving", - "description": "Write the results of complex queries to AWS S3.", - "documentationUrl": "https://fleetdm.com/docs/configuration/fleet-server-configuration#s-3-file-carving-backend", - "tier": "Free", - "jamfProHasFeature": "no", - "jamfProtectHasFeature": "no", - "usualDepartment": "Security", - "productCategories": [ - "Endpoint operations" - ], - "pricingTableCategories": [ - "Devices" - ], - "name": "File carving" - }, - { - "industryName": "Binary authorization", - "friendlyName": "Restrict what programs can run, and what files running programs can access.", - "description": null, - "documentationUrl": null, - "tier": "Free", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "dri": "mikermcneil", - "usualDepartment": "Security", - "productCategories": [ - "Endpoint operations" - ], - "pricingTableCategories": [ - "Devices" - ], - "comingSoonOn": "2025-06-30", - "buzzwords": [ - "Mandatory Access Control (MAC)", - "Privilege confinement", - "Binary authorization", - "Santa", - "Binary allowlisting", - "Binary whitelisting" - ], - "demos": [ - { - "description": null, - "moreInfoUrl": null - } - ], - "waysToUse": [ - { - "description": "Confine programs to a limited set of resources." - }, - { - "description": "Report on AppArmor events", - "moreInfoUrl": "https://fleetdm.com/tables/apparmor_events" - }, - { - "description": "Confine programs according to a set of rules that specify which files a program can access.", - "moreInfoUrl": "https://wiki.debian.org/AppArmor" - }, - { - "description": "Proactively protect the system against both known and unknown vulnerabilities." - } - ], - "name": "Binary authorization", - "comingSoon": true - }, - { - "industryName": "Reporting", - "description": "Generate reports based on searchable device attributes", - "documentationUrl": "https://fleetdm.com/docs/rest-api/rest-api#get-query-report", - "productCategories": [ - "Endpoint operations", - "Device management", - "Vulnerability management" - ], - "pricingTableCategories": [ - "Devices" - ], - "usualDepartment": "IT", - "tier": "Premium", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "name": "Reporting" - }, - { - "industryName": "Incident response", - "friendlyName": "Interrogate hosts in real time", - "description": "Live query, triage, figuring out scope of impact, remediate using scripts or MDM commands (e.g. remote wipe), and quarantine or reimage using other systems and APIs (e.g. remove from network, decommission container)", - "documentationUrl": "https://fleetdm.com/securing/how-osquery-can-help-cyber-responders#simplifying-endpoint-visibility-with-osquery-and-fleet", - "tier": "Free", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "dri": "mikermcneil", - "usualDepartment": "Security", - "productCategories": [ - "Endpoint operations" - ], - "pricingTableCategories": [ - "Devices" - ], - "buzzwords": [], - "demos": [ - { - "description": null, - "moreInfoUrl": null - } - ], - "waysToUse": [ - { - "description": null - } - ], - "name": "Incident response" - }, - { - "industryName": "Custom logging", - "description": "Flexible, configurable logging destinations (AWS Kinesis, Lambda, GCP, Kafka).", - "documentationUrl": "https://fleetdm.com/docs/using-fleet/log-destinations#log-destinations", - "tier": "Free", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "usualDepartment": "Security", - "productCategories": [ - "Endpoint operations" - ], - "pricingTableCategories": [ - "Devices" - ], - "buzzwords": [ - "Real-time export", - "Ship logs" - ], - "name": "Custom logging" - }, - { - "industryName": "Malware detection (YARA/custom IoCs)", - "friendlyName": "Scan files for zero days and malware signatures", - "description": "Use YARA signatures to report and trigger automations when zero days, malware, or unexpected files are detected on a host.", - "documentationUrl": "https://fleetdm.com/tables/yara", - "tier": "Free", - "jamfProHasFeature": "no", - "jamfProtectHasFeature": "yes", - "dri": "mikermcneil", - "usualDepartment": "Security", - "productCategories": [ - "Endpoint operations", - "Vulnerability management" - ], - "pricingTableCategories": [ - "Devices" - ], - "buzzwords": [ - "YARA scanning", - "Cyber Threat Intelligence (CTI)", - "Indicators of compromise (IOCs)", - "Antivirus (AV)", - "Endpoint protection platform (EPP)", - "Endpoint detection and response (EDR)", - "Malware detection", - "Signature-based malware detection", - "Malware scanning", - "Malware analysis", - "Anomaly detection" - ], - "demos": [ - { - "description": "A top media company used Fleet policies with YARA rules to continuously scan host filesystems for malware signatures provided by internal and external threat intelligence teams.", - "moreInfoUrl": null - } - ], - "waysToUse": [ - { - "description": "Detect suspicious bytecode in JAR files" - }, - { - "description": "Identify suspicious patterns in binaries using YARA signatures" - }, - { - "description": "Continuously scan host filesystems for malware signatures.", - "moreInfoUrl": "https://yara.readthedocs.io/en/stable/writingrules.html" - }, - { - "description": "Monitor for relevent filesystem changes (YARA events) and on-demand YARA signature scans.", - "moreInfoUrl": "https://osquery.readthedocs.io/en/stable/deployment/yara/" - }, - { - "description": "Use YARA for malware detection", - "moreInfoUrl": "https://www.cisa.gov/sites/default/files/FactSheets/NCCIC%20ICS_FactSheet_YARA_S508C.pdf" - }, - { - "description": "Scan for indicators of compromise (IoC) for common malware.", - "moreInfoUrl": "https://github.com/Cisco-Talos/osquery_queries" - }, - { - "description": "Analyze malware using data from osquery, such as endpoint certificates and launch daemons (launchd).", - "moreInfoUrl": "https://medium.com/hackernoon/malware-analysis-using-osquery-part-3-9dc805b67d16" - }, - { - "description": "Detect persistent malware (e.g. WireLurker) in endpoints by generating simple policies that search for their static indicators of compromise (IoCs).", - "moreInfoUrl": "https://osquery.readthedocs.io/en/stable/deployment/anomaly-detection/" - }, - { - "description": "Run a targeted YARA scan with osquery as a lightweight approach to scan anything on a host filesystem, with minimal performance impact. Unlike full system YARA scans which consume considerable CPU resources, an equivalent YARA scan targeted in Fleet can be 8x cheaper (CPU %).", - "moreInfoUrl": "https://www.tripwire.com/state-of-security/signature-socket-based-malware-detection-osquery-yara" - } - ], - "name": "Malware detection (YARA/custom IoCs)" - }, - { - "industryName": "Continuous scanning", - "friendlyName": "Detect vulnerable software", - "documentationUrl": "https://fleetdm.com/vulnerability-management", - "productCategories": [ - "Vulnerability management" - ], - "pricingTableCategories": [ - "Devices" - ], - "usualDepartment": "Security", - "tier": "Free", - "jamfProHasFeature": "no", - "jamfProtectHasFeature": "yes", - "buzzwords": [ - "Stakeholder-specific vulnerability categorization (SSVC)", - "Continuous scanning", - "Continuous vulnerability scanning", - "Risk-based vulnerability management" - ], - "waysToUse": [ - { - "description": "Use an SSVC decision tree model to prioritize relevant vulnerabilities into four possible decisions: \"Track\", \"Track*\", \"Attend\", and \"Act\".", - "moreInfoUrl": "https://www.cisa.gov/stakeholder-specific-vulnerability-categorization-ssvc" - }, - { - "description": "Balint Fazakas: I think what offers a better use of CVSS if you break it down to vectors. You may find that a DoS (High Availability Impact) not as relevant for you, or equally a vulnerability requiring user interaction has a very low likelihood of exploit in another scenario. If you want to fine tune your SSVC, it worth using the vectors you care about instead of the score itself. But ultimately you would want to read the description of the vulnerabilities to determine the risk they are posing to your environment. SSVC can assist you to do that in a more efficient way.", - "moreInfoUrl": "https://www.linkedin.com/feed/update/urn:li:activity:7162614115025215488?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7162614115025215488%2C7162681703918985216%29&dashCommentUrn=urn%3Ali%3Afsd_comment%3A%287162681703918985216%2Curn%3Ali%3Aactivity%3A7162614115025215488%29" - }, - { - "description": "Melissa Bischoping: CVSS is never enough to contextualize the urgency or risk of a vulnerability in your environment. It is one metric that needs to be part of an overall risk calculus, but a CVSS of 6 can be a greater threat in your organization than a CVSS of 10 based on the environmental variables and mitigations. Only two 10.0s here, but several lower severity that are resulting in high-impact breaches. Getting a handle on managing that public facing infrastructure and being able to rapidly patch the apps and devices with such exposure needs to be part of an overall plan, but must go hand in hand with mitigations and layers of a zero trust design. CVSS isn’t the sole determination of risk, it’s only one partial piece of data to understand the impact of a vulnerability if exploited.", - "moreInfoUrl": "https://www.linkedin.com/feed/update/urn:li:activity:7162614115025215488?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7162614115025215488%2C7162629486344159232%29&dashCommentUrn=urn%3Ali%3Afsd_comment%3A%287162629486344159232%2Curn%3Ali%3Aactivity%3A7162614115025215488%29" - } - ], - "demos": [ - { - "description": "A top gaming company wanted to replace Qualys for infrastructure vulnerability detection.", - "quote": "So we have some stuff today through Qualys, but it's just not very good. A lot of it is...it's just really noisy. I'm trying to find out specifically, actually what packages are installed where, and then the ability to live query them.", - "moreInfoUrl": "https://docs.google.com/document/d/1JWtRsW1FUTCkZEESJj9-CvXjLXK4219by-C6vvVVyBY/edit" - }, - { - "description": "One of the world's largest, top transportation companies uses Fleet's API to email relevant, actually-installed vulnerabilities to responsible teams so they can fix them.", - "moreInfoUrl": "https://docs.google.com/document/d/1oeCmT077o_5nxzLhnxs7kcg_4Qn1Pn1F5zx10nQOAp8/edit" - } - ], - "name": "Continuous scanning" - }, - { - "industryName": "Vulnerability scores", - "friendlyName": "EPSS and CVSS", - "documentationUrl": "https://fleetdm.com/vulnerability-management", - "tier": "Premium", - "jamfProHasFeature": "no", - "jamfProtectHasFeature": "yes", - "usualDepartment": "Security", - "productCategories": [ - "Vulnerability management" - ], - "pricingTableCategories": [ - "Devices" - ], - "buzzwords": [ - "Risk scores", - "Cyber risk", - "Risk reduction", - "Security operations effectiveness", - "Peer benchmarking", - "Security program effectiveness", - "Risk-based exposure scoring", - "Threat context", - "Cyber exposure", - "Exposure quantification and benchmarking", - "Optimize security investments", - "Vulnerability assessment" - ], - "demos": [ - { - "description": "Fleet enables a more modern, threat-first prioritization approach to vulnerability management.", - "quote": "In reality, across our inventory of devices, it's unlikely to ever be exploited. I'd rather do that legwork on my team and then go and ask and prioritize work on these infrastructure teams that are already busy with things that could or could not be vulnerable. Being able to be more exact allows us to go to these teams less, which saves everybody time.", - "moreInfoUrl": "https://www.youtube.com/watch?v=G5Ry_vQPaYc&t=131s" - } - ], - "waysToUse": [ - { - "description": "By leveraging EPSS (Exploit Prediction Scoring System), security professionals gain insight on the true risk behind rated CVEs." - }, - { - "description": "An Introduction to EPSS, The Exploit Prediction Scoring System" - }, - { - "moreInfoUrl": "https://www.youtube.com/watch?v=vw1RlZCSRcQ" - }, - { - "description": "By extracting metadata from the National Vulnerability Database (NVD) and Microsoft Security Response Center (MSRC), we can determine which version of software is no longer vulnerable." - } - ], - "name": "Vulnerability scores" - }, - { - "industryName": "CISA KEVs", - "description": "Known exploited vulnerabilities", - "documentationUrl": "https://fleetdm.com/vulnerability-management", - "tier": "Premium", - "jamfProHasFeature": "no", - "jamfProtectHasFeature": "yes", - "usualDepartment": "Security", - "productCategories": [ - "Vulnerability management" - ], - "pricingTableCategories": [ - "Devices" - ], - "demos": [ - { - "description": null, - "moreInfoUrl": null - } - ], - "waysToUse": [ - { - "description": "Help teams work on vulnerabilities that have actually been exploited (CISA KEVs) or have a high probability of being exploited (EPSS), or whatever is important in your environment." - }, - { - "description": "Use CISA KEVs for vulnerability management" - }, - { - "moreInfoUrl": "https://www.youtube.com/watch?v=Z3mw2oxssYk" - } - ], - "name": "CISA KEVs" - }, - { - "industryName": "Asset discovery", - "documentationUrl": null, - "tier": "Premium", - "comingSoonOn": "2025-06-30", - "usualDepartment": "Security", - "productCategories": [ - "Vulnerability management" - ], - "pricingTableCategories": [ - "Devices" - ], - "name": "Asset discovery", - "comingSoon": true - }, - { - "industryName": "REST API", - "friendlyName": "Automate any feature", - "description": null, - "productCategories": [ - "Endpoint operations", - "Device management", - "Vulnerability management" - ], - "pricingTableCategories": [ - "Integrations" - ], - "usualDepartment": "IT", - "documentationUrl": "https://fleetdm.com/docs/rest-api/rest-api", - "screenshotSrc": null, - "tier": "Free", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "dri": "rachaelshaw", - "name": "REST API" - }, - { - "industryName": "Webhooks", - "friendlyName": "Automations", - "documentationUrl": "https://fleetdm.com/docs/using-fleet/automations#automations", - "productCategories": [ - "Endpoint operations", - "Device management", - "Vulnerability management" - ], - "pricingTableCategories": [ - "Integrations" - ], - "usualDepartment": "IT", - "tier": "Free", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "name": "Webhooks" - }, - { - "industryName": "Grant API-only access", - "description": "Grant API-only access to accounts exclusively for automation.", - "documentationUrl": "https://fleetdm.com/docs/using-fleet/fleetctl-cli#using-fleetctl-with-an-api-only-user", - "productCategories": [ - "Endpoint operations" - ], - "pricingTableCategories": [ - "Integrations" - ], - "tier": "Free", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "name": "Grant API-only access" - }, - { - "industryName": "Single sign on", - "description": "SSO, SAML", - "documentationUrl": "https://fleetdm.com/docs/deploy/single-sign-on-sso#single-sign-on-sso", - "productCategories": [ - "Endpoint operations", - "Device management", - "Vulnerability management" - ], - "pricingTableCategories": [ - "Integrations" - ], - "usualDepartment": "IT", - "tier": "Free", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "name": "Single sign on" - }, - { - "industryName": "Automatic user creation (JIT, SCIM)", - "description": "Auto-create and manipulate Fleet users from Okta, etc with just-in-time (JIT) provisioning.", - "documentationUrl": "https://fleetdm.com/docs/deploy/single-sign-on-sso#just-in-time-jit-user-provisioning", - "productCategories": [ - "Endpoint operations", - "Device management", - "Vulnerability management" - ], - "pricingTableCategories": [ - "Integrations" - ], - "usualDepartment": "IT", - "tier": "Premium", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "no", - "name": "Automatic user creation (JIT, SCIM)" - }, - { - "industryName": "Third-party automation", - "friendlyName": "Borrow off-the-shelf tactics from the community", - "documentationUrl": "https://fleetdm.com/integrations", - "productCategories": [ - "Endpoint operations", - "Device management", - "Vulnerability management" - ], - "pricingTableCategories": [ - "Integrations" - ], - "usualDepartment": "IT", - "description": "Plug Fleet into other frameworks and tools like Tines, Snowflake, Terraform, Chronicle, Jira, Zendesk, etc", - "moreInfoUrl": "https://fleetdm.com/integrations", - "tier": "Free", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "waysToUse": [ - { - "description": "(ActiveDirectory) Know who opened your computer and check their device posture before you let them log into anything." - }, - { - "description": "(Ansible) Easily issue MDM commands and standardize data across operating systems." - }, - { - "description": "(AWS) Deploy your own self-managed Fleet in any AWS environment in minutes." - }, - { - "description": "(Azure) Deploy your own self-managed Fleet in the Microsoft Cloud in minutes." - }, - { - "description": "(Chef) Easily issue MDM commands and standardize data across operating systems." - }, - { - "description": "(Elastic) Ingest osquery data and monitor for important changes or events." - }, - { - "description": "(GitHub) Version control using git, enabling collaboration and a GitOps workflow." - }, - { - "description": "(GitLab) Version control using git, enabling collaboration and a GitOps workflow." - }, - { - "description": "(Chronicle) Ingest osquery data and monitor for important changes or events." - }, - { - "description": "(Google Cloud) Deploy your own self-managed Fleet in any GCP environment in minutes." - }, - { - "description": "(Munki) Easily issue MDM commands and standardize data across operating systems." - }, - { - "description": "(Okta) Know who opened your computer and check their device posture before you let them log into anything." - }, - { - "description": "(Snowflake) Ingest osquery data and monitor for important changes or events." - }, - { - "description": "(Splunk) Ingest osquery data and monitor for important changes or events." - }, - { - "description": "(Tines) Build custom workflows that trigger in various situations." - }, - { - "description": "(Webhooks) Configure automations that send webhooks to specific URLs when Fleet detects changes to host, policy, and CVE statuses." - }, - { - "description": "(Zendesk) Automatically create Zendesk tickets in various situations." - }, - { - "description": "(Jira) Automatically create Jira tickets in various situations, including exporting vulnerabilities to Jira and syncing tickets." - } - ], - "buzzwords": [ - "Snowflake", - "Okta", - "Tines", - "Splunk", - "Elastic", - "AWS", - "ActiveDirectory", - "Ansible", - "GitHub", - "GitLab", - "Chronicle", - "Google Cloud", - "Munki", - "Vanta", - "Chef", - "Zendesk", - "Jira" - ], - "name": "Third-party automation" - }, - { - "industryName": "Third-party orchestration", - "friendlyName": "Borrow off-the-shelf tactics from legendary brands", - "documentationUrl": "https://fleetdm.com/integrations", - "description": "Plug Fleet into other frameworks and tools like Puppet, Vanta, etc.", - "productCategories": [ - "Endpoint operations", - "Device management", - "Vulnerability management" - ], - "pricingTableCategories": [ - "Integrations" - ], - "usualDepartment": "IT", - "moreInfoUrl": "https://fleetdm.com/integrations", - "tier": "Premium", - "waysToUse": [ - { - "description": "(Vanta) Trigger a workflow based on a failing policy." - }, - { - "description": "(Puppet) Easily issue MDM commands, standardize data across operating systems, and map macOS+Windows settings to computers with the Puppet module." - }, - { - "description": "(Torq) Build custom workflows that trigger in various situations." - }, - { - "description": "(Custom IdP) Manage access to Fleet single sign-on (SSO) through any IdP (using SAML)." - } - ], - "buzzwords": [ - "Vanta", - "Puppet", - "Custom IdP" - ], - "name": "Third-party orchestration" - }, - { - "industryName": "Munki compatibility + visibility", - "tier": "Premium", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "usualDepartment": "IT", - "productCategories": [ - "Device management" - ], - "pricingTableCategories": [ - "Integrations" - ], - "name": "Munki compatibility + visibility" - }, - { - "industryName": "Open-source issue tracker (GitHub)", - "documentationUrl": "https://fleetdm.com/support", - "productCategories": [ - "Endpoint operations", - "Device management", - "Vulnerability management" - ], - "pricingTableCategories": [ - "Support" - ], - "tier": "Free", - "jamfProHasFeature": "no", - "jamfProtectHasFeature": "no", - "name": "Open-source issue tracker (GitHub)" - }, - { - "industryName": "Community Slack channel", - "documentationUrl": "https://fleetdm.com/support", - "productCategories": [ - "Endpoint operations", - "Device management", - "Vulnerability management" - ], - "pricingTableCategories": [ - "Support" - ], - "tier": "Free", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "name": "Community Slack channel" - }, - { - "industryName": "Unlimited email support (confidential)", - "documentationUrl": "https://fleetdm.com/support", - "productCategories": [ - "Endpoint operations", - "Device management", - "Vulnerability management" - ], - "pricingTableCategories": [ - "Support" - ], - "tier": "Premium", - "jamfProHasFeature": "yes", - "jamfProtectHasFeature": "yes", - "name": "Unlimited email support (confidential)" - }, - { - "industryName": "Phone and video call support", - "documentationUrl": "https://fleetdm.com/support", - "productCategories": [ - "Endpoint operations", - "Device management", - "Vulnerability management" - ], - "pricingTableCategories": [ - "Support" - ], - "tier": "Premium", - "jamfProHasFeature": "no", - "jamfProtectHasFeature": "no", - "name": "Phone and video call support" - } - ], - "markdownPages": [ - { - "url": "/docs", - "title": "Readme.md", - "lastModifiedAt": 1726839803427, - "htmlId": "docs--readme--51292620cf", - "docNavCategory": "Uncategorized", - "sectionRelativeRepoPath": "README.md", - "meta": {} - }, - { - "url": "/docs/rest-api/rest-api", - "title": "REST API", - "lastModifiedAt": 1726839804830, - "htmlId": "docs--rest-api--aa8babd202", - "pageOrderInSectionPath": 30, - "docNavCategory": "Uncategorized", - "sectionRelativeRepoPath": "REST API/rest-api.md", - "meta": { - "description": "Documentation for Fleet's REST API. See example requests and responses for each API endpoint." - } - }, - { - "url": "/docs/configuration/agent-configuration", - "title": "Agent configuration", - "lastModifiedAt": 1726839804835, - "htmlId": "docs--agent-configuration--ac988306ab", - "pageOrderInSectionPath": 300, - "docNavCategory": "Uncategorized", - "sectionRelativeRepoPath": "Configuration/agent-configuration.md", - "meta": { - "description": "Learn how to use configuration files and the fleetctl command line tool to configure agent options." - } - }, - { - "url": "/docs/configuration", - "title": "Configuration", - "lastModifiedAt": 1726839804836, - "htmlId": "docs--readme--71f5513034", - "docNavCategory": "Uncategorized", - "sectionRelativeRepoPath": "Configuration/README.md", - "meta": {} - }, - { - "url": "/docs/configuration/fleet-server-configuration", - "title": "Fleet server configuration", - "lastModifiedAt": 1726839804850, - "htmlId": "docs--fleet-server-configu--51d934dc8a", - "pageOrderInSectionPath": 100, - "docNavCategory": "Uncategorized", - "sectionRelativeRepoPath": "Configuration/fleet-server-configuration.md", - "meta": { - "description": "This page includes resources for configuring the Fleet binary, managing osquery configurations, and running with systemd." - } - }, - { - "url": "/docs/configuration/yaml-files", - "title": "YAML files", - "lastModifiedAt": 1726839804856, - "htmlId": "docs--yaml-files--1c08b93d5e", - "pageOrderInSectionPath": 1500, - "docNavCategory": "Uncategorized", - "sectionRelativeRepoPath": "Configuration/yaml-files.md", - "meta": { - "description": "Reference documentation for Fleet's GitOps workflow. See examples and configuration options." - } - }, - { - "url": "/docs/rest-api", - "title": "REST API", - "lastModifiedAt": 1726839804857, - "htmlId": "docs--readme--1c430dc120", - "docNavCategory": "Uncategorized", - "sectionRelativeRepoPath": "REST API/README.md", - "meta": {} - }, - { - "url": "/docs/deploy/reference-architectures", - "title": "Reference architectures", - "lastModifiedAt": 1726839804860, - "htmlId": "docs--reference-architectu--1e6f63e559", - "pageOrderInSectionPath": 400, - "docNavCategory": "Uncategorized", - "sectionRelativeRepoPath": "Deploy/Reference-Architectures.md", - "meta": { - "description": "An opinionated view of running Fleet in a production environment, and configuration strategies to enable high availability." - } - }, - { - "url": "/docs/deploy", - "title": "Deploy", - "lastModifiedAt": 1726839804861, - "htmlId": "docs--readme--926e990cf4", - "docNavCategory": "Uncategorized", - "sectionRelativeRepoPath": "Deploy/README.md", - "meta": { - "description": "An overview of the deployment documentation for Fleet." - } - }, - { - "url": "/docs/deploy/deploy-fleet", - "title": "Deploy Fleet", - "lastModifiedAt": 1726839804863, - "htmlId": "docs--deploy-fleet--82212f6ffe", - "pageOrderInSectionPath": 100, - "docNavCategory": "Uncategorized", - "sectionRelativeRepoPath": "Deploy/deploy-fleet.md", - "meta": { - "description": "Learn how to easily deploy Fleet on Render or AWS with Terraform." - } - }, - { - "url": "/docs/deploy/single-sign-on-sso", - "title": "Single sign-on (SSO)", - "lastModifiedAt": 1726839804865, - "htmlId": "docs--single-sign-on-sso--89a4f43390", - "pageOrderInSectionPath": 200, - "docNavCategory": "Uncategorized", - "sectionRelativeRepoPath": "Deploy/single-sign-on-sso.md", - "meta": { - "description": "Learn how to configure single sign-on (SSO)" - } - }, - { - "url": "/docs/get-started/faq", - "title": "FAQ", - "lastModifiedAt": 1726839804868, - "htmlId": "docs--faq--abab6eff91", - "docNavCategory": "Uncategorized", - "sectionRelativeRepoPath": "Get started/FAQ.md", - "meta": { - "description": "Commonly asked questions and answers about deployment from the Fleet community." - } - }, - { - "url": "/docs/get-started", - "title": "Get started", - "lastModifiedAt": 1726839804869, - "htmlId": "docs--readme--3568e93d97", - "docNavCategory": "Uncategorized", - "sectionRelativeRepoPath": "Get started/README.md", - "meta": {} - }, - { - "url": "/docs/get-started/anatomy", - "title": "Anatomy", - "lastModifiedAt": 1726839804869, - "htmlId": "docs--anatomy--1f83ca9de5", - "pageOrderInSectionPath": 200, - "docNavCategory": "Uncategorized", - "sectionRelativeRepoPath": "Get started/anatomy.md", - "meta": {} - }, - { - "url": "/docs/get-started/why-fleet", - "title": "Why Fleet", - "lastModifiedAt": 1726839804870, - "htmlId": "docs--why-fleet--9ea776ea58", - "pageOrderInSectionPath": 100, - "docNavCategory": "Uncategorized", - "sectionRelativeRepoPath": "Get started/why-fleet.md", - "meta": {} - }, - { - "url": "/docs/deploy/upgrading-fleet", - "title": "Upgrading Fleet", - "lastModifiedAt": 1726839804871, - "htmlId": "docs--upgrading-fleet--a39ae08550", - "pageOrderInSectionPath": 300, - "docNavCategory": "Uncategorized", - "sectionRelativeRepoPath": "Deploy/Upgrading-Fleet.md", - "meta": { - "description": "Learn how to upgrade your Fleet instance to the latest version." - } - }, - { - "url": "/docs/get-started/tutorials-and-guides", - "title": "Tutorials and guides", - "lastModifiedAt": 1726839804872, - "htmlId": "docs--tutorials-and-guides--27a7cc6bcf", - "pageOrderInSectionPath": 300, - "docNavCategory": "Uncategorized", - "sectionRelativeRepoPath": "Get started/tutorials-and-guides.md", - "meta": { - "description": "Links to deployment tutorials and guides for using Fleet." - } - }, - { - "url": "/docs/using-fleet", - "title": "Using Fleet", - "lastModifiedAt": 1726839804873, - "htmlId": "docs--readme--d3ac87c2d1", - "docNavCategory": "Uncategorized", - "sectionRelativeRepoPath": "Using Fleet/README.md", - "meta": {} - }, - { - "url": "/handbook", - "title": "Readme.md", - "lastModifiedAt": 1726839804876, - "htmlId": "handbook--readme--58c6582576", - "sectionRelativeRepoPath": "README.md", - "meta": { - "maintainedBy": "mikermcneil" - }, - "linksForHandbookIndex": [ - { - "headingText": "Introduction", - "hashLink": "/handbook#introduction" - } - ] - }, - { - "url": "/handbook/company", - "title": "🔭 Company", - "lastModifiedAt": 1726839804878, - "htmlId": "handbook--readme--e464663acc", - "sectionRelativeRepoPath": "company/README.md", - "meta": { - "maintainedBy": "mikermcneil" - }, - "linksForHandbookIndex": [ - { - "headingText": "Purpose", - "hashLink": "/handbook/company#purpose" - }, - { - "headingText": "Culture", - "hashLink": "/handbook/company#culture" - }, - { - "headingText": "Open positions", - "hashLink": "/handbook/company#open-positions" - }, - { - "headingText": "Values", - "hashLink": "/handbook/company#values" - }, - { - "headingText": "History", - "hashLink": "/handbook/company#history" - }, - { - "headingText": "Org chart", - "hashLink": "/handbook/company#org-chart" - }, - { - "headingText": "Advisors", - "hashLink": "/handbook/company#advisors" - } - ] - }, - { - "url": "/handbook/company/handbook", - "title": "Handbook", - "lastModifiedAt": 1726839804879, - "htmlId": "handbook--handbook--9ae510ce56", - "sectionRelativeRepoPath": "company/handbook.md", - "meta": { - "maintainedBy": "mike-j-thomas" - }, - "linksForHandbookIndex": [ - { - "headingText": "Contributing to the handbook", - "hashLink": "/handbook/company/handbook#contributing-to-the-handbook" - } - ] - }, - { - "url": "/handbook/company/communications", - "title": "🛰️ Communications", - "lastModifiedAt": 1726839804891, - "htmlId": "handbook--communications--f0d5a4a053", - "sectionRelativeRepoPath": "company/communications.md", - "meta": { - "maintainedBy": "mikermcneil" - }, - "linksForHandbookIndex": [ - { - "headingText": "All hands", - "hashLink": "/handbook/company/communications#all-hands" - }, - { - "headingText": "Strategy", - "hashLink": "/handbook/company/communications#strategy" - }, - { - "headingText": "Directly responsible individuals (DRIs)", - "hashLink": "/handbook/company/communications#directly-responsible-individuals-dr-is" - }, - { - "headingText": "Tech stack admins", - "hashLink": "/handbook/company/communications#tech-stack-admins" - }, - { - "headingText": "Fleetdm.com", - "hashLink": "/handbook/company/communications#fleetdm-com" - }, - { - "headingText": "Marketing programs", - "hashLink": "/handbook/company/communications#marketing-programs" - }, - { - "headingText": "Meetings", - "hashLink": "/handbook/company/communications#meetings" - }, - { - "headingText": "Skip-level 1:1 meetings ", - "hashLink": "/handbook/company/communications#skip-level-1-1-meetings" - }, - { - "headingText": "Zoom", - "hashLink": "/handbook/company/communications#zoom" - }, - { - "headingText": "Levels of confidentiality", - "hashLink": "/handbook/company/communications#levels-of-confidentiality" - }, - { - "headingText": "Google Drive", - "hashLink": "/handbook/company/communications#google-drive" - }, - { - "headingText": "Email relays", - "hashLink": "/handbook/company/communications#email-relays" - }, - { - "headingText": "Slack", - "hashLink": "/handbook/company/communications#slack" - }, - { - "headingText": "GitHub", - "hashLink": "/handbook/company/communications#git-hub" - }, - { - "headingText": "High priority user stories and bugs", - "hashLink": "/handbook/company/communications#high-priority-user-stories-and-bugs" - }, - { - "headingText": "Figma", - "hashLink": "/handbook/company/communications#figma" - }, - { - "headingText": "Spending company money", - "hashLink": "/handbook/company/communications#spending-company-money" - }, - { - "headingText": "Travel", - "hashLink": "/handbook/company/communications#travel" - }, - { - "headingText": "SOC 2", - "hashLink": "/handbook/company/communications#soc-2" - }, - { - "headingText": "Vendor questionnaires ", - "hashLink": "/handbook/company/communications#vendor-questionnaires" - }, - { - "headingText": "Getting a contract signed", - "hashLink": "/handbook/company/communications#getting-a-contract-signed" - }, - { - "headingText": "Getting a contract reviewed", - "hashLink": "/handbook/company/communications#getting-a-contract-reviewed" - }, - { - "headingText": "Trust", - "hashLink": "/handbook/company/communications#trust" - }, - { - "headingText": "Benefits", - "hashLink": "/handbook/company/communications#benefits" - }, - { - "headingText": "Compensation", - "hashLink": "/handbook/company/communications#compensation" - }, - { - "headingText": "Team member onboarding", - "hashLink": "/handbook/company/communications#team-member-onboarding" - }, - { - "headingText": "Performance feedback", - "hashLink": "/handbook/company/communications#performance-feedback" - }, - { - "headingText": "Equipment", - "hashLink": "/handbook/company/communications#equipment" - }, - { - "headingText": "Writing", - "hashLink": "/handbook/company/communications#writing" - }, - { - "headingText": "Writing in Fleet-flavored Markdown", - "hashLink": "/handbook/company/communications#writing-in-fleet-flavored-markdown" - }, - { - "headingText": "Things", - "hashLink": "/handbook/company/communications#things" - }, - { - "headingText": "Commonly used terms", - "hashLink": "/handbook/company/communications#commonly-used-terms" - } - ] - }, - { - "url": "/handbook/company/leadership", - "title": "🛠️ Leadership", - "lastModifiedAt": 1726839804898, - "htmlId": "handbook--leadership--7d8a02ee64", - "sectionRelativeRepoPath": "company/leadership.md", - "meta": { - "maintainedBy": "mikermcneil" - }, - "linksForHandbookIndex": [ - { - "headingText": "CEO flaws", - "hashLink": "/handbook/company/leadership#ceo-flaws" - }, - { - "headingText": "Contact the CEO", - "hashLink": "/handbook/company/leadership#contact-the-ceo" - }, - { - "headingText": "CEO responsibilities", - "hashLink": "/handbook/company/leadership#ceo-responsibilities" - }, - { - "headingText": "Outline of departmental page structure", - "hashLink": "/handbook/company/leadership#outline-of-departmental-page-structure" - }, - { - "headingText": "Key reviews", - "hashLink": "/handbook/company/leadership#key-reviews" - }, - { - "headingText": "Hiring", - "hashLink": "/handbook/company/leadership#hiring" - }, - { - "headingText": "CEO shadow program", - "hashLink": "/handbook/company/leadership#ceo-shadow-program" - }, - { - "headingText": "Tracking hours", - "hashLink": "/handbook/company/leadership#tracking-hours" - }, - { - "headingText": "Communicating departures", - "hashLink": "/handbook/company/leadership#communicating-departures" - }, - { - "headingText": "Changing someone's position", - "hashLink": "/handbook/company/leadership#changing-someone-s-position" - }, - { - "headingText": "Delivering performance feedback", - "hashLink": "/handbook/company/leadership#delivering-performance-feedback" - } - ] - }, - { - "url": "/handbook/company/product-groups", - "title": "🛩️ Product groups", - "lastModifiedAt": 1726839804907, - "htmlId": "handbook--product-groups--44ec471e19", - "sectionRelativeRepoPath": "company/product-groups.md", - "meta": { - "maintainedBy": "lukeheath" - }, - "linksForHandbookIndex": [ - { - "headingText": "Product roadmap", - "hashLink": "/handbook/company/product-groups#product-roadmap" - }, - { - "headingText": "What are product groups?", - "hashLink": "/handbook/company/product-groups#what-are-product-groups" - }, - { - "headingText": "Current product groups", - "hashLink": "/handbook/company/product-groups#current-product-groups" - }, - { - "headingText": "Making changes", - "hashLink": "/handbook/company/product-groups#making-changes" - }, - { - "headingText": "Outages", - "hashLink": "/handbook/company/product-groups#outages" - }, - { - "headingText": "Scaling Fleet", - "hashLink": "/handbook/company/product-groups#scaling-fleet" - }, - { - "headingText": "Load testing", - "hashLink": "/handbook/company/product-groups#load-testing" - }, - { - "headingText": "Version support", - "hashLink": "/handbook/company/product-groups#version-support" - }, - { - "headingText": "Release testing", - "hashLink": "/handbook/company/product-groups#release-testing" - }, - { - "headingText": "Feature fest", - "hashLink": "/handbook/company/product-groups#feature-fest" - }, - { - "headingText": "Quality", - "hashLink": "/handbook/company/product-groups#quality" - }, - { - "headingText": "How to reach the developer on-call", - "hashLink": "/handbook/company/product-groups#how-to-reach-the-developer-on-call" - }, - { - "headingText": "Wireframes", - "hashLink": "/handbook/company/product-groups#wireframes" - }, - { - "headingText": "Meetings", - "hashLink": "/handbook/company/product-groups#meetings" - }, - { - "headingText": "Development best practices", - "hashLink": "/handbook/company/product-groups#development-best-practices" - }, - { - "headingText": "Product design conventions", - "hashLink": "/handbook/company/product-groups#product-design-conventions" - }, - { - "headingText": "Scrum at Fleet", - "hashLink": "/handbook/company/product-groups#scrum-at-fleet" - }, - { - "headingText": "Sprints", - "hashLink": "/handbook/company/product-groups#sprints" - }, - { - "headingText": "Outside contributions", - "hashLink": "/handbook/company/product-groups#outside-contributions" - } - ] - }, - { - "url": "/handbook/company/why-this-way", - "title": "💭 Why this way?", - "lastModifiedAt": 1726839804912, - "htmlId": "handbook--why-this-way--52ff9aa8d3", - "sectionRelativeRepoPath": "company/why-this-way.md", - "meta": { - "maintainedBy": "mikermcneil" - }, - "linksForHandbookIndex": [ - { - "headingText": "Why open source?", - "hashLink": "/handbook/company/why-this-way#why-open-source" - }, - { - "headingText": "Why handbook-first strategy?", - "hashLink": "/handbook/company/why-this-way#why-handbook-first-strategy" - }, - { - "headingText": "Why read documentation?", - "hashLink": "/handbook/company/why-this-way#why-read-documentation" - }, - { - "headingText": "Why the emphasis on training?", - "hashLink": "/handbook/company/why-this-way#why-the-emphasis-on-training" - }, - { - "headingText": "Why direct responsibility?", - "hashLink": "/handbook/company/why-this-way#why-direct-responsibility" - }, - { - "headingText": "Why do we use a wireframe-first approach?", - "hashLink": "/handbook/company/why-this-way#why-do-we-use-a-wireframe-first-approach" - }, - { - "headingText": "Why do we use one repo?", - "hashLink": "/handbook/company/why-this-way#why-do-we-use-one-repo" - }, - { - "headingText": "Why not continuously generate REST API reference docs from javadoc-style code comments?", - "hashLink": "/handbook/company/why-this-way#why-not-continuously-generate-rest-api-reference-docs-from-javadoc-style-code-comments" - }, - { - "headingText": "Why group Slack channels?", - "hashLink": "/handbook/company/why-this-way#why-group-slack-channels" - }, - { - "headingText": "Why make work visible?", - "hashLink": "/handbook/company/why-this-way#why-make-work-visible" - }, - { - "headingText": "Why agile?", - "hashLink": "/handbook/company/why-this-way#why-agile" - }, - { - "headingText": "Why a three-week cadence?", - "hashLink": "/handbook/company/why-this-way#why-a-three-week-cadence" - }, - { - "headingText": "Why spend so much energy responding to every potential production incident?", - "hashLink": "/handbook/company/why-this-way#why-spend-so-much-energy-responding-to-every-potential-production-incident" - }, - { - "headingText": "Why make it obvious when stuff breaks?", - "hashLink": "/handbook/company/why-this-way#why-make-it-obvious-when-stuff-breaks" - }, - { - "headingText": "Why keep issue templates simple?", - "hashLink": "/handbook/company/why-this-way#why-keep-issue-templates-simple" - }, - { - "headingText": "Why spend less?", - "hashLink": "/handbook/company/why-this-way#why-spend-less" - }, - { - "headingText": "Why don't we sell like everyone else?", - "hashLink": "/handbook/company/why-this-way#why-don-t-we-sell-like-everyone-else" - }, - { - "headingText": "Why does Fleet support query packs?", - "hashLink": "/handbook/company/why-this-way#why-does-fleet-support-query-packs" - }, - { - "headingText": "Why does Fleet use sentence case?", - "hashLink": "/handbook/company/why-this-way#why-does-fleet-use-sentence-case" - }, - { - "headingText": "Why not use superlatives?", - "hashLink": "/handbook/company/why-this-way#why-not-use-superlatives" - }, - { - "headingText": "Why does Fleet use \"MDM on/off\" instead of \"MDM enrolled/unenrolled\"?", - "hashLink": "/handbook/company/why-this-way#why-does-fleet-use-mdm-on-off-instead-of-mdm-enrolled-unenrolled" - }, - { - "headingText": "Why not mention the CEO in Slack threads?", - "hashLink": "/handbook/company/why-this-way#why-not-mention-the-ceo-in-slack-threads" - } - ] - }, - { - "url": "/handbook/customer-success", - "title": "🌦️ Customer Success", - "lastModifiedAt": 1726839804915, - "htmlId": "handbook--readme--f00a4291b8", - "sectionRelativeRepoPath": "customer-success/README.md", - "meta": { - "maintainedBy": "zayhanlon" - }, - "linksForHandbookIndex": [ - { - "headingText": "Team", - "hashLink": "/handbook/customer-success#team" - }, - { - "headingText": "Contact us", - "hashLink": "/handbook/customer-success#contact-us" - }, - { - "headingText": "Responsibilities", - "hashLink": "/handbook/customer-success#responsibilities" - }, - { - "headingText": "Rituals", - "hashLink": "/handbook/customer-success#rituals" - } - ] - }, - { - "url": "/handbook/engineering/debugging", - "title": "Debugging", - "lastModifiedAt": 1726839804916, - "htmlId": "handbook--debugging--72906ebdd6", - "sectionRelativeRepoPath": "engineering/Debugging.md", - "meta": { - "maintainedBy": "lukeheath", - "description": "A guide to triaging and diagnosing issues in Fleet." - }, - "linksForHandbookIndex": [ - { - "headingText": "Goals of this guide", - "hashLink": "/handbook/engineering/debugging#goals-of-this-guide" - }, - { - "headingText": "Basic data that is needed", - "hashLink": "/handbook/engineering/debugging#basic-data-that-is-needed" - }, - { - "headingText": "Triaging the issue", - "hashLink": "/handbook/engineering/debugging#triaging-the-issue" - } - ] - }, - { - "url": "/handbook/engineering/load-testing", - "title": "Load testing", - "lastModifiedAt": 1726839804917, - "htmlId": "handbook--load-testing--5fd9ee04e0", - "sectionRelativeRepoPath": "engineering/Load-testing.md", - "meta": { - "maintainedBy": "lukeheath", - "description": "This page outlines the most recent results of a semi-annual load test of the Fleet server." - }, - "linksForHandbookIndex": [ - { - "headingText": "Test parameters", - "hashLink": "/handbook/engineering/load-testing#test-parameters" - }, - { - "headingText": "Results", - "hashLink": "/handbook/engineering/load-testing#results" - }, - { - "headingText": "How we are simulating osquery", - "hashLink": "/handbook/engineering/load-testing#how-we-are-simulating-osquery" - }, - { - "headingText": "Infrastructure setup", - "hashLink": "/handbook/engineering/load-testing#infrastructure-setup" - }, - { - "headingText": "Limitations", - "hashLink": "/handbook/engineering/load-testing#limitations" - } - ] - }, - { - "url": "/handbook/engineering", - "title": "🚀 Engineering", - "lastModifiedAt": 1726839804924, - "htmlId": "handbook--readme--777ccc3e11", - "sectionRelativeRepoPath": "engineering/README.md", - "meta": { - "maintainedBy": "lukeheath" - }, - "linksForHandbookIndex": [ - { - "headingText": "Team", - "hashLink": "/handbook/engineering#team" - }, - { - "headingText": "Contact us", - "hashLink": "/handbook/engineering#contact-us" - }, - { - "headingText": "Responsibilities", - "hashLink": "/handbook/engineering#responsibilities" - }, - { - "headingText": "Rituals", - "hashLink": "/handbook/engineering#rituals" - } - ] - }, - { - "url": "/handbook/engineering/scaling-fleet", - "title": "Scaling Fleet", - "lastModifiedAt": 1726839804925, - "htmlId": "handbook--scaling-fleet--7496895e6e", - "sectionRelativeRepoPath": "engineering/scaling-fleet.md", - "meta": { - "maintainedBy": "lukeheath" - } - }, - { - "url": "/handbook/finance", - "title": "💸 Finance", - "lastModifiedAt": 1726839804931, - "htmlId": "handbook--readme--adb6ad624d", - "sectionRelativeRepoPath": "finance/README.md", - "meta": { - "maintainedBy": "jostableford" - }, - "linksForHandbookIndex": [ - { - "headingText": "Team", - "hashLink": "/handbook/finance#team" - }, - { - "headingText": "Contact us", - "hashLink": "/handbook/finance#contact-us" - }, - { - "headingText": "Responsibilities", - "hashLink": "/handbook/finance#responsibilities" - }, - { - "headingText": "Rituals", - "hashLink": "/handbook/finance#rituals" - } - ] - }, - { - "url": "/handbook/demand", - "title": "🫧 Demand", - "lastModifiedAt": 1726839804935, - "htmlId": "handbook--readme--5f95cdc89d", - "sectionRelativeRepoPath": "demand/README.md", - "meta": { - "maintainedBy": "Drew-P-Drawers" - }, - "linksForHandbookIndex": [ - { - "headingText": "Team", - "hashLink": "/handbook/demand#team" - }, - { - "headingText": "Contact us", - "hashLink": "/handbook/demand#contact-us" - }, - { - "headingText": "Responsibilities", - "hashLink": "/handbook/demand#responsibilities" - }, - { - "headingText": "Rituals", - "hashLink": "/handbook/demand#rituals" - } - ] - }, - { - "url": "/handbook/product-design", - "title": "🦢 Product design", - "lastModifiedAt": 1726839804937, - "htmlId": "handbook--readme--5ce44066f3", - "sectionRelativeRepoPath": "product-design/README.md", - "meta": { - "maintainedBy": "noahtalerman" - }, - "linksForHandbookIndex": [ - { - "headingText": "Team", - "hashLink": "/handbook/product-design#team" - }, - { - "headingText": "Contact us", - "hashLink": "/handbook/product-design#contact-us" - }, - { - "headingText": "Responsibilities", - "hashLink": "/handbook/product-design#responsibilities" - }, - { - "headingText": "Rituals", - "hashLink": "/handbook/product-design#rituals" - } - ] - }, - { - "url": "/handbook/digital-experience/application-security", - "title": "Application security", - "lastModifiedAt": 1726839804939, - "htmlId": "handbook--application-security--60a7adaa5a", - "sectionRelativeRepoPath": "digital-experience/application-security.md", - "meta": { - "description": "Explore Fleet's application security practices, including secure coding, SQL injection prevention, authentication, data encryption, access controls, and more.", - "maintainedBy": "hollidayn" - } - }, - { - "url": "/handbook/digital-experience", - "title": "🌐 Digital Experience", - "lastModifiedAt": 1726839804945, - "htmlId": "handbook--readme--7c78659bd2", - "sectionRelativeRepoPath": "digital-experience/README.md", - "meta": { - "maintainedBy": "Sampfluger88" - }, - "linksForHandbookIndex": [ - { - "headingText": "Team", - "hashLink": "/handbook/digital-experience#team" - }, - { - "headingText": "Contact us", - "hashLink": "/handbook/digital-experience#contact-us" - }, - { - "headingText": "Responsibilities", - "hashLink": "/handbook/digital-experience#responsibilities" - }, - { - "headingText": "Rituals", - "hashLink": "/handbook/digital-experience#rituals" - } - ] - }, - { - "url": "/handbook/digital-experience/security-audits", - "title": "Security audits", - "lastModifiedAt": 1726839804948, - "htmlId": "handbook--security-audits--b0d65992c5", - "sectionRelativeRepoPath": "digital-experience/security-audits.md", - "meta": { - "description": "Explanations of the latest external security audits performed on Fleet software.", - "maintainedBy": "hollidayn" - }, - "linksForHandbookIndex": [ - { - "headingText": "June 2024 penetration testing of Fleet 4.50.1", - "hashLink": "/handbook/digital-experience/security-audits#june-2024-penetration-testing-of-fleet-4-50-1" - }, - { - "headingText": "June 2023 penetration testing of Fleet 4.32 ", - "hashLink": "/handbook/digital-experience/security-audits#june-2023-penetration-testing-of-fleet-4-32" - }, - { - "headingText": "April 2022 penetration testing of Fleet 4.12 ", - "hashLink": "/handbook/digital-experience/security-audits#april-2022-penetration-testing-of-fleet-4-12" - }, - { - "headingText": "August 2021 security of Orbit auto-updater", - "hashLink": "/handbook/digital-experience/security-audits#august-2021-security-of-orbit-auto-updater" - } - ] - }, - { - "url": "/handbook/digital-experience/security-policies", - "title": "📜 Security policies", - "lastModifiedAt": 1726839804955, - "htmlId": "handbook--security-policies--96158a5cf6", - "sectionRelativeRepoPath": "digital-experience/security-policies.md", - "meta": { - "maintainedBy": "jostableford" - }, - "linksForHandbookIndex": [ - { - "headingText": "Information security policy and acceptable use policy", - "hashLink": "/handbook/digital-experience/security-policies#information-security-policy-and-acceptable-use-policy" - }, - { - "headingText": "Access control policy", - "hashLink": "/handbook/digital-experience/security-policies#access-control-policy" - }, - { - "headingText": "Asset management policy", - "hashLink": "/handbook/digital-experience/security-policies#asset-management-policy" - }, - { - "headingText": "Business continuity and disaster recovery policy", - "hashLink": "/handbook/digital-experience/security-policies#business-continuity-and-disaster-recovery-policy" - }, - { - "headingText": "Data management policy", - "hashLink": "/handbook/digital-experience/security-policies#data-management-policy" - }, - { - "headingText": "Encryption policy", - "hashLink": "/handbook/digital-experience/security-policies#encryption-policy" - }, - { - "headingText": "Human resources security policy", - "hashLink": "/handbook/digital-experience/security-policies#human-resources-security-policy" - }, - { - "headingText": "Incident response policy", - "hashLink": "/handbook/digital-experience/security-policies#incident-response-policy" - }, - { - "headingText": "Network and system hardening standards", - "hashLink": "/handbook/digital-experience/security-policies#network-and-system-hardening-standards" - }, - { - "headingText": "Operations security and change management policy", - "hashLink": "/handbook/digital-experience/security-policies#operations-security-and-change-management-policy" - }, - { - "headingText": "Risk management policy", - "hashLink": "/handbook/digital-experience/security-policies#risk-management-policy" - }, - { - "headingText": "Secure software development and product security policy ", - "hashLink": "/handbook/digital-experience/security-policies#secure-software-development-and-product-security-policy" - }, - { - "headingText": "Security policy management policy", - "hashLink": "/handbook/digital-experience/security-policies#security-policy-management-policy" - }, - { - "headingText": "Third-party management policy", - "hashLink": "/handbook/digital-experience/security-policies#third-party-management-policy" - }, - { - "headingText": "Anti-corruption policy", - "hashLink": "/handbook/digital-experience/security-policies#anti-corruption-policy" - } - ] - }, - { - "url": "/handbook/digital-experience/vendor-questionnaires", - "title": "📃 Vendor questionnaires", - "lastModifiedAt": 1726839804956, - "htmlId": "handbook--vendor-questionnaire--46cac642a1", - "sectionRelativeRepoPath": "digital-experience/vendor-questionnaires.md", - "meta": { - "maintainedBy": "dherder" - }, - "linksForHandbookIndex": [ - { - "headingText": "Scoping", - "hashLink": "/handbook/digital-experience/vendor-questionnaires#scoping" - }, - { - "headingText": "Application security", - "hashLink": "/handbook/digital-experience/vendor-questionnaires#application-security" - }, - { - "headingText": "Data security", - "hashLink": "/handbook/digital-experience/vendor-questionnaires#data-security" - }, - { - "headingText": "Service monitoring and logging", - "hashLink": "/handbook/digital-experience/vendor-questionnaires#service-monitoring-and-logging" - }, - { - "headingText": "Encryption and key management", - "hashLink": "/handbook/digital-experience/vendor-questionnaires#encryption-and-key-management" - }, - { - "headingText": "Governance and risk management", - "hashLink": "/handbook/digital-experience/vendor-questionnaires#governance-and-risk-management" - }, - { - "headingText": "Business continuity", - "hashLink": "/handbook/digital-experience/vendor-questionnaires#business-continuity" - }, - { - "headingText": "Network security", - "hashLink": "/handbook/digital-experience/vendor-questionnaires#network-security" - }, - { - "headingText": "Privacy", - "hashLink": "/handbook/digital-experience/vendor-questionnaires#privacy" - }, - { - "headingText": "Sub-processors", - "hashLink": "/handbook/digital-experience/vendor-questionnaires#sub-processors" - } - ] - }, - { - "url": "/handbook/digital-experience/security", - "title": "Security", - "lastModifiedAt": 1726839804965, - "htmlId": "handbook--security--585b03364d", - "sectionRelativeRepoPath": "digital-experience/security.md", - "meta": { - "maintainedBy": "hollidayn" - }, - "linksForHandbookIndex": [ - { - "headingText": "Security policies", - "hashLink": "/handbook/digital-experience/security#security-policies" - }, - { - "headingText": "Account recovery process", - "hashLink": "/handbook/digital-experience/security#account-recovery-process" - }, - { - "headingText": "How we protect end-user devices", - "hashLink": "/handbook/digital-experience/security#how-we-protect-end-user-devices" - }, - { - "headingText": "Hardware security keys", - "hashLink": "/handbook/digital-experience/security#hardware-security-keys" - }, - { - "headingText": "GitHub security", - "hashLink": "/handbook/digital-experience/security#git-hub-security" - }, - { - "headingText": "Google Workspace security", - "hashLink": "/handbook/digital-experience/security#google-workspace-security" - }, - { - "headingText": "Vulnerability management", - "hashLink": "/handbook/digital-experience/security#vulnerability-management" - }, - { - "headingText": "Trust report", - "hashLink": "/handbook/digital-experience/security#trust-report" - }, - { - "headingText": "Securtiy audits", - "hashLink": "/handbook/digital-experience/security#securtiy-audits" - }, - { - "headingText": "Application security", - "hashLink": "/handbook/digital-experience/security#application-security" - } - ] - }, - { - "url": "/handbook/sales", - "title": "🐋 Sales", - "lastModifiedAt": 1726839804968, - "htmlId": "handbook--readme--4fe57c451a", - "sectionRelativeRepoPath": "sales/README.md", - "meta": { - "maintainedBy": "alexmitchelliii" - }, - "linksForHandbookIndex": [ - { - "headingText": "Team", - "hashLink": "/handbook/sales#team" - }, - { - "headingText": "Contact us", - "hashLink": "/handbook/sales#contact-us" - }, - { - "headingText": "Responsibilities", - "hashLink": "/handbook/sales#responsibilities" - }, - { - "headingText": "Rituals", - "hashLink": "/handbook/sales#rituals" - } - ] - }, - { - "url": "/engineering/tips-for-github-actions-usability", - "title": "Tips for github actions usability", - "lastModifiedAt": 1726839804972, - "htmlId": "articles--4-tips-for-github-ac--c93d8d672b", - "sectionRelativeRepoPath": "4-tips-for-github-actions-usability.md", - "meta": { - "category": "engineering", - "authorGitHubUsername": "zwass", - "authorFullName": "Zach Wasserman", - "publishedOn": "2022-01-03", - "articleTitle": "4 tips for GitHub Actions usability (+2 bonus tips for debugging)", - "articleImageUrl": "/images/articles/4-tips-for-github-actions-usability-cover-1600x900@2x.jpg" - } - }, - { - "url": "/guides/apple-developer-certificates-on-linux-for-configuration-profile-signing", - "title": "Apple developer certificates on linux for configuration profile signing", - "lastModifiedAt": 1726839804973, - "htmlId": "articles--apple-developer-cert--3d7bfdf01f", - "sectionRelativeRepoPath": "apple-developer-certificates-on-linux-for-configuration-profile-signing.md", - "meta": { - "articleTitle": "Apple developer certificates on Linux for configuration profile signing", - "authorFullName": "Brock Walters", - "authorGitHubUsername": "nonpunctual", - "category": "guides", - "publishedOn": "2024-03-06", - "articleImageUrl": "/images/articles/apple-developer-certificates-on-linux-for-configuration-profile-signing-1600x900@2x.png", - "description": "This guide walks through the process of adding an Apple signing certificate to a Linux host." - } - }, - { - "url": "/announcements/a-new-fleet", - "title": "A new Fleet", - "lastModifiedAt": 1726839804974, - "htmlId": "articles--a-new-fleet--0c5af0e434", - "sectionRelativeRepoPath": "a-new-fleet.md", - "meta": { - "category": "announcements", - "authorGitHubUsername": "zwass", - "authorFullName": "Zach Wasserman", - "publishedOn": "2020-01-05", - "articleTitle": "A new Fleet", - "articleImageUrl": "/images/articles/a-new-fleet-cover-700x340@2x.jpeg" - } - }, - { - "url": "/securing/apply-byod-to-soothe-supply-chain-pain", - "title": "Apply byod to soothe supply chain pain", - "lastModifiedAt": 1726839804976, - "htmlId": "articles--apply-byod-to-soothe--866604b091", - "sectionRelativeRepoPath": "apply-byod-to-soothe-supply-chain-pain.md", - "meta": { - "category": "security", - "authorGitHubUsername": "GuillaumeRoss", - "authorFullName": "Guillaume Ross", - "publishedOn": "2022-02-10", - "articleTitle": "Apply BYOD to soothe supply chain pain", - "articleImageUrl": "/images/articles/apply-byod-to-soothe-supply-chain-pain-cover-1600x900@2x.jpg" - } - }, - { - "url": "/guides/automations", - "title": "Automations", - "lastModifiedAt": 1726839804976, - "htmlId": "articles--automations--ff5e8024a5", - "sectionRelativeRepoPath": "automations.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "noahtalerman", - "authorFullName": "Noah Talerman", - "publishedOn": "2024-07-03", - "articleTitle": "Automations", - "description": "Configure Fleet automations to trigger webhooks or create tickets in Jira and Zendesk for vulnerability, policy, and host status events." - } - }, - { - "url": "/guides/building-webhook-flows-with-fleet-and-tines", - "title": "Building webhook flows with Fleet and tines", - "lastModifiedAt": 1726839804978, - "htmlId": "articles--building-webhook-flo--3ffb4a9791", - "sectionRelativeRepoPath": "building-webhook-flows-with-fleet-and-tines.md", - "meta": { - "articleTitle": "Building webhook flows with Fleet and Tines", - "authorFullName": "Victor Lyuboslavsky", - "authorGitHubUsername": "getvictor", - "category": "guides", - "publishedOn": "2024-05-30", - "articleImageUrl": "/images/articles/building-webhook-flows-with-fleet-and-tines-1600x900@2x.png", - "description": "A guide to workflows using Tines and Fleet via webhook to update outdated OS versions." - } - }, - { - "url": "/guides/building-an-effective-dashboard-with-fleet-rest-api-flask-and-plotly", - "title": "Building an effective dashboard with Fleet REST API flask and plotly", - "lastModifiedAt": 1726839804979, - "htmlId": "articles--building-an-effectiv--d3c30b5cf6", - "sectionRelativeRepoPath": "building-an-effective-dashboard-with-fleet-rest-api-flask-and-plotly.md", - "meta": { - "articleTitle": "Building an effective dashboard with Fleet's REST API, Flask, and Plotly: A step-by-step guide", - "authorFullName": "Dave Herder", - "authorGitHubUsername": "dherder", - "category": "guides", - "publishedOn": "2023-05-22", - "articleImageUrl": "/images/articles/building-an-effective-dashboard-with-fleet-rest-api-flask-and-plotly@2x.jpg", - "description": "Step-by-step guide on building a dynamic dashboard with Fleet's REST API, Flask, and Plotly. Master data visualization with open-source tools!" - } - }, - { - "url": "/guides/certificates-in-fleetd", - "title": "Certificates in fleetd", - "lastModifiedAt": 1726839804980, - "htmlId": "articles--certificates-in-flee--f860411dcf", - "sectionRelativeRepoPath": "certificates-in-fleetd.md", - "meta": { - "articleTitle": "Certificates in fleetd", - "authorFullName": "Lucas Manuel Rodriguez", - "authorGitHubUsername": "lucasmrod", - "category": "guides", - "publishedOn": "2024-07-09", - "articleImageUrl": "/images/articles/apple-developer-certificates-on-linux-for-configuration-profile-signing-1600x900@2x.png", - "description": "TLS certificates in fleetd" - } - }, - { - "url": "/guides/chrome-os", - "title": "Chrome os", - "lastModifiedAt": 1726839804981, - "htmlId": "articles--chrome-os--8f9e4f0cca", - "sectionRelativeRepoPath": "chrome-os.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "zhumo", - "authorFullName": "Mo Zhu", - "publishedOn": "2023-11-21", - "articleTitle": "ChromeOS", - "description": "Learn about ChromeOS and Fleet." - } - }, - { - "url": "/guides/catch-missed-authorization-checks-during-software-development", - "title": "Catch missed authorization checks during software development", - "lastModifiedAt": 1726839804981, - "htmlId": "articles--catch-missed-authori--74d449dae1", - "sectionRelativeRepoPath": "catch-missed-authorization-checks-during-software-development.md", - "meta": { - "articleTitle": "Catch missed authorization checks during software development", - "authorFullName": "Victor Lyuboslavsky", - "authorGitHubUsername": "getvictor", - "category": "guides", - "publishedOn": "2023-12-04", - "description": "How to perform authorization checks in a golang codebase for cybersecurity" - } - }, - { - "url": "/guides/cis-benchmarks", - "title": "Cis benchmarks", - "lastModifiedAt": 1726839804982, - "htmlId": "articles--cis-benchmarks--c493697884", - "sectionRelativeRepoPath": "cis-benchmarks.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "lucasmrod", - "authorFullName": "Lucas Rodriguez", - "publishedOn": "2024-04-02", - "articleTitle": "CIS Benchmarks", - "description": "Read about how Fleet's implementation of CIS Benchmarks offers consensus-based cybersecurity guidance." - } - }, - { - "url": "/announcements/comparative-look-at-ws1-and-fleet", - "title": "Comparative look at ws1 and Fleet", - "lastModifiedAt": 1726839804983, - "htmlId": "articles--comparative-look-at---d3aff5bdd7", - "sectionRelativeRepoPath": "comparative-look-at-ws1-and-fleet.md", - "meta": { - "category": "announcements", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2024-02-01", - "articleTitle": "A comparative look at VMware Workspace ONE and Fleet Device Management", - "articleImageUrl": "/images/articles/comparative-look-at-ws1-and-fleet-1600x900@2x.png" - } - }, - { - "url": "/guides/config-less-fleetd-agent-deployment", - "title": "Config less fleetd agent deployment", - "lastModifiedAt": 1726839804984, - "htmlId": "articles--config-less-fleetd-a--e5546949d5", - "sectionRelativeRepoPath": "config-less-fleetd-agent-deployment.md", - "meta": { - "articleTitle": "Config-less fleetd agent deployment", - "authorFullName": "Noah Talerman", - "authorGitHubUsername": "noahtalerman", - "category": "guides", - "publishedOn": "2024-01-31", - "articleImageUrl": "/images/articles/config-less-fleetd-agent-deployment-1600x900@2x.png", - "description": "Config-less `fleetd` agent deployment" - } - }, - { - "url": "/guides/configuring-default-teams-for-devices-in-fleet", - "title": "Configuring default teams for devices in Fleet", - "lastModifiedAt": 1726839804985, - "htmlId": "articles--configuring-default---d9b024f2b7", - "sectionRelativeRepoPath": "configuring-default-teams-for-devices-in-fleet.md", - "meta": { - "articleTitle": "Configuring default teams for macOS, iOS, and iPadOS devices in Fleet", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "category": "guides", - "publishedOn": "2024-09-12", - "description": "This guide will walk you through configuring default teams for devices using the Fleet web UI." - } - }, - { - "url": "/guides/converting-unix-timestamps-with-osquery", - "title": "Converting unix timestamps with osquery", - "lastModifiedAt": 1726839804986, - "htmlId": "articles--converting-unix-time--ace81a16aa", - "sectionRelativeRepoPath": "converting-unix-timestamps-with-osquery.md", - "meta": { - "category": "guides", - "authorFullName": "Mike Thomas", - "authorGitHubUsername": "mike-j-thomas", - "publishedOn": "2021-06-15", - "articleTitle": "Converting unix timestamps with osquery", - "articleImageUrl": "/images/articles/converting-unix-timestamps-with-osquery-cover-800x450@2x.jpeg" - } - }, - { - "url": "/guides/correlate-network-connections-with-community-id-in-osquery", - "title": "Correlate network connections with community id in osquery", - "lastModifiedAt": 1726839804987, - "htmlId": "articles--correlate-network-co--10ea0b1641", - "sectionRelativeRepoPath": "correlate-network-connections-with-community-id-in-osquery.md", - "meta": { - "category": "guides", - "authorFullName": "Zach Wasserman", - "authorGitHubUsername": "zwass", - "publishedOn": "2021-06-02", - "articleTitle": "Correlate network connections with community ID in osquery.", - "articleImageUrl": "/images/articles/correlate-network-connections-with-community-id-in-osquery-cover-800x502@2x.jpeg" - } - }, - { - "url": "/guides/custom-os-settings", - "title": "Custom os settings", - "lastModifiedAt": 1726839804988, - "htmlId": "articles--custom-os-settings--5e97a43205", - "sectionRelativeRepoPath": "custom-os-settings.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "noahtalerman", - "authorFullName": "Noah Talerman", - "publishedOn": "2024-07-27", - "articleTitle": "Custom OS settings", - "description": "Learn how to enforce custom settings on macOS and Window hosts using Fleet's configuration profiles." - } - }, - { - "url": "/announcements/debunk-the-cross-platform-myth", - "title": "Debunk the cross platform myth", - "lastModifiedAt": 1726839804989, - "htmlId": "articles--debunk-the-cross-pla--d46aac3cb4", - "sectionRelativeRepoPath": "debunk-the-cross-platform-myth.md", - "meta": { - "category": "announcements", - "authorFullName": "Mike McNeil", - "authorGitHubUsername": "mikermcneil", - "publishedOn": "2024-08-27", - "articleTitle": "Debunk the cross-platform myth", - "description": "Debunk the cross-platform myth with MDM" - } - }, - { - "url": "/guides/delivering-data-to-snowflake-from-fleet-and-osquery", - "title": "Delivering data to snowflake from Fleet and osquery", - "lastModifiedAt": 1726839804991, - "htmlId": "articles--delivering-data-to-s--9677bbe81b", - "sectionRelativeRepoPath": "delivering-data-to-snowflake-from-fleet-and-osquery.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "t-lark", - "authorFullName": "Tom Larkin", - "publishedOn": "2022-02-01", - "articleTitle": "Delivering data to Snowflake from Fleet and osquery.", - "articleImageUrl": "/images/articles/delivering-data-to-snowflake-from-fleet-and-osquery-cover-1600x900@2x.jpg" - } - }, - { - "url": "/guides/deploy-fleet-on-aws-ecs", - "title": "Deploy Fleet on aws ecs", - "lastModifiedAt": 1726839804992, - "htmlId": "articles--deploy-fleet-on-aws---ca8c5b2fc4", - "sectionRelativeRepoPath": "deploy-fleet-on-aws-ecs.md", - "meta": { - "articleTitle": "Deploy Fleet on AWS ECS", - "authorGitHubUsername": "edwardsb", - "authorFullName": "Ben Edwards", - "publishedOn": "2021-10-06", - "category": "guides", - "articleImageUrl": "/images/articles/deploy-fleet-on-aws-ecs-800x450@2x.png", - "description": "Information for deploying Fleet on AWS ECS." - } - }, - { - "url": "/guides/deploy-fleet-on-aws-with-terraform", - "title": "Deploy Fleet on aws with terraform", - "lastModifiedAt": 1726839804993, - "htmlId": "articles--deploy-fleet-on-aws---8b2a9168ab", - "sectionRelativeRepoPath": "deploy-fleet-on-aws-with-terraform.md", - "meta": { - "articleTitle": "Deploy Fleet on AWS with Terraform", - "authorGitHubUsername": "edwardsb", - "authorFullName": "Ben Edwards", - "publishedOn": "2021-11-30", - "category": "guides", - "articleImageUrl": "/images/articles/deploy-fleet-on-aws-with-terraform-800x450@2x.png", - "description": "Learn how to deploy Fleet on AWS." - } - }, - { - "url": "/guides/deploy-fleet-on-centos", - "title": "Deploy Fleet on centos", - "lastModifiedAt": 1726839804994, - "htmlId": "articles--deploy-fleet-on-cent--4841e96234", - "sectionRelativeRepoPath": "deploy-fleet-on-centos.md", - "meta": { - "articleTitle": "Deploy Fleet on CentOS", - "authorGitHubUsername": "marpaia", - "authorFullName": "Mike Arpaia", - "publishedOn": "2017-09-22", - "category": "guides", - "articleImageUrl": "/images/articles/deploy-fleet-on-centos-800x450@2x.png", - "description": "A guide to deploy Fleet on CentOS." - } - }, - { - "url": "/guides/deploy-fleet-on-cloudgov", - "title": "Deploy Fleet on cloudgov", - "lastModifiedAt": 1726839804995, - "htmlId": "articles--deploy-fleet-on-clou--ecdaaf656b", - "sectionRelativeRepoPath": "deploy-fleet-on-cloudgov.md", - "meta": { - "articleTitle": "Deploy Fleet on Cloud.gov", - "authorGitHubUsername": "JJediny", - "authorFullName": "John Jediny", - "publishedOn": "2022-09-08", - "category": "guides", - "articleImageUrl": "/images/articles/deploy-fleet-on-cloudgov-800x450@2x.png", - "description": "Information for deploying Fleet on Cloud.gov." - } - }, - { - "url": "/guides/deploy-fleet-on-hetzner-cloud", - "title": "Deploy Fleet on hetzner cloud", - "lastModifiedAt": 1726839804999, - "htmlId": "articles--deploy-fleet-on-hetz--ab40dd3e5f", - "sectionRelativeRepoPath": "deploy-fleet-on-hetzner-cloud.md", - "meta": { - "articleTitle": "Deploy Fleet on Hetzner Cloud", - "authorGitHubUsername": "ksatter", - "authorFullName": "Kathy Satterlee", - "publishedOn": "2022-06-27", - "category": "guides", - "articleImageUrl": "/images/articles/deploy-fleet-on-hetzner-cloud-800x450@2x.png", - "description": "Learn how to deploy Fleet on Hetzner Cloud using cloud-init and Docker." - } - }, - { - "url": "/guides/deploy-fleet-on-kubernetes", - "title": "Deploy Fleet on kubernetes", - "lastModifiedAt": 1726839805000, - "htmlId": "articles--deploy-fleet-on-kube--b62fcc97c7", - "sectionRelativeRepoPath": "deploy-fleet-on-kubernetes.md", - "meta": { - "articleTitle": "Deploy Fleet on Kubernetes", - "authorGitHubUsername": "marpaia", - "authorFullName": "Mike Arpaia", - "publishedOn": "2017-11-18", - "category": "guides", - "articleImageUrl": "/images/articles/deploy-fleet-on-kubernetes-800x450@2x.png", - "description": "Learn how to deploy Fleet on Kubernetes." - } - }, - { - "url": "/guides/deploy-fleet-on-render", - "title": "Deploy Fleet on render", - "lastModifiedAt": 1726839805001, - "htmlId": "articles--deploy-fleet-on-rend--175bce353f", - "sectionRelativeRepoPath": "deploy-fleet-on-render.md", - "meta": { - "articleTitle": "Deploy Fleet on Render", - "authorGitHubUsername": "edwardsb", - "authorFullName": "Ben Edwards", - "publishedOn": "2021-11-21", - "category": "guides", - "articleImageUrl": "/images/articles/deploy-fleet-on-render-800x450@2x.png", - "description": "Learn how to deploy Fleet on Render." - } - }, - { - "url": "/guides/deploy-fleet-on-ubuntu-with-elastic", - "title": "Deploy Fleet on ubuntu with elastic", - "lastModifiedAt": 1726839805004, - "htmlId": "articles--deploy-fleet-on-ubun--db33029e1f", - "sectionRelativeRepoPath": "deploy-fleet-on-ubuntu-with-elastic.md", - "meta": { - "articleTitle": "Deploy Fleet on Ubuntu", - "authorGitHubUsername": "defensivedepth", - "authorFullName": "Josh Brower", - "publishedOn": "2024-06-12", - "category": "guides", - "description": "A guide to deploy Fleet and Elastic on Ubuntu.", - "articleImageUrl": "/images/articles/deploy-fleet-on-ubuntu-with-elastic-1600x900@2x.png" - } - }, - { - "url": "/guides/deploy-security-agents", - "title": "Deploy security agents", - "lastModifiedAt": 1726839805005, - "htmlId": "articles--deploy-security-agen--a3a93c715b", - "sectionRelativeRepoPath": "deploy-security-agents.md", - "meta": { - "articleTitle": "Deploy security agents", - "authorFullName": "Roberto Dip", - "authorGitHubUsername": "roperzh", - "category": "guides", - "publishedOn": "2024-08-05", - "articleImageUrl": "/images/articles/deploy-security-agents-1600x900@2x.png", - "description": "This guide will walk you through adding software to Fleet." - } - }, - { - "url": "/securing/detect-log4j-with-osquery-and-fleet", - "title": "Detect log4j with osquery and Fleet", - "lastModifiedAt": 1726839805006, - "htmlId": "articles--detect-log4j-with-os--812eb5ba15", - "sectionRelativeRepoPath": "detect-log4j-with-osquery-and-fleet.md", - "meta": { - "category": "security", - "authorFullName": "Zach Wasserman", - "authorGitHubUsername": "zwass", - "publishedOn": "2021-12-15", - "articleTitle": "Detect Log4j with osquery (and Fleet)", - "articleImageUrl": "/images/articles/detect-log4j-with-osquery-and-fleet-1600x900@2x.jpg" - } - }, - { - "url": "/guides/discovering-chrome-ai-using-fleet", - "title": "Discovering chrome ai using Fleet", - "lastModifiedAt": 1726839805007, - "htmlId": "articles--discovering-chrome-a--4de87d4fb6", - "sectionRelativeRepoPath": "discovering-chrome-ai-using-fleet.md", - "meta": { - "articleTitle": "Discovering Chrome AI using Fleet", - "authorFullName": "Brock Walters", - "authorGitHubUsername": "nonpunctual", - "category": "guides", - "publishedOn": "2024-09-06", - "articleImageUrl": "/images/articles/discovering-chrome-ai-using-fleet-1600x900@2x.jpg", - "description": "Use Fleet to detect and monitor settings enabled in Google Chrome by querying Chrome's preferences JSON file." - } - }, - { - "url": "/guides/discovering-geacon-using-fleet", - "title": "Discovering geacon using Fleet", - "lastModifiedAt": 1726839805008, - "htmlId": "articles--discovering-geacon-u--bab06239aa", - "sectionRelativeRepoPath": "discovering-geacon-using-fleet.md", - "meta": { - "articleTitle": "Discovering Geacon using Fleet", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "category": "guides", - "publishedOn": "2023-05-18", - "articleImageUrl": "/images/articles/discovering-geacon-using-fleet-1600x900@2x.jpg", - "description": "Enterprise security teams can use Fleet to identify and locate Geacon payloads and protect their macOS devices from this threat." - } - }, - { - "url": "/guides/discovering-xz-vulnerability-with-fleet", - "title": "Discovering xz vulnerability with Fleet", - "lastModifiedAt": 1726839805010, - "htmlId": "articles--discovering-xz-vulne--0a7dc5a7f8", - "sectionRelativeRepoPath": "discovering-xz-vulnerability-with-fleet.md", - "meta": { - "articleTitle": "Discovering xz vulnerability with Fleet", - "authorFullName": "Brock Walters", - "authorGitHubUsername": "nonpunctual", - "category": "guides", - "publishedOn": "2024-06-03", - "articleImageUrl": "/images/articles/discovering-geacon-using-fleet-1600x900@2x.jpg", - "description": "Discover and create a comprehensive end-to-end remediation workflow for the xz vulnerability (CVE-2024-3094) with Fleet." - } - }, - { - "url": "/securing/does-osquery-violate-the-new-york-employee-monitoring-law", - "title": "Does osquery violate the new york employee monitoring law", - "lastModifiedAt": 1726839805011, - "htmlId": "articles--does-osquery-violate--fcac4cc8a5", - "sectionRelativeRepoPath": "does-osquery-violate-the-new-york-employee-monitoring-law.md", - "meta": { - "category": "security", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2023-04-18", - "articleTitle": "Does osquery violate the New York employee monitoring law?" - } - }, - { - "url": "/guides/downgrade-fleet", - "title": "Downgrade Fleet", - "lastModifiedAt": 1726839805012, - "htmlId": "articles--downgrade-fleet--76de2fe679", - "sectionRelativeRepoPath": "downgrade-fleet.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "eashaw", - "authorFullName": "Eric Shaw", - "publishedOn": "2024-01-09", - "articleTitle": "Downgrade from Fleet Premium", - "description": "Learn how to downgrade from Fleet Premium." - } - }, - { - "url": "/guides/driving-company-culture-through-ai-haiku-poetry", - "title": "Driving company culture through ai haiku poetry", - "lastModifiedAt": 1726839805013, - "htmlId": "articles--driving-company-cult--52db9708d4", - "sectionRelativeRepoPath": "driving-company-culture-through-ai-haiku-poetry.md", - "meta": { - "articleTitle": "Driving company culture through AI haiku poetry", - "authorFullName": "Luke Heath", - "authorGitHubUsername": "lukeheath", - "category": "guides", - "publishedOn": "2024-04-17", - "articleImageUrl": "/images/articles/driving-company-culture-through-ai-haiku-poetry-1600x900@2x.png", - "description": "Code and verse entwine, Silicon sparks, haikus shine, Art meets design line." - } - }, - { - "url": "/securing/ebpf-the-future-of-osquery-on-linux", - "title": "Ebpf the future of osquery on linux", - "lastModifiedAt": 1726839805014, - "htmlId": "articles--ebpf-the-future-of-o--cd30e84170", - "sectionRelativeRepoPath": "ebpf-the-future-of-osquery-on-linux.md", - "meta": { - "category": "security", - "authorGitHubUsername": "zwass", - "authorFullName": "Zach Wasserman", - "publishedOn": "2021-01-25", - "articleTitle": "eBPF & the future of osquery on Linux", - "articleImageUrl": "/images/articles/ebpf-the-future-of-osquery-on-linux-cover-700x394@2x.png" - } - }, - { - "url": "/announcements/embracing-the-future-declarative-device-management", - "title": "Embracing the future declarative device management", - "lastModifiedAt": 1726839805015, - "htmlId": "articles--embracing-the-future--b3151457e1", - "sectionRelativeRepoPath": "embracing-the-future-declarative-device-management.md", - "meta": { - "category": "announcements", - "authorGitHubUsername": "spokanemac", - "authorFullName": "JD Strong", - "publishedOn": "2023-07-06", - "articleTitle": "Embracing the future: Declarative Device Management", - "articleImageUrl": "/images/articles/embracing-the-future-declarative-device-management@2x.png", - "description": "Explore the transformative impact of Declarative Device Management (DDM), Fleet, and osquery for MacAdmins." - } - }, - { - "url": "/securing/end-user-self-remediation", - "title": "End user self remediation", - "lastModifiedAt": 1726839805016, - "htmlId": "articles--end-user-self-remedi--1ebc67c784", - "sectionRelativeRepoPath": "end-user-self-remediation.md", - "meta": { - "category": "security", - "authorFullName": "Chris McGillicuddy", - "authorGitHubUsername": "chris-mcgillicuddy", - "publishedOn": "2022-12-15", - "articleTitle": "End-user self remediation: empower your employees to fix security issues with Fleet" - } - }, - { - "url": "/announcements/endpoint-managements-crucial-role-in-healthcare", - "title": "Endpoint managements crucial role in healthcare", - "lastModifiedAt": 1726839805017, - "htmlId": "articles--endpoint-managements--ec90fcd20a", - "sectionRelativeRepoPath": "endpoint-managements-crucial-role-in-healthcare.md", - "meta": { - "category": "announcements", - "authorFullName": "Alex Mitchell", - "authorGitHubUsername": "alexmitchelliii", - "publishedOn": "2024-05-24", - "articleTitle": "Endpoint management's crucial role in healthcare", - "articleImageUrl": "/images/articles/endpoint-managements-crucial-role-in-healthcare-1600x900@2x.png", - "description": "Discover how robust endpoint management is essential for securing healthcare data, ensuring compliance, and building patient trust." - } - }, - { - "url": "/guides/enforce-disk-encryption", - "title": "Enforce disk encryption", - "lastModifiedAt": 1726839805018, - "htmlId": "articles--enforce-disk-encrypt--0ab61200c1", - "sectionRelativeRepoPath": "enforce-disk-encryption.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "noahtalerman", - "authorFullName": "Noah Talerman", - "publishedOn": "2024-08-14", - "articleTitle": "Enforce disk encryption", - "description": "Learn how to enforce disk encryption on macOS and Windows hosts and manage encryption keys with Fleet Premium." - } - }, - { - "url": "/guides/enforce-os-updates", - "title": "Enforce os updates", - "lastModifiedAt": 1726839805019, - "htmlId": "articles--enforce-os-updates--0ddd6f9117", - "sectionRelativeRepoPath": "enforce-os-updates.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "noahtalerman", - "authorFullName": "Noah Talerman", - "publishedOn": "2024-08-10", - "articleTitle": "Enforce OS updates", - "description": "Learn how to manage OS updates on macOS, Windows, iOS, and iPadOS devices." - } - }, - { - "url": "/announcements/enhancing-fleets-vulnerability-management-with-vulncheck-integration", - "title": "Enhancing fleets vulnerability management with vulncheck integration", - "lastModifiedAt": 1726839805020, - "htmlId": "articles--enhancing-fleets-vul--3cc4d5cb3a", - "sectionRelativeRepoPath": "enhancing-fleets-vulnerability-management-with-vulncheck-integration.md", - "meta": { - "category": "announcements", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2024-04-23", - "articleTitle": "Enhancing Fleet's vulnerability management with VulnCheck integration", - "articleImageUrl": "/images/articles/enhancing-fleets-vulnerability-management-with-vulncheck-integration-1600x900@2x.png" - } - }, - { - "url": "/announcements/enhancing-k-12-cybersecurity-with-fcc-funds-and-fleet", - "title": "Enhancing k 12 cybersecurity with fcc funds and Fleet", - "lastModifiedAt": 1726839805021, - "htmlId": "articles--enhancing-k-12-cyber--90c76b24ef", - "sectionRelativeRepoPath": "enhancing-k-12-cybersecurity-with-fcc-funds-and-fleet.md", - "meta": { - "category": "announcements", - "authorFullName": "Alex Mitchell", - "authorGitHubUsername": "alexmitchelliii", - "publishedOn": "2024-07-25", - "articleTitle": "Enhancing K-12 cybersecurity with FCC funds and Fleet", - "articleImageUrl": "/images/articles/enhancing-k-12-cybersecurity-with-fcc-funds-and-fleet-1600x900@2x.png" - } - }, - { - "url": "/guides/enroll-hosts", - "title": "Enroll hosts", - "lastModifiedAt": 1726839805023, - "htmlId": "articles--enroll-hosts--72fecd86ff", - "sectionRelativeRepoPath": "enroll-hosts.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "noahtalerman", - "authorFullName": "Noah Talerman", - "publishedOn": "2024-08-08", - "articleTitle": "Enroll hosts", - "description": "Learn how to enroll hosts to Fleet." - } - }, - { - "url": "/guides/enrolling-a-digital-ocean-droplet-on-a-fleet-instance", - "title": "Enrolling a digital ocean droplet on a Fleet instance", - "lastModifiedAt": 1726839805025, - "htmlId": "articles--enrolling-a-digital---6fbc5a61b0", - "sectionRelativeRepoPath": "enrolling-a-digital-ocean-droplet-on-a-fleet-instance.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "DominusKelvin", - "authorFullName": "Kelvin Omereshone", - "publishedOn": "2022-05-26", - "articleTitle": "Enrolling a DigitalOcean Droplet on a Fleet instance", - "articleImageUrl": "/images/articles/enrolling-a-digitalocean-droplet-server-on-a-fleet-instance-cover-1600x900@2x.jpg" - } - }, - { - "url": "/podcasts/expeditioners-bradley-chambers", - "title": "Expeditioners bradley chambers", - "lastModifiedAt": 1726839805026, - "htmlId": "articles--expeditioners-bradle--434ed8f62f", - "sectionRelativeRepoPath": "expeditioners-bradley-chambers.md", - "meta": { - "category": "podcasts", - "authorGitHubUsername": "zwass", - "authorFullName": "Zach Wasserman", - "publishedOn": "2023-07-20", - "articleTitle": "ExpedITioners podcast with Bradley Chambers", - "articleImageUrl": "/images/articles/expeditioners-podcast-ep1-1600x900@2x.png" - } - }, - { - "url": "/podcasts/expeditioners-charles-edge", - "title": "Expeditioners charles edge", - "lastModifiedAt": 1726839805027, - "htmlId": "articles--expeditioners-charle--078e2e677d", - "sectionRelativeRepoPath": "expeditioners-charles-edge.md", - "meta": { - "category": "podcasts", - "authorGitHubUsername": "zwass", - "authorFullName": "Zach Wasserman", - "publishedOn": "2023-10-23", - "articleTitle": "ExpedITioners podcast with Charles Edge", - "articleImageUrl": "/images/articles/expeditioners-podcast-ep5-1600x900@2x.jpg" - } - }, - { - "url": "/podcasts/expeditioners-huxley-barbee", - "title": "Expeditioners huxley barbee", - "lastModifiedAt": 1726839805027, - "htmlId": "articles--expeditioners-huxley--59793f39c1", - "sectionRelativeRepoPath": "expeditioners-huxley-barbee.md", - "meta": { - "category": "podcasts", - "authorGitHubUsername": "zwass", - "authorFullName": "Zach Wasserman", - "publishedOn": "2024-01-30", - "articleTitle": "ExpedITioners podcast with Huxley Barbee", - "articleImageUrl": "/images/articles/expeditioners-podcast-ep8-1600x900@2x.jpg" - } - }, - { - "url": "/podcasts/expeditioners-jeff-chao", - "title": "Expeditioners jeff chao", - "lastModifiedAt": 1726839805028, - "htmlId": "articles--expeditioners-jeff-c--69f6b2fce1", - "sectionRelativeRepoPath": "expeditioners-jeff-chao.md", - "meta": { - "category": "podcasts", - "authorGitHubUsername": "zwass", - "authorFullName": "Zach Wasserman", - "publishedOn": "2023-11-15", - "articleTitle": "ExpedITioners podcast with Jeff Chao", - "articleImageUrl": "/images/articles/expeditioners-podcast-ep6-1600x900@2x.jpg" - } - }, - { - "url": "/podcasts/expeditioners-john-reynolds", - "title": "Expeditioners john reynolds", - "lastModifiedAt": 1726839805029, - "htmlId": "articles--expeditioners-john-r--2abfb47f0e", - "sectionRelativeRepoPath": "expeditioners-john-reynolds.md", - "meta": { - "category": "podcasts", - "authorGitHubUsername": "zwass", - "authorFullName": "Zach Wasserman", - "publishedOn": "2023-09-21", - "articleTitle": "ExpedITioners podcast with John Reynolds", - "articleImageUrl": "/images/articles/expeditioners-podcast-ep4-1600x900@2x.jpg" - } - }, - { - "url": "/podcasts/expeditioners-niels-hofmans", - "title": "Expeditioners niels hofmans", - "lastModifiedAt": 1726839805030, - "htmlId": "articles--expeditioners-niels---d1c8e645af", - "sectionRelativeRepoPath": "expeditioners-niels-hofmans.md", - "meta": { - "category": "podcasts", - "authorGitHubUsername": "zwass", - "authorFullName": "Zach Wasserman", - "publishedOn": "2023-08-22", - "articleTitle": "ExpedITioners podcast with Niels Hofmans", - "articleImageUrl": "/images/articles/expeditioners-podcast-ep2-1600x900@2x.jpg" - } - }, - { - "url": "/podcasts/expeditioners-podcast-with-marcus-ransom", - "title": "Expeditioners podcast with marcus ransom", - "lastModifiedAt": 1726839805031, - "htmlId": "articles--expeditioners-podcas--98c32a782f", - "sectionRelativeRepoPath": "expeditioners-podcast-with-marcus-ransom.md", - "meta": { - "category": "podcasts", - "authorGitHubUsername": "zwass", - "authorFullName": "Zach Wasserman", - "publishedOn": "2023-12-11", - "articleTitle": "ExpedITioners podcast with Marcus Ransom", - "articleImageUrl": "/images/articles/expeditioners-podcast-ep7-1600x900@2x.jpg" - } - }, - { - "url": "/podcasts/expeditioners-rich-trouton", - "title": "Expeditioners rich trouton", - "lastModifiedAt": 1726839805032, - "htmlId": "articles--expeditioners-rich-t--c394f4ba38", - "sectionRelativeRepoPath": "expeditioners-rich-trouton.md", - "meta": { - "category": "podcasts", - "authorGitHubUsername": "zwass", - "authorFullName": "Zach Wasserman", - "publishedOn": "2023-08-31", - "articleTitle": "ExpedITioners podcast with Rich Trouton", - "articleImageUrl": "/images/articles/expeditioners-podcast-ep3-1600x900@2x.jpg" - } - }, - { - "url": "/guides/filtering-software-by-vulnerability", - "title": "Filtering software by vulnerability", - "lastModifiedAt": 1726839805033, - "htmlId": "articles--filtering-software-b--900d8b7307", - "sectionRelativeRepoPath": "filtering-software-by-vulnerability.md", - "meta": { - "articleTitle": "Filtering software by vulnerability in Fleet", - "authorFullName": "Tim Lee", - "authorGitHubUsername": "mostlikelee", - "category": "guides", - "publishedOn": "2024-08-30", - "articleImageUrl": "/images/articles/discovering-geacon-using-fleet-1600x900@2x.jpg", - "description": "Filter software by vulnerability in Fleet to prioritize critical patches and enhance your organization's security posture." - } - }, - { - "url": "/releases/fleet-3.10.0", - "title": "Fleet 3.10.0", - "lastModifiedAt": 1726839805034, - "htmlId": "articles--fleet-3100--09d2002dcd", - "sectionRelativeRepoPath": "fleet-3.10.0.md", - "meta": { - "category": "releases", - "authorFullName": "Noah Talerman", - "authorGitHubUsername": "noahtalerman", - "publishedOn": "2021-04-01", - "articleTitle": "Fleet 3.10.0 released with agent auto-updates beta", - "articleImageUrl": "/images/articles/fleet-3.10.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-3.11.0", - "title": "Fleet 3.11.0", - "lastModifiedAt": 1726839805035, - "htmlId": "articles--fleet-3110--ad56a464f5", - "sectionRelativeRepoPath": "fleet-3.11.0.md", - "meta": { - "category": "releases", - "authorFullName": "Noah Talerman", - "authorGitHubUsername": "noahtalerman", - "publishedOn": "2021-04-29", - "articleTitle": "Fleet 3.11.0 released with software inventory", - "articleImageUrl": "/images/articles/fleet-3.11.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-3.12.0", - "title": "Fleet 3.12.0", - "lastModifiedAt": 1726839805036, - "htmlId": "articles--fleet-3120--8f3c795b51", - "sectionRelativeRepoPath": "fleet-3.12.0.md", - "meta": { - "category": "releases", - "authorFullName": "Noah Talerman", - "authorGitHubUsername": "noahtalerman", - "publishedOn": "2021-05-20", - "articleTitle": "Fleet 3.12.0", - "articleImageUrl": "/images/articles/fleet-3.12.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-3.5.0", - "title": "Fleet 3.5.0", - "lastModifiedAt": 1726839805037, - "htmlId": "articles--fleet-350--0912885a04", - "sectionRelativeRepoPath": "fleet-3.5.0.md", - "meta": { - "category": "releases", - "authorFullName": "Noah Talerman", - "authorGitHubUsername": "noahtalerman", - "publishedOn": "2020-12-12", - "articleTitle": "Fleet 3.5.0", - "articleImageUrl": "/images/articles/fleet-3.5.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-3.6.0", - "title": "Fleet 3.6.0", - "lastModifiedAt": 1726839805039, - "htmlId": "articles--fleet-360--b415aaaf59", - "sectionRelativeRepoPath": "fleet-3.6.0.md", - "meta": { - "category": "releases", - "authorFullName": "Noah Talerman", - "authorGitHubUsername": "noahtalerman", - "publishedOn": "2021-01-09", - "articleTitle": "Fleet 3.6.0", - "articleImageUrl": "/images/articles/fleet-3.6.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-3.13.0", - "title": "Fleet 3.13.0", - "lastModifiedAt": 1726839805041, - "htmlId": "articles--fleet-3130--6a4b26ee04", - "sectionRelativeRepoPath": "fleet-3.13.0.md", - "meta": { - "category": "releases", - "authorFullName": "Noah Talerman", - "authorGitHubUsername": "noahtalerman", - "publishedOn": "2021-06-04", - "articleTitle": "Fleet 3.13.0", - "articleImageUrl": "/images/articles/fleet-3.13.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-3.7.1", - "title": "Fleet 3.7.1", - "lastModifiedAt": 1726839805042, - "htmlId": "articles--fleet-371--a3099c00cb", - "sectionRelativeRepoPath": "fleet-3.7.1.md", - "meta": { - "category": "releases", - "authorFullName": "Noah Talerman", - "authorGitHubUsername": "noahtalerman", - "publishedOn": "2021-02-04", - "articleTitle": "Fleet 3.7.1", - "articleImageUrl": "/images/articles/fleet-3.7.1-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-3.8.0", - "title": "Fleet 3.8.0", - "lastModifiedAt": 1726839805042, - "htmlId": "articles--fleet-380--681019a9ad", - "sectionRelativeRepoPath": "fleet-3.8.0.md", - "meta": { - "category": "releases", - "authorFullName": "Noah Talerman", - "authorGitHubUsername": "noahtalerman", - "publishedOn": "2021-02-26", - "articleTitle": "Fleet 3.8.0", - "articleImageUrl": "/images/articles/fleet-3.8.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-3.9.0", - "title": "Fleet 3.9.0", - "lastModifiedAt": 1726839805043, - "htmlId": "articles--fleet-390--7ceb277f2f", - "sectionRelativeRepoPath": "fleet-3.9.0.md", - "meta": { - "category": "releases", - "authorFullName": "Noah Talerman", - "authorGitHubUsername": "noahtalerman", - "publishedOn": "2021-03-10", - "articleTitle": "Fleet 3.9.0", - "articleImageUrl": "/images/articles/fleet-3.9.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.0.0", - "title": "Fleet 4.0.0", - "lastModifiedAt": 1726839805044, - "htmlId": "articles--fleet-400--33d96e46d6", - "sectionRelativeRepoPath": "fleet-4.0.0.md", - "meta": { - "category": "releases", - "authorFullName": "Noah Talerman", - "authorGitHubUsername": "noahtalerman", - "publishedOn": "2021-06-30", - "articleTitle": "Fleet 4.0.0 released with Role-based access control and Teams features", - "articleImageUrl": "/images/articles/fleet-4.0.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.1.0", - "title": "Fleet 4.1.0", - "lastModifiedAt": 1726839805045, - "htmlId": "articles--fleet-410--2f2a288a79", - "sectionRelativeRepoPath": "fleet-4.1.0.md", - "meta": { - "category": "releases", - "authorFullName": "Noah Talerman", - "authorGitHubUsername": "noahtalerman", - "publishedOn": "2021-07-27", - "articleTitle": "Fleet 4.1.0 released with Schedule and Activity feed features", - "articleImageUrl": "/images/articles/fleet-4.1.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.10.0", - "title": "Fleet 4.10.0", - "lastModifiedAt": 1726839805046, - "htmlId": "articles--fleet-4100--dd259b5e42", - "sectionRelativeRepoPath": "fleet-4.10.0.md", - "meta": { - "category": "releases", - "authorFullName": "Mike Thomas", - "authorGitHubUsername": "mike-j-thomas", - "publishedOn": "2022-02-14", - "articleTitle": "Fleet 4.10.0 brings new features and improvements for vulnerability analysts.", - "articleImageUrl": "/images/articles/fleet-4.10.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.12.0", - "title": "Fleet 4.12.0", - "lastModifiedAt": 1726839805047, - "htmlId": "articles--fleet-4120--150c6e2731", - "sectionRelativeRepoPath": "fleet-4.12.0.md", - "meta": { - "category": "releases", - "authorFullName": "Mike Thomas", - "authorGitHubUsername": "mike-j-thomas", - "publishedOn": "2022-03-25", - "articleTitle": "Fleet 4.12.0 | Platform-specific policies, and improved query results", - "articleImageUrl": "/images/articles/fleet-4.12.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.13.0", - "title": "Fleet 4.13.0", - "lastModifiedAt": 1726839805047, - "htmlId": "articles--fleet-4130--771b1f08ac", - "sectionRelativeRepoPath": "fleet-4.13.0.md", - "meta": { - "category": "releases", - "authorFullName": "Fleet", - "authorGitHubUsername": "fleetdm", - "publishedOn": "2022-04-19", - "articleTitle": "Fleet 4.13.0 | Security fixes, policy automations for teams, and aggregated macOS versions for MacAdmins.", - "articleImageUrl": "/images/articles/fleet-4.13.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.11.0", - "title": "Fleet 4.11.0", - "lastModifiedAt": 1726839805048, - "htmlId": "articles--fleet-4110--a057b8896f", - "sectionRelativeRepoPath": "fleet-4.11.0.md", - "meta": { - "category": "releases", - "authorFullName": "Mike Thomas", - "authorGitHubUsername": "mike-j-thomas", - "publishedOn": "2022-03-07", - "articleTitle": "Fleet 4.11.0 brings impact clarity, improvements to vulnerability processing, and performance updates.", - "articleImageUrl": "/images/articles/fleet-4.11.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.14.0", - "title": "Fleet 4.14.0", - "lastModifiedAt": 1726839805049, - "htmlId": "articles--fleet-4140--e58b7a34f3", - "sectionRelativeRepoPath": "fleet-4.14.0.md", - "meta": { - "category": "releases", - "authorFullName": "Kathy Satterlee", - "authorGitHubUsername": "ksatter", - "publishedOn": "2022-05-06", - "articleTitle": "Fleet 4.14.0 adds beta support for automatic ticket creation and improves the live query experience.", - "articleImageUrl": "/images/articles/fleet-4.14.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.16.0", - "title": "Fleet 4.16.0", - "lastModifiedAt": 1726839805051, - "htmlId": "articles--fleet-4160--ac79cd8c59", - "sectionRelativeRepoPath": "fleet-4.16.0.md", - "meta": { - "category": "releases", - "authorFullName": "Kathy Satterlee", - "authorGitHubUsername": "ksatter", - "publishedOn": "2022-06-16", - "articleTitle": "Fleet 4.16.0 | more customization, beefed up vuln management, Jira added to integrations.", - "articleImageUrl": "/images/articles/fleet-4.16.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.17.0", - "title": "Fleet 4.17.0", - "lastModifiedAt": 1726839805052, - "htmlId": "articles--fleet-4170--a276e12e2a", - "sectionRelativeRepoPath": "fleet-4.17.0.md", - "meta": { - "category": "releases", - "authorFullName": "Kathy Satterlee", - "authorGitHubUsername": "ksatter", - "publishedOn": "2022-07-11", - "articleTitle": "Fleet 4.17.0 | Better osquery management, user engagement, improved host vitals.", - "articleImageUrl": "/images/articles/fleet-4.17.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.15.0", - "title": "Fleet 4.15.0", - "lastModifiedAt": 1726839805053, - "htmlId": "articles--fleet-4150--3865641c1c", - "sectionRelativeRepoPath": "fleet-4.15.0.md", - "meta": { - "category": "releases", - "authorFullName": "Kathy Satterlee", - "authorGitHubUsername": "ksatter", - "publishedOn": "2022-05-30", - "articleTitle": "Fleet 4.15.0 adds beta support for Self-service, Scope transparency, and brings Zendesk to the party.", - "articleImageUrl": "/images/articles/fleet-4.15.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.18.0", - "title": "Fleet 4.18.0", - "lastModifiedAt": 1726839805054, - "htmlId": "articles--fleet-4180--9e4ce6c31b", - "sectionRelativeRepoPath": "fleet-4.18.0.md", - "meta": { - "category": "releases", - "authorFullName": "Kathy Satterlee", - "authorGitHubUsername": "ksatter", - "publishedOn": "2022-08-03", - "articleTitle": "Fleet 4.18.0 | Better security and user messaging in Fleet Desktop", - "articleImageUrl": "/images/articles/fleet-4.18.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.19.0", - "title": "Fleet 4.19.0", - "lastModifiedAt": 1726839805055, - "htmlId": "articles--fleet-4190--450188c15f", - "sectionRelativeRepoPath": "fleet-4.19.0.md", - "meta": { - "category": "releases", - "authorFullName": "Noah Talerman", - "authorGitHubUsername": "noahtalerman", - "publishedOn": "2022-08-22", - "articleTitle": "Fleet 4.19.0 | Just-in-time (JIT) user provisioning, remaining disk space, aggregate Windows and mobile device management (MDM) data", - "articleImageUrl": "/images/articles/fleet-4.19.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.2.0", - "title": "Fleet 4.2.0", - "lastModifiedAt": 1726839805055, - "htmlId": "articles--fleet-420--ead484f1f9", - "sectionRelativeRepoPath": "fleet-4.2.0.md", - "meta": { - "category": "releases", - "authorFullName": "Noah Talerman", - "authorGitHubUsername": "noahtalerman", - "publishedOn": "2021-08-12", - "articleTitle": "Fleet 4.2.0", - "articleImageUrl": "/images/articles/fleet-4.2.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.20.0", - "title": "Fleet 4.20.0", - "lastModifiedAt": 1726839805057, - "htmlId": "articles--fleet-4200--3a3e9234b6", - "sectionRelativeRepoPath": "fleet-4.20.0.md", - "meta": { - "category": "releases", - "authorFullName": "Noah Talerman", - "authorGitHubUsername": "noahtalerman", - "publishedOn": "2022-09-09", - "articleTitle": "Fleet 4.20.0 | Aggregate Munki issues, test features on canary teams, improved macOS vulnerability detection", - "articleImageUrl": "/images/articles/fleet-4.20.0-1600x900.jpg" - } - }, - { - "url": "/releases/fleet-4.21.0", - "title": "Fleet 4.21.0", - "lastModifiedAt": 1726839805058, - "htmlId": "articles--fleet-4210--ef1f69ba72", - "sectionRelativeRepoPath": "fleet-4.21.0.md", - "meta": { - "category": "releases", - "authorFullName": "Chris McGillicuddy", - "authorGitHubUsername": "chris-mcgillicuddy", - "publishedOn": "2022-10-05", - "articleTitle": "Fleet 4.21.0 | Validate config and teams YAML documents, manage osquery flags remotely with Orbit, view team and global policy compliance", - "articleImageUrl": "/images/articles/fleet-4.21.0-1600x900@2x.jpeg" - } - }, - { - "url": "/releases/fleet-4.22.0", - "title": "Fleet 4.22.0", - "lastModifiedAt": 1726839805059, - "htmlId": "articles--fleet-4220--79ccc66c3c", - "sectionRelativeRepoPath": "fleet-4.22.0.md", - "meta": { - "category": "releases", - "authorFullName": "Chris McGillicuddy", - "authorGitHubUsername": "chris-mcgillicuddy", - "publishedOn": "2022-10-21", - "articleTitle": "Fleet 4.22.0 | Easier access to host information, better query console UX, and clearer display names", - "articleImageUrl": "/images/articles/fleet-4.22.0-cover-800x450@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.23.0", - "title": "Fleet 4.23.0", - "lastModifiedAt": 1726839805060, - "htmlId": "articles--fleet-4230--653ee52499", - "sectionRelativeRepoPath": "fleet-4.23.0.md", - "meta": { - "category": "releases", - "authorFullName": "Noah Talerman", - "authorGitHubUsername": "noahtalerman", - "publishedOn": "2022-11-14", - "articleTitle": "Fleet 4.23.0 | Better insight into inherited policies, improved host vitals, and more configuration visibility", - "articleImageUrl": "/images/articles/fleet-4.23.0-800x450@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.24.0", - "title": "Fleet 4.24.0", - "lastModifiedAt": 1726839805061, - "htmlId": "articles--fleet-4240--19516bb4b8", - "sectionRelativeRepoPath": "fleet-4.24.0.md", - "meta": { - "category": "releases", - "authorFullName": "Noah Talerman", - "authorGitHubUsername": "noahtalerman", - "publishedOn": "2022-12-06", - "articleTitle": "Fleet 4.24.0 | Live query notifications and navigation improvements", - "articleImageUrl": "/images/articles/fleet-4.24.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.25.0", - "title": "Fleet 4.25.0", - "lastModifiedAt": 1726839805063, - "htmlId": "articles--fleet-4250--9127fac1f2", - "sectionRelativeRepoPath": "fleet-4.25.0.md", - "meta": { - "category": "releases", - "authorFullName": "Noah Talerman", - "authorGitHubUsername": "noahtalerman", - "publishedOn": "2023-01-03", - "articleTitle": "Fleet 4.25.0 | Extra security and MDM visibility", - "articleImageUrl": "/images/articles/fleet-4.25.0-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.26.0", - "title": "Fleet 4.26.0", - "lastModifiedAt": 1726839805064, - "htmlId": "articles--fleet-4260--3ecc26a58f", - "sectionRelativeRepoPath": "fleet-4.26.0.md", - "meta": { - "category": "releases", - "authorFullName": "Noah Talerman", - "authorGitHubUsername": "noahtalerman", - "publishedOn": "2023-01-16", - "articleTitle": "Fleet 4.26.0 | Easier osquery extensions, external audit log destinations, and cleaner data lakes", - "articleImageUrl": "/images/articles/fleet-4.26.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.27.0", - "title": "Fleet 4.27.0", - "lastModifiedAt": 1726839805065, - "htmlId": "articles--fleet-4270--5def591f64", - "sectionRelativeRepoPath": "fleet-4.27.0.md", - "meta": { - "category": "releases", - "authorFullName": "Noah Talerman", - "authorGitHubUsername": "noahtalerman", - "publishedOn": "2023-02-14", - "articleTitle": "Fleet 4.27.0 | Improved access management and improved search filters", - "articleImageUrl": "/images/articles/fleet-4.27.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.28.0", - "title": "Fleet 4.28.0", - "lastModifiedAt": 1726839805066, - "htmlId": "articles--fleet-4280--52f2441fa4", - "sectionRelativeRepoPath": "fleet-4.28.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2023-02-28", - "articleTitle": "Fleet 4.28.0 | CIS benchmarks for Ventura", - "articleImageUrl": "/images/articles/fleet-4.28.0-800x450@2x.png" - } - }, - { - "url": "/releases/fleet-4.29.0", - "title": "Fleet 4.29.0", - "lastModifiedAt": 1726839805067, - "htmlId": "articles--fleet-4290--507fc72ef3", - "sectionRelativeRepoPath": "fleet-4.29.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2023-03-22", - "articleTitle": "Fleet 4.29.0 | SSO provides JIT Fleet user roles", - "articleImageUrl": "/images/articles/fleet-4.29.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.3.0", - "title": "Fleet 4.3.0", - "lastModifiedAt": 1726839805068, - "htmlId": "articles--fleet-430--f231d44352", - "sectionRelativeRepoPath": "fleet-4.3.0.md", - "meta": { - "category": "releases", - "authorFullName": "Mike Thomas", - "authorGitHubUsername": "mike-j-thomas", - "publishedOn": "2021-09-07", - "articleTitle": "Fleet 4.3.0", - "articleImageUrl": "/images/articles/fleet-4.3.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.30.0", - "title": "Fleet 4.30.0", - "lastModifiedAt": 1726839805069, - "htmlId": "articles--fleet-4300--0e053dac25", - "sectionRelativeRepoPath": "fleet-4.30.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2023-04-11", - "articleTitle": "Fleet 4.30.0 | MDM public beta, Observer+ role, Vulnerability publication dates", - "articleImageUrl": "/images/articles/fleet-4.30.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.31.0", - "title": "Fleet 4.31.0", - "lastModifiedAt": 1726839805071, - "htmlId": "articles--fleet-4310--439ea795b4", - "sectionRelativeRepoPath": "fleet-4.31.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2023-05-01", - "articleTitle": "Fleet 4.31.0 | MDM enrollment workflow, API user role.", - "articleImageUrl": "/images/articles/fleet-4.31.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.32.0", - "title": "Fleet 4.32.0", - "lastModifiedAt": 1726839805073, - "htmlId": "articles--fleet-4320--221d90689c", - "sectionRelativeRepoPath": "fleet-4.32.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2023-05-24", - "articleTitle": "Fleet 4.32.0 | User migration, customizing macOS Setup Assistant.", - "articleImageUrl": "/images/articles/fleet-4.32.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.33.0", - "title": "Fleet 4.33.0", - "lastModifiedAt": 1726839805074, - "htmlId": "articles--fleet-4330--3b965c130a", - "sectionRelativeRepoPath": "fleet-4.33.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2023-06-13", - "articleTitle": "Fleet 4.33.0 | ChromeOS support, new verified status", - "articleImageUrl": "/images/articles/fleet-4.33.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.34.0", - "title": "Fleet 4.34.0", - "lastModifiedAt": 1726839805075, - "htmlId": "articles--fleet-4340--aab74d16d2", - "sectionRelativeRepoPath": "fleet-4.34.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2023-07-12", - "articleTitle": "Fleet 4.34.0 | ChromeOS tables, CIS Benchmark load testing", - "articleImageUrl": "/images/articles/fleet-4.34.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.35.0", - "title": "Fleet 4.35.0", - "lastModifiedAt": 1726839805076, - "htmlId": "articles--fleet-4350--d4921e1140", - "sectionRelativeRepoPath": "fleet-4.35.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2023-08-01", - "articleTitle": "Fleet 4.35.0 | Improvements and bug fixes.", - "articleImageUrl": "/images/articles/fleet-4.35.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.37.0", - "title": "Fleet 4.37.0", - "lastModifiedAt": 1726839805077, - "htmlId": "articles--fleet-4370--56524e6b70", - "sectionRelativeRepoPath": "fleet-4.37.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2023-09-07", - "articleTitle": "Fleet 4.37.0 | Remote script execution & Puppet support.", - "articleImageUrl": "/images/articles/fleet-4.37.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.36.0", - "title": "Fleet 4.36.0", - "lastModifiedAt": 1726839805078, - "htmlId": "articles--fleet-4360--0167b9704b", - "sectionRelativeRepoPath": "fleet-4.36.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2023-08-18", - "articleTitle": "Fleet 4.36.0 | Saved and scheduled queries merge.", - "articleImageUrl": "/images/articles/fleet-4.36.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.38.0", - "title": "Fleet 4.38.0", - "lastModifiedAt": 1726839805080, - "htmlId": "articles--fleet-4380--8522df1a2e", - "sectionRelativeRepoPath": "fleet-4.38.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2023-09-25", - "articleTitle": "Fleet 4.38.0 | Profile redelivery, NVD details, and custom extension label support.", - "articleImageUrl": "/images/articles/fleet-4.38.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.39.0", - "title": "Fleet 4.39.0", - "lastModifiedAt": 1726839805081, - "htmlId": "articles--fleet-4390--ad9a535d1c", - "sectionRelativeRepoPath": "fleet-4.39.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2023-10-26", - "articleTitle": "Fleet 4.39.0 | Sonoma support, script library, query reports.", - "articleImageUrl": "/images/articles/fleet-4.39.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.4.0", - "title": "Fleet 4.4.0", - "lastModifiedAt": 1726839805082, - "htmlId": "articles--fleet-440--24061a1eff", - "sectionRelativeRepoPath": "fleet-4.4.0.md", - "meta": { - "category": "releases", - "authorFullName": "Mike Thomas", - "authorGitHubUsername": "mike-j-thomas", - "publishedOn": "2021-10-07", - "articleTitle": "Fleet 4.4.0 releases aggregated software inventory, team policies, and improved team scheduling", - "articleImageUrl": "/images/articles/fleet-4.4.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.40.0", - "title": "Fleet 4.40.0", - "lastModifiedAt": 1726839805083, - "htmlId": "articles--fleet-4400--53f1a0954b", - "sectionRelativeRepoPath": "fleet-4.40.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2023-11-06", - "articleTitle": "Fleet 4.40.0 | More Data, Rapid Security Response, CIS Benchmark updates.", - "articleImageUrl": "/images/articles/fleet-4.40.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.41.0", - "title": "Fleet 4.41.0", - "lastModifiedAt": 1726839805084, - "htmlId": "articles--fleet-4410--f4c37d963b", - "sectionRelativeRepoPath": "fleet-4.41.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2023-11-28", - "articleTitle": "Fleet 4.41.0 | NVD API 2.0, Windows script library.", - "articleImageUrl": "/images/articles/fleet-4.41.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.42.0", - "title": "Fleet 4.42.0", - "lastModifiedAt": 1726839805086, - "htmlId": "articles--fleet-4420--8d6641fa28", - "sectionRelativeRepoPath": "fleet-4.42.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2023-12-21", - "articleTitle": "Fleet 4.42.0 | Query performance reporting, host targeting improvements.", - "articleImageUrl": "/images/articles/fleet-4.42.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.43.0", - "title": "Fleet 4.43.0", - "lastModifiedAt": 1726839805087, - "htmlId": "articles--fleet-4430--296526b139", - "sectionRelativeRepoPath": "fleet-4.43.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2024-01-09", - "articleTitle": "Fleet 4.43.0 | Query performance reporting, host targeting improvements.", - "articleImageUrl": "/images/articles/fleet-4.43.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.44.0", - "title": "Fleet 4.44.0", - "lastModifiedAt": 1726839805089, - "htmlId": "articles--fleet-4440--e0c9504248", - "sectionRelativeRepoPath": "fleet-4.44.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2024-02-05", - "articleTitle": "Fleet 4.44.0 | Script execution, host expiry, and host targeting improvements.", - "articleImageUrl": "/images/articles/fleet-4.44.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.45.0", - "title": "Fleet 4.45.0", - "lastModifiedAt": 1726839805090, - "htmlId": "articles--fleet-4450--525bed4841", - "sectionRelativeRepoPath": "fleet-4.45.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2024-02-21", - "articleTitle": "Fleet 4.45.0 | Remote lock, Linux script library, osquery storage location.", - "articleImageUrl": "/images/articles/fleet-4.45.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.46.0", - "title": "Fleet 4.46.0", - "lastModifiedAt": 1726839805091, - "htmlId": "articles--fleet-4460--2bc79fbeb9", - "sectionRelativeRepoPath": "fleet-4.46.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2024-02-26", - "articleTitle": "Fleet 4.46.0 | Automatic SCEP certificate renewal.", - "articleImageUrl": "/images/articles/fleet-4.46.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.47.0", - "title": "Fleet 4.47.0", - "lastModifiedAt": 1726839805092, - "htmlId": "articles--fleet-4470--d61e2e7199", - "sectionRelativeRepoPath": "fleet-4.47.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2024-03-12", - "articleTitle": "Fleet 4.47.0 | Cross-platform remote wipe, vulnerabilities page, and scripting improvements.", - "articleImageUrl": "/images/articles/fleet-4.47.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.48.0", - "title": "Fleet 4.48.0", - "lastModifiedAt": 1726839805094, - "htmlId": "articles--fleet-4480--ecbe7beab5", - "sectionRelativeRepoPath": "fleet-4.48.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2024-04-03", - "articleTitle": "Fleet 4.48.0 | IdP local account creation, VS Code extensions.", - "articleImageUrl": "/images/articles/fleet-4.48.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.49.0", - "title": "Fleet 4.49.0", - "lastModifiedAt": 1726839805095, - "htmlId": "articles--fleet-4490--c90f5fc656", - "sectionRelativeRepoPath": "fleet-4.49.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2024-04-23", - "articleTitle": "Fleet 4.49.0 | VulnCheck's NVD++, device health API, fleetd data parsing.", - "articleImageUrl": "/images/articles/fleet-4.49.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.5.0", - "title": "Fleet 4.5.0", - "lastModifiedAt": 1726839805096, - "htmlId": "articles--fleet-450--2c474c8040", - "sectionRelativeRepoPath": "fleet-4.5.0.md", - "meta": { - "category": "releases", - "authorFullName": "Mike Thomas", - "authorGitHubUsername": "mike-j-thomas", - "publishedOn": "2021-11-02", - "articleTitle": "Fleet 4.5.0 with new team admin role, live OS compatibility checking, query performance impact, and a new-look dashboard", - "articleImageUrl": "/images/articles/fleet-4.5.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.50.0", - "title": "Fleet 4.50.0", - "lastModifiedAt": 1726839805098, - "htmlId": "articles--fleet-4500--44757c8700", - "sectionRelativeRepoPath": "fleet-4.50.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2024-05-22", - "articleTitle": "Fleet 4.50.0 | Security agent deployment, AI descriptions, and Mac Admins SOFA support.", - "articleImageUrl": "/images/articles/fleet-4.50.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.53.0", - "title": "Fleet 4.53.0", - "lastModifiedAt": 1726839805100, - "htmlId": "articles--fleet-4530--1cc540fb24", - "sectionRelativeRepoPath": "fleet-4.53.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2024-06-25", - "articleTitle": "Fleet 4.53.0 | Better vuln matching, multi-issue hosts, & `fleetd` logs as tables", - "articleImageUrl": "/images/articles/fleet-4.53.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.54.0", - "title": "Fleet 4.54.0", - "lastModifiedAt": 1726839805101, - "htmlId": "articles--fleet-4540--11b1c848f2", - "sectionRelativeRepoPath": "fleet-4.54.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2024-07-17", - "articleTitle": "Fleet 4.54.0 | Target hosts via label exclusion, script execution time.", - "articleImageUrl": "/images/articles/fleet-4.54.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.51.0", - "title": "Fleet 4.51.0", - "lastModifiedAt": 1726839805102, - "htmlId": "articles--fleet-4510--7274f6fa9d", - "sectionRelativeRepoPath": "fleet-4.51.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2024-06-10", - "articleTitle": "Fleet 4.51.0 | Global activity webhook, macOS TCC table, and software self-service.", - "articleImageUrl": "/images/articles/fleet-4.51.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.55.0", - "title": "Fleet 4.55.0", - "lastModifiedAt": 1726839805106, - "htmlId": "articles--fleet-4550--f7134a8007", - "sectionRelativeRepoPath": "fleet-4.55.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2024-08-09", - "articleTitle": "Fleet 4.55.0 | MySQL 8, arm64 support, FileVault improvements, VPP support.", - "articleImageUrl": "/images/articles/fleet-4.55.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.56.0", - "title": "Fleet 4.56.0", - "lastModifiedAt": 1726839805108, - "htmlId": "articles--fleet-4560--6f2f9c6451", - "sectionRelativeRepoPath": "fleet-4.56.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2024-09-07", - "articleTitle": "Fleet 4.56.0 | Enhanced MDM migration, Exact CVE Search, and Self-Service VPP Apps.", - "articleImageUrl": "/images/articles/fleet-4.56.0-1600x900@2x.png" - } - }, - { - "url": "/releases/fleet-4.6.0", - "title": "Fleet 4.6.0", - "lastModifiedAt": 1726839805109, - "htmlId": "articles--fleet-460--d71c3386e5", - "sectionRelativeRepoPath": "fleet-4.6.0.md", - "meta": { - "category": "releases", - "authorFullName": "Mike Thomas", - "authorGitHubUsername": "mike-j-thomas", - "publishedOn": "2021-11-19", - "articleTitle": "Fleet 4.6.0 with osquery installer, enroll secret management, and improved host vitals", - "articleImageUrl": "/images/articles/fleet-4.6.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.7.0", - "title": "Fleet 4.7.0", - "lastModifiedAt": 1726839805110, - "htmlId": "articles--fleet-470--f6d85e866c", - "sectionRelativeRepoPath": "fleet-4.7.0.md", - "meta": { - "category": "releases", - "authorFullName": "Mike Thomas", - "authorGitHubUsername": "mike-j-thomas", - "publishedOn": "2021-12-14", - "articleTitle": "Does Fleet 4.7.0 bring more power to your osquery compliance policies? Yes.", - "articleImageUrl": "/images/articles/fleet-4.7.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.8.0", - "title": "Fleet 4.8.0", - "lastModifiedAt": 1726839805111, - "htmlId": "articles--fleet-480--e0296e324b", - "sectionRelativeRepoPath": "fleet-4.8.0.md", - "meta": { - "category": "releases", - "authorFullName": "Drew Baker", - "authorGitHubUsername": "Drew-P-drawers", - "publishedOn": "2021-12-31", - "articleTitle": "Looking for policy automations, Google Chrome profile search, and Munki details from your hosts? Upgrade to Fleet 4.8.0", - "articleImageUrl": "/images/articles/fleet-4.8.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/releases/fleet-4.9.0", - "title": "Fleet 4.9.0", - "lastModifiedAt": 1726839805112, - "htmlId": "articles--fleet-490--d6149315ff", - "sectionRelativeRepoPath": "fleet-4.9.0.md", - "meta": { - "category": "releases", - "authorFullName": "Mike Thomas", - "authorGitHubUsername": "mike-j-thomas", - "publishedOn": "2022-01-24", - "articleTitle": "Fleet 4.9.0 brings performance updates, paginated live query results, and policy YAML doc support.", - "articleImageUrl": "/images/articles/fleet-4.9.0-cover-1600x900@2x.jpg" - } - }, - { - "url": "/guides/fleet-ai-assisted-policy-descriptions-and-resolutions", - "title": "Fleet ai assisted policy descriptions and resolutions", - "lastModifiedAt": 1726839805113, - "htmlId": "articles--fleet-ai-assisted-po--74a94535fe", - "sectionRelativeRepoPath": "fleet-ai-assisted-policy-descriptions-and-resolutions.md", - "meta": { - "articleTitle": "Fleet’s AI-assisted policy descriptions and resolutions", - "authorFullName": "Rachel Perkins", - "authorGitHubUsername": "rachelelysia", - "category": "guides", - "publishedOn": "2024-05-20", - "articleImageUrl": "/images/articles/fleet-ai-assisted-policy-descriptions-and-resolutions-1600x900@2x.png", - "description": "AI guides our way, Policies clear, secure paths, Compliance shines bright." - } - }, - { - "url": "/announcements/fleet-adds-support-for-chrome-os", - "title": "Fleet adds support for chrome os", - "lastModifiedAt": 1726839805114, - "htmlId": "articles--fleet-adds-support-f--e846968e31", - "sectionRelativeRepoPath": "fleet-adds-support-for-chrome-os.md", - "meta": { - "category": "announcements", - "authorGitHubUsername": "spokanemac", - "authorFullName": "JD Strong", - "publishedOn": "2023-06-13", - "articleTitle": "Fleet enhances device management with ChromeOS support", - "articleImageUrl": "/images/articles/fleet-adds-support-for-chrome-os-1600x900@2x.png", - "description": "We're thrilled to announce that Fleet has expanded support to include ChromeOS and ChromeOS Flex!" - } - }, - { - "url": "/announcements/fleet-desktop-says-hello-world", - "title": "Fleet desktop says hello world", - "lastModifiedAt": 1726839805115, - "htmlId": "articles--fleet-desktop-says-h--b773918322", - "sectionRelativeRepoPath": "fleet-desktop-says-hello-world.md", - "meta": { - "category": "announcements", - "authorGitHubUsername": "zhumo", - "authorFullName": "Mo Zhu", - "publishedOn": "2022-08-02", - "articleTitle": "Fleet Desktop says “Hello, world!”", - "articleImageUrl": "/images/articles/fleet-desktop-says-hello-world-cover-1600x900@2x.jpg" - } - }, - { - "url": "/guides/fleet-desktop", - "title": "Fleet desktop", - "lastModifiedAt": 1726839805116, - "htmlId": "articles--fleet-desktop--9214a6a67a", - "sectionRelativeRepoPath": "fleet-desktop.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "zhumo", - "authorFullName": "Mo Zhu", - "publishedOn": "2024-04-19", - "articleTitle": "Fleet Desktop", - "description": "Learn about Fleet Desktop's features for self-remediation and transparency." - } - }, - { - "url": "/announcements/fleet-in-vegas-2023", - "title": "Fleet in vegas 2023", - "lastModifiedAt": 1726839805117, - "htmlId": "articles--fleet-in-vegas-2023--284818a7ab", - "sectionRelativeRepoPath": "fleet-in-vegas-2023.md", - "meta": { - "category": "announcements", - "authorGitHubUsername": "spokanemac", - "authorFullName": "JD Strong", - "publishedOn": "2023-08-02", - "articleTitle": "Fleet takes on Vegas: Exploring cybersecurity's future at Black Hat, B-Sides, and DEF CON 31", - "articleImageUrl": "/images/articles/fleet-in-vegas-2023@2x.jpg", - "description": "Explore cybersecurity's cutting edge with Fleet at three top-tier conferences - Black Hat, Security B-Sides, and DEF CON." - } - }, - { - "url": "/releases/fleet-introduces-mdm", - "title": "Fleet introduces mdm", - "lastModifiedAt": 1726839805118, - "htmlId": "articles--fleet-introduces-mdm--e7ec825f3a", - "sectionRelativeRepoPath": "fleet-introduces-mdm.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2023-04-11", - "articleTitle": "Fleet introduces MDM", - "articleImageUrl": "/images/articles/fleet-mdm-launch-cover-800x450@2x.jpg" - } - }, - { - "url": "/announcements/fleet-in-your-calendar-introducing-maintenance-windows", - "title": "Fleet in your calendar introducing maintenance windows", - "lastModifiedAt": 1726839805119, - "htmlId": "articles--fleet-in-your-calend--35d205d395", - "sectionRelativeRepoPath": "fleet-in-your-calendar-introducing-maintenance-windows.md", - "meta": { - "category": "announcements", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2024-04-30", - "articleTitle": "Fleet in your calendar: introducing maintenance windows", - "articleImageUrl": "/images/articles/fleet-in-your-calendar-introducing-maintenance-windows-cover-900x450@2x.png", - "description": "Like any good colleague, when Fleet needs some of your time, it puts it on your calendar." - } - }, - { - "url": "/announcements/fleet-introduces-windows-mdm", - "title": "Fleet introduces windows mdm", - "lastModifiedAt": 1726839805120, - "htmlId": "articles--fleet-introduces-win--c7cafc9ba6", - "sectionRelativeRepoPath": "fleet-introduces-windows-mdm.md", - "meta": { - "category": "announcements", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2024-01-24", - "articleTitle": "Fleet introduces Windows MDM", - "articleImageUrl": "/images/articles/fleet-win-mdm-launch-cover-800x450@2x.png" - } - }, - { - "url": "/announcements/fleet-is-abuzz-for-macdevops-yvr-2023", - "title": "Fleet is abuzz for macdevops yvr 2023", - "lastModifiedAt": 1726839805121, - "htmlId": "articles--fleet-is-abuzz-for-m--ad5da5f6fb", - "sectionRelativeRepoPath": "fleet-is-abuzz-for-macdevops-yvr-2023.md", - "meta": { - "category": "announcements", - "authorGitHubUsername": "spokanemac", - "authorFullName": "JD Strong", - "publishedOn": "2023-06-07", - "articleTitle": "Fleet is abuzz 🐝 for MacDevOps:YVR", - "articleImageUrl": "/images/articles/fleet-is-abuzz-for-macdevops-yvr-2023@2x.png", - "description": "Fleet is a proud sponsor of MacDevOps:YVR which is back in person in Vancouver, B.C. June 21-22, 2023" - } - }, - { - "url": "/securing/fleet-osquery-unlocking-the-value-of-axonius-with-open-source-telemetry", - "title": "Fleet osquery unlocking the value of axonius with open source telemetry", - "lastModifiedAt": 1726839805122, - "htmlId": "articles--fleet-osquery-unlock--3d8a42de76", - "sectionRelativeRepoPath": "fleet-osquery-unlocking-the-value-of-axonius-with-open-source-telemetry.md", - "meta": { - "category": "security", - "authorFullName": "Brad Macdowall", - "authorGitHubUsername": "BradMacd", - "publishedOn": "2023-12-28", - "articleTitle": "Fleet & osquery: Unlocking the value of Axonius with open-source telemetry", - "articleImageUrl": "/images/articles/fleet-osquery-unlocking-the-value-of-axonius-with-open-source-telemetry-1600x900@2x.png" - } - }, - { - "url": "/guides/fleet-quick-tips-querying-procdump-eula-has-been-accepted", - "title": "Fleet quick tips querying procdump eula has been accepted", - "lastModifiedAt": 1726839805123, - "htmlId": "articles--fleet-quick-tips-que--083c7ab95c", - "sectionRelativeRepoPath": "fleet-quick-tips-querying-procdump-eula-has-been-accepted.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "mike-j-thomas", - "authorFullName": "Mike Thomas", - "publishedOn": "2021-05-11", - "articleTitle": "Fleet quick tips — identify systems where the ProcDump EULA has been accepted", - "articleImageUrl": "/images/articles/fleet-quick-tips-querying-procdump-eula-has-been-accepted-cover-700x440@2x.png" - } - }, - { - "url": "/guides/fleet-terraform-byo-vpc-module", - "title": "Fleet terraform byo vpc module", - "lastModifiedAt": 1726839805124, - "htmlId": "articles--fleet-terraform-byo---dc914e6434", - "sectionRelativeRepoPath": "fleet-terraform-byo-vpc-module.md", - "meta": { - "category": "guides", - "authorFullName": "Robert Fairburn", - "authorGitHubUsername": "rfairburn", - "publishedOn": "2023-09-01", - "articleTitle": "Using the Fleet Terraform module with an existing VPC" - } - }, - { - "url": "/announcements/fleet-terraform-module", - "title": "Fleet terraform module", - "lastModifiedAt": 1726839805125, - "htmlId": "articles--fleet-terraform-modu--290ad35faf", - "sectionRelativeRepoPath": "fleet-terraform-module.md", - "meta": { - "category": "announcements", - "authorFullName": "Zachary Winnerman", - "authorGitHubUsername": "zwinnerman-fleetdm", - "publishedOn": "2023-01-09", - "articleTitle": "Keep Fleet running smoothly on AWS with the new Terraform module" - } - }, - { - "url": "/guides/fleet-usage-statistics", - "title": "Fleet usage statistics", - "lastModifiedAt": 1726839805126, - "htmlId": "articles--fleet-usage-statisti--8212e2baf7", - "sectionRelativeRepoPath": "fleet-usage-statistics.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "noahtalerman", - "authorFullName": "Noah Talerman", - "publishedOn": "2024-08-13", - "articleTitle": "Fleet usage statistics", - "description": "Learn about Fleet's usage statistics and what information is collected." - } - }, - { - "url": "/success-stories/fleet-user-stories-f100", - "title": "Fleet user stories f100", - "lastModifiedAt": 1726839805127, - "htmlId": "articles--fleet-user-stories-f--869652e2be", - "sectionRelativeRepoPath": "fleet-user-stories-f100.md", - "meta": { - "category": "success stories", - "authorGitHubUsername": "mike-j-thomas", - "authorFullName": "Mike Thomas", - "publishedOn": "2021-09-29", - "articleTitle": "Fleet user stories — F100", - "articleImageUrl": "/images/articles/fleet-user-stories-f100-cover-800x450@2x.png" - } - }, - { - "url": "/success-stories/fleet-user-stories-schrodinger", - "title": "Fleet user stories schrodinger", - "lastModifiedAt": 1726839805127, - "htmlId": "articles--fleet-user-stories-s--1486ea1812", - "sectionRelativeRepoPath": "fleet-user-stories-schrodinger.md", - "meta": { - "category": "success stories", - "authorGitHubUsername": "mike-j-thomas", - "authorFullName": "Mike Thomas", - "publishedOn": "2021-09-10", - "articleTitle": "Fleet user stories — Schrödinger", - "articleImageUrl": "/images/articles/fleet-user-stories-schrodinger-cover-800x450@2x.png" - } - }, - { - "url": "/success-stories/fleet-user-stories-wayfair", - "title": "Fleet user stories wayfair", - "lastModifiedAt": 1726839805128, - "htmlId": "articles--fleet-user-stories-w--c78d4fa6b9", - "sectionRelativeRepoPath": "fleet-user-stories-wayfair.md", - "meta": { - "category": "success stories", - "authorGitHubUsername": "mike-j-thomas", - "authorFullName": "Mike Thomas", - "publishedOn": "2021-08-20", - "articleTitle": "Fleet user stories — Wayfair", - "articleImageUrl": "/images/articles/fleet-user-stories-wayfair-cover-800x450@2x.png" - } - }, - { - "url": "/guides/fleetctl", - "title": "Fleetctl", - "lastModifiedAt": 1726839805129, - "htmlId": "articles--fleetctl--0cb8193ba2", - "sectionRelativeRepoPath": "fleetctl.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "noahtalerman", - "authorFullName": "Noah Talerman", - "publishedOn": "2024-07-04", - "articleTitle": "fleetctl", - "description": "Read about fleetctl, a CLI tool for managing Fleet and osquery configurations, running queries, generating Fleet's agent (fleetd) and more." - } - }, - { - "url": "/announcements/from-osquery-to-fleet-planting-the-seed", - "title": "From osquery to Fleet planting the seed", - "lastModifiedAt": 1726839805130, - "htmlId": "articles--from-osquery-to-flee--229a9b9742", - "sectionRelativeRepoPath": "from-osquery-to-fleet-planting-the-seed.md", - "meta": { - "category": "announcements", - "authorGitHubUsername": "zwass", - "authorFullName": "Zach Wasserman", - "publishedOn": "2022-01-20", - "articleTitle": "The next step for Fleet: our $5M seed round 🌱", - "articleImageUrl": "/images/articles/from-osquery-to-fleet-planting-the-seed-cover-800x450@2x.png" - } - }, - { - "url": "/guides/fleetd-updates", - "title": "Fleetd updates", - "lastModifiedAt": 1726839805131, - "htmlId": "articles--fleetd-updates--6d4aebafec", - "sectionRelativeRepoPath": "fleetd-updates.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "noahtalerman", - "authorFullName": "Noah Talerman", - "publishedOn": "2024-04-30", - "articleTitle": "Fleetd updates", - "description": "Information on how to manage and secure Fleet agent updates." - } - }, - { - "url": "/guides/generate-process-trees-with-osquery", - "title": "Generate process trees with osquery", - "lastModifiedAt": 1726839805132, - "htmlId": "articles--generate-process-tre--d1b0edcce1", - "sectionRelativeRepoPath": "generate-process-trees-with-osquery.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "zwass", - "authorFullName": "Zach Wasserman", - "publishedOn": "2020-03-17", - "articleTitle": "Generate process trees with osquery", - "articleImageUrl": "/images/articles/generate-process-trees-with-osquery-cover-700x393@2x.jpeg" - } - }, - { - "url": "/securing/get-and-stay-compliant-across-your-devices-with-fleet", - "title": "Get and stay compliant across your devices with Fleet", - "lastModifiedAt": 1726839805133, - "htmlId": "articles--get-and-stay-complia--2cb805730d", - "sectionRelativeRepoPath": "get-and-stay-compliant-across-your-devices-with-fleet.md", - "meta": { - "category": "security", - "authorFullName": "Drew Baker", - "authorGitHubUsername": "Drew-P-drawers", - "publishedOn": "2022-03-09", - "articleTitle": "Get and stay compliant across your devices with Fleet.", - "articleImageUrl": "/images/articles/get-and-stay-compliant-across-your-devices-with-fleet-cover-1600x900@2x.jpg" - } - }, - { - "url": "/guides/get-current-telemetry-from-your-devices-with-live-queries", - "title": "Get current telemetry from your devices with live queries", - "lastModifiedAt": 1726839805134, - "htmlId": "articles--get-current-telemetr--019d64996a", - "sectionRelativeRepoPath": "get-current-telemetry-from-your-devices-with-live-queries.md", - "meta": { - "articleTitle": "Get current telemetry from your devices with live queries", - "authorFullName": "Victor Lyuboslavsky", - "authorGitHubUsername": "getvictor", - "category": "guides", - "publishedOn": "2023-12-27", - "description": "Learn how live queries work under the hood." - } - }, - { - "url": "/announcements/government-agencies-need-to-dith-the-mdm-thicket", - "title": "Government agencies need to dith the mdm thicket", - "lastModifiedAt": 1726839805135, - "htmlId": "articles--government-agencies---f0385b3f79", - "sectionRelativeRepoPath": "government-agencies-need-to-dith-the-mdm-thicket.md", - "meta": { - "category": "announcements", - "authorFullName": "Keith Barnes", - "authorGitHubUsername": "KAB703", - "publishedOn": "2024-02-09", - "articleTitle": "Government agencies need to ditch the MDM thicket: multiple solutions cost you more than you think", - "articleImageUrl": "/images/articles/government-agencies-need-to-dith-the-mdm-thicket-1600x900@2x.png" - } - }, - { - "url": "/announcements/happy-1st-anniversary-fleet", - "title": "Happy 1st anniversary Fleet", - "lastModifiedAt": 1726839805135, - "htmlId": "articles--happy-1st-anniversar--128480e14b", - "sectionRelativeRepoPath": "happy-1st-anniversary-fleet.md", - "meta": { - "category": "announcements", - "authorGitHubUsername": "mike-j-thomas", - "authorFullName": "Mike Thomas", - "publishedOn": "2021-10-08", - "articleTitle": "Happy 1st anniversary, Fleet.", - "articleImageUrl": "/images/articles/happy-1st-anniversary-fleet-cover-800x450@2x.png" - } - }, - { - "url": "/securing/how-fleet-helps-federal-agencies-meet-cisa-bod-23-01", - "title": "How Fleet helps federal agencies meet cisa bod 23 01", - "lastModifiedAt": 1726839805136, - "htmlId": "articles--how-fleet-helps-fede--82d74da10e", - "sectionRelativeRepoPath": "how-fleet-helps-federal-agencies-meet-cisa-bod-23-01.md", - "meta": { - "category": "security", - "authorFullName": "Chris McGillicuddy", - "authorGitHubUsername": "chris-mcgillicuddy", - "publishedOn": "2022-10-28", - "articleTitle": "How Fleet helps federal agencies meet CISA BOD 23-01", - "articleImageUrl": "/images/articles/BOD-23-01-800x450@2x.jpg" - } - }, - { - "url": "/securing/how-osquery-can-help-cyber-responders", - "title": "How osquery can help cyber responders", - "lastModifiedAt": 1726839805138, - "htmlId": "articles--how-osquery-can-help--eca2df006d", - "sectionRelativeRepoPath": "how-osquery-can-help-cyber-responders.md", - "meta": { - "category": "security", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2023-11-02", - "articleTitle": "How osquery can help cyber responders.", - "articleImageUrl": "/images/articles/osquery-for-cyber-responders-1600x900@2x.png" - } - }, - { - "url": "/guides/how-to-configure-logging-destinations", - "title": "How to configure logging destinations", - "lastModifiedAt": 1726839805139, - "htmlId": "articles--how-to-configure-log--e7ef58a2dc", - "sectionRelativeRepoPath": "how-to-configure-logging-destinations.md", - "meta": { - "category": "guides", - "authorFullName": "Grant Bilstad", - "authorGitHubUsername": "pacamaster", - "publishedOn": "2024-06-28", - "articleTitle": "How to configure logging destinations", - "articleImageUrl": "/images/articles/how-to-configure-logging-destinations-1600x900@2x.jpg" - } - }, - { - "url": "/guides/how-to-install-osquery-and-enroll-linux-devices-into-fleet", - "title": "How to install osquery and enroll linux devices into Fleet", - "lastModifiedAt": 1726839805140, - "htmlId": "articles--how-to-install-osque--7ef1932c39", - "sectionRelativeRepoPath": "how-to-install-osquery-and-enroll-linux-devices-into-fleet.md", - "meta": { - "category": "guides", - "authorFullName": "Kathy Satterlee", - "authorGitHubUsername": "ksatter", - "publishedOn": "2022-03-19", - "articleTitle": "How to install osquery and enroll Linux devices into Fleet", - "articleImageUrl": "/images/articles/install-osquery-and-enroll-linux-devices-into-fleet-cover-1600x900@2x.jpg" - } - }, - { - "url": "/guides/how-to-install-osquery-and-enroll-macos-devices-into-fleet", - "title": "How to install osquery and enroll macos devices into Fleet", - "lastModifiedAt": 1726839805142, - "htmlId": "articles--how-to-install-osque--9584297736", - "sectionRelativeRepoPath": "how-to-install-osquery-and-enroll-macos-devices-into-fleet.md", - "meta": { - "category": "guides", - "authorFullName": "Kelvin Omereshone", - "authorGitHubUsername": "dominuskelvin", - "publishedOn": "2022-01-13", - "articleTitle": "How to install osquery and enroll macOS devices into Fleet", - "articleImageUrl": "/images/articles/install-osquery-and-enroll-macos-devices-into-fleet-cover-1600x900@2x.jpg" - } - }, - { - "url": "/guides/how-to-install-osquery-and-enroll-windows-devices-into-fleet", - "title": "How to install osquery and enroll windows devices into Fleet", - "lastModifiedAt": 1726839805143, - "htmlId": "articles--how-to-install-osque--65750e792f", - "sectionRelativeRepoPath": "how-to-install-osquery-and-enroll-windows-devices-into-fleet.md", - "meta": { - "category": "guides", - "authorFullName": "Kelvin Omereshone", - "authorGitHubUsername": "dominuskelvin", - "publishedOn": "2022-02-03", - "articleTitle": "How to install osquery and enroll Windows devices into Fleet", - "articleImageUrl": "/images/articles/install-osquery-and-enroll-windows-devices-into-fleet-cover-1600x900@2x.jpg" - } - }, - { - "url": "/guides/how-to-uninstall-osquery", - "title": "How to uninstall osquery", - "lastModifiedAt": 1726839805143, - "htmlId": "articles--how-to-uninstall-osq--7455ca45fc", - "sectionRelativeRepoPath": "how-to-uninstall-osquery.md", - "meta": { - "category": "guides", - "authorFullName": "Eric Shaw", - "authorGitHubUsername": "eashaw", - "publishedOn": "2021-09-08", - "articleTitle": "How to uninstall osquery", - "articleImageUrl": "/images/articles/how-to-uninstall-osquery-cover-1600x900@2x.jpg" - } - }, - { - "url": "/guides/import-and-export-queries-in-fleet", - "title": "Import and export queries in Fleet", - "lastModifiedAt": 1726839805144, - "htmlId": "articles--import-and-export-qu--44b09ee020", - "sectionRelativeRepoPath": "import-and-export-queries-in-fleet.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "noahtalerman", - "authorFullName": "Noah Talerman", - "publishedOn": "2021-02-16", - "articleTitle": "Import and export queries in Fleet", - "articleImageUrl": "/images/articles/import-and-export-queries-in-Fleet-1600x900@2x.png" - } - }, - { - "url": "/guides/install-vpp-apps-on-macos-using-fleet", - "title": "Install vpp apps on macos using Fleet", - "lastModifiedAt": 1726839805145, - "htmlId": "articles--install-vpp-apps-on---4e6a161ea8", - "sectionRelativeRepoPath": "install-vpp-apps-on-macos-using-fleet.md", - "meta": { - "articleTitle": "Install VPP apps on macOS, iOS, and iPadOS using Fleet", - "authorFullName": "Jahziel Villasana-Espinoza", - "authorGitHubUsername": "jahzielv", - "category": "guides", - "publishedOn": "2024-08-12", - "articleImageUrl": "/images/articles/install-vpp-apps-on-macos-using-fleet-1600x900@2x.png", - "description": "This guide will walk you through installing VPP apps on macOS, iOS, and iPadOS using Fleet." - } - }, - { - "url": "/announcements/introducing-cross-platform-script-execution", - "title": "Introducing cross platform script execution", - "lastModifiedAt": 1726839805147, - "htmlId": "articles--introducing-cross-pl--f50031e3db", - "sectionRelativeRepoPath": "introducing-cross-platform-script-execution.md", - "meta": { - "category": "announcements", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2023-10-17", - "articleTitle": "Introducing cross-platform script execution", - "articleImageUrl": "/images/articles/introducing-cross-platform-script-execution-800x450@2x.png" - } - }, - { - "url": "/announcements/introducing-fleet-ultimate", - "title": "Introducing Fleet ultimate", - "lastModifiedAt": 1726839805147, - "htmlId": "articles--introducing-fleet-ul--caba265ec4", - "sectionRelativeRepoPath": "introducing-fleet-ultimate.md", - "meta": { - "category": "announcements", - "authorGitHubUsername": "jarodreyes", - "authorFullName": "Jarod Reyes", - "publishedOn": "2023-02-20", - "articleTitle": "Introducing CIS benchmarks, managed-cloud hosting and custom calculator in the new Fleet Ultimate plan.", - "articleImageUrl": "/images/articles/happy-1st-anniversary-fleet-cover-800x450@2x.png" - } - }, - { - "url": "/announcements/introducing-orbit-your-fleet-agent-manager", - "title": "Introducing orbit your Fleet agent manager", - "lastModifiedAt": 1726839805148, - "htmlId": "articles--introducing-orbit-yo--1de0ea07ab", - "sectionRelativeRepoPath": "introducing-orbit-your-fleet-agent-manager.md", - "meta": { - "category": "announcements", - "authorFullName": "Mo Zhu", - "authorGitHubUsername": "zhumo", - "publishedOn": "2022-08-18", - "articleTitle": "Introducing Orbit, your Fleet agent manager", - "articleImageUrl": "/images/articles/fleet-4.17.0-1-1600x900@2x.jpg" - } - }, - { - "url": "/engineering/linux-vulnerability-detection-with-oval-and-fleet", - "title": "Linux vulnerability detection with oval and Fleet", - "lastModifiedAt": 1726839805150, - "htmlId": "articles--linux-vulnerability---0d4c8fd5ac", - "sectionRelativeRepoPath": "linux-vulnerability-detection-with-oval-and-fleet.md", - "meta": { - "category": "engineering", - "authorGitHubUsername": "juan-fdz-hawa", - "authorFullName": "Juan Fernandes", - "publishedOn": "2022-07-29", - "articleTitle": "Linux vulnerability detection with OVAL and Fleet", - "articleImageUrl": "/images/articles/linux-vulnerability-detection-with-oval-and-fleet-1600x900@2x.jpg" - } - }, - { - "url": "/guides/locate-assets-with-osquery", - "title": "Locate assets with osquery", - "lastModifiedAt": 1726839805150, - "htmlId": "articles--locate-assets-with-o--764d2b5f55", - "sectionRelativeRepoPath": "locate-assets-with-osquery.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "zwass", - "authorFullName": "Zach Wasserman", - "publishedOn": "2021-05-11", - "articleTitle": "Locate device assets in the event of an emergency.", - "articleImageUrl": "/images/articles/locate-assets-with-osquery-cover-700x393@2x.jpeg" - } - }, - { - "url": "/guides/log-destinations", - "title": "Log destinations", - "lastModifiedAt": 1726839805152, - "htmlId": "articles--log-destinations--9bb62f5aa2", - "sectionRelativeRepoPath": "log-destinations.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "rachaelshaw", - "authorFullName": "Rachael Shaw", - "publishedOn": "2023-11-02", - "articleTitle": "Log destinations", - "description": "Learn about supported log destinations in Fleet, including Amazon Kinesis, AWS Lambda Snowflake, Splunk, and more." - } - }, - { - "url": "/securing/lossless-yubikeys-with-yubitrak-and-airtag", - "title": "Lossless yubikeys with yubitrak and airtag", - "lastModifiedAt": 1726839805153, - "htmlId": "articles--lossless-yubikeys-wi--b260bfc20a", - "sectionRelativeRepoPath": "lossless-yubiKeys-with-yubitrak-and-airtag.md", - "meta": { - "category": "security", - "authorGitHubUsername": "GuillaumeRoss", - "authorFullName": "Guillaume Ross", - "publishedOn": "2022-06-16", - "articleTitle": "Lossless YubiKeys with Yubitrak and AirTag", - "articleImageUrl": "/images/articles/lossless-yubikeys-with-yubitrak-and-airtag-cover-1600x900@2x.jpg" - } - }, - { - "url": "/guides/macos-mdm-setup", - "title": "Macos mdm setup", - "lastModifiedAt": 1726839805154, - "htmlId": "articles--macos-mdm-setup--66538706f5", - "sectionRelativeRepoPath": "macos-mdm-setup.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "zhumo", - "authorFullName": "Mo Zhu", - "publishedOn": "2024-07-02", - "articleTitle": "macOS MDM setup", - "description": "Learn how to turn on MDM features in Fleet." - } - }, - { - "url": "/guides/macos-setup-experience", - "title": "Macos setup experience", - "lastModifiedAt": 1726839805155, - "htmlId": "articles--macos-setup-experien--cca7e9e073", - "sectionRelativeRepoPath": "macos-setup-experience.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "noahtalerman", - "authorFullName": "Noah Talerman", - "publishedOn": "2024-07-03", - "articleTitle": "macOS setup experience", - "description": "Customize your macOS setup experience with Fleet Premium by managing user authentication, Setup Assistant panes, and installing bootstrap packages." - } - }, - { - "url": "/guides/managing-labels-in-fleet", - "title": "Managing labels in Fleet", - "lastModifiedAt": 1726839805156, - "htmlId": "articles--managing-labels-in-f--b2e5aed976", - "sectionRelativeRepoPath": "managing-labels-in-fleet.md", - "meta": { - "articleTitle": "Managing labels in Fleet", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "category": "guides", - "publishedOn": "2024-07-18", - "articleImageUrl": "/images/articles/managing-labels-in-fleet-1600x900@2x.png", - "description": "This guide will walk you through managing labels using the Fleet web UI." - } - }, - { - "url": "/securing/mapping-fleet-and-osquery-results-to-the-mitre-attck-framework-via-splunk", - "title": "Mapping Fleet and osquery results to the mitre attck framework via splunk", - "lastModifiedAt": 1726839805157, - "htmlId": "articles--mapping-fleet-and-os--7ee9249dc4", - "sectionRelativeRepoPath": "mapping-fleet-and-osquery-results-to-the-mitre-attck-framework-via-splunk.md", - "meta": { - "category": "security", - "authorFullName": "Dave Herder", - "authorGitHubUsername": "dherder", - "publishedOn": "2023-01-30", - "articleTitle": "Mapping Fleet and osquery results to the MITRE ATT&CK® framework via Splunk", - "articleImageUrl": "/images/articles/mapping-fleet-and-osquery-results-to-the-mitre-attck-framework-via-splunk-1600x900@2x.png" - } - }, - { - "url": "/guides/mdm-commands", - "title": "Mdm commands", - "lastModifiedAt": 1726839805158, - "htmlId": "articles--mdm-commands--8de440c455", - "sectionRelativeRepoPath": "mdm-commands.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "noahtalerman", - "authorFullName": "Noah Talerman", - "publishedOn": "2024-06-12", - "articleTitle": "MDM commands", - "description": "Learn how to run custom MDM commands on hosts using Fleet." - } - }, - { - "url": "/guides/mdm-migration", - "title": "Mdm migration", - "lastModifiedAt": 1726839805159, - "htmlId": "articles--mdm-migration--a500f61869", - "sectionRelativeRepoPath": "mdm-migration.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "zhumo", - "authorFullName": "Mo Zhu", - "publishedOn": "2024-08-14", - "articleTitle": "MDM migration", - "description": "Instructions for migrating hosts away from an old MDM solution to Fleet." - } - }, - { - "url": "/announcements/nvd-api-2.0", - "title": "Nvd API 2.0", - "lastModifiedAt": 1726839805160, - "htmlId": "articles--nvd-api-20--a754d441c3", - "sectionRelativeRepoPath": "nvd-api-2.0.md", - "meta": { - "category": "announcements", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2023-11-28", - "articleTitle": "NVD API 2.0: An important update for Fleet users", - "articleImageUrl": "/images/articles/nvd-api-2.0-1600x900@2x.jpg" - } - }, - { - "url": "/securing/optimizing-government-cybersecurity-strategies", - "title": "Optimizing government cybersecurity strategies", - "lastModifiedAt": 1726839805161, - "htmlId": "articles--optimizing-governmen--78189d23a6", - "sectionRelativeRepoPath": "optimizing-government-cybersecurity-strategies.md", - "meta": { - "category": "security", - "authorFullName": "Keith Barnes", - "authorGitHubUsername": "KAB703", - "publishedOn": "2023-11-14", - "articleTitle": "Optimizing government cybersecurity strategies with Fleet.", - "articleImageUrl": "/images/articles/optimizing-government-cybersecurity-strategies-1600x900@2x.png" - } - }, - { - "url": "/releases/osquery-5.11.0", - "title": "Osquery 5.11.0", - "lastModifiedAt": 1726839805162, - "htmlId": "articles--osquery-5110--5af6435495", - "sectionRelativeRepoPath": "osquery-5.11.0.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2024-02-16", - "articleTitle": "osquery 5.11.0 | VSCode, Apple silicon, and more", - "articleImageUrl": "/images/articles/osquery-5.11.0-cover-1600x900@2x.png" - } - }, - { - "url": "/guides/osquery-a-tool-to-easily-ask-questions-about-operating-systems", - "title": "Osquery a tool to easily ask questions about operating systems", - "lastModifiedAt": 1726839805163, - "htmlId": "articles--osquery-a-tool-to-ea--424e2ed801", - "sectionRelativeRepoPath": "osquery-a-tool-to-easily-ask-questions-about-operating-systems.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "dominuskelvin", - "authorFullName": "Kelvin Omereshone", - "publishedOn": "2022-04-04", - "articleTitle": "Osquery: a tool to easily ask questions about operating systems", - "articleImageUrl": "/images/articles/osquery-a-tool-to-easily-ask-questions-about-operating-systems-cover-1600x900@2x.jpg" - } - }, - { - "url": "/securing/osquery-as-a-threat-hunting-platform", - "title": "Osquery as a threat hunting platform", - "lastModifiedAt": 1726839805164, - "htmlId": "articles--osquery-as-a-threat---d96a59f1dd", - "sectionRelativeRepoPath": "osquery-as-a-threat-hunting-platform.md", - "meta": { - "category": "security", - "authorFullName": "Chris McGillicuddy", - "authorGitHubUsername": "chris-mcgillicuddy", - "publishedOn": "2022-09-16", - "articleTitle": "Osquery… as a threat hunting platform?", - "articleImageUrl": "/images/articles/osquery-for-threat-hunting-1600x900@2x.jpg" - } - }, - { - "url": "/guides/osquery-consider-joining-against-the-users-table", - "title": "Osquery consider joining against the users table", - "lastModifiedAt": 1726839805165, - "htmlId": "articles--osquery-consider-joi--b99ae264e4", - "sectionRelativeRepoPath": "osquery-consider-joining-against-the-users-table.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "zwass", - "authorFullName": "Zach Wasserman", - "publishedOn": "2021-05-06", - "articleTitle": "Osquery: Consider joining against the users table", - "articleImageUrl": "/images/articles/osquery-consider-joining-against-the-users-table-cover-700x437@2x.jpeg" - } - }, - { - "url": "/releases/osquery-5.8.1", - "title": "Osquery 5.8.1", - "lastModifiedAt": 1726839805166, - "htmlId": "articles--osquery-581--5d3dced550", - "sectionRelativeRepoPath": "osquery-5.8.1.md", - "meta": { - "category": "releases", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "publishedOn": "2023-03-14", - "articleTitle": "osquery 5.8.1 | Process auditing, stats, and additional tables", - "articleImageUrl": "/images/articles/osquery-5.8.1-cover-1600x900@2x.png" - } - }, - { - "url": "/guides/osquery-evented-tables-overview", - "title": "Osquery evented tables overview", - "lastModifiedAt": 1726839805168, - "htmlId": "articles--osquery-evented-tabl--b9b1176562", - "sectionRelativeRepoPath": "osquery-evented-tables-overview.md", - "meta": { - "articleTitle": "How to use osquery evented tables", - "authorFullName": "Mo Zhu", - "authorGitHubUsername": "zhumo", - "category": "guides", - "publishedOn": "2022-09-21" - } - }, - { - "url": "/securing/osquery-vulnerability-management-at-scale", - "title": "Osquery vulnerability management at scale", - "lastModifiedAt": 1726839805169, - "htmlId": "articles--osquery-vulnerabilit--cac605ad18", - "sectionRelativeRepoPath": "osquery-vulnerability-management-at-scale.md", - "meta": { - "category": "security", - "authorFullName": "Chris McGillicuddy", - "authorGitHubUsername": "chris-mcgillicuddy", - "publishedOn": "2022-10-05", - "articleTitle": "Vulnerability management at scale: a presentation from osquery Co-creator Zach Wasserman", - "articleImageUrl": "/images/articles/vulnerability-management-at-scale-with-osquery_800x450@2x.jpg" - } - }, - { - "url": "/guides/osquery-watchdog", - "title": "Osquery watchdog", - "lastModifiedAt": 1726839805170, - "htmlId": "articles--osquery-watchdog--6a195970e0", - "sectionRelativeRepoPath": "osquery-watchdog.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "juan-fdz-hawa", - "authorFullName": "Juan Fernandes", - "publishedOn": "2023-07-28", - "articleTitle": "Osquery watchdog", - "description": "Learn about how osquery process manages child processes and managed extensions in Fleet." - } - }, - { - "url": "/announcements/psu-macadmins-conference-2023", - "title": "Psu macadmins conference 2023", - "lastModifiedAt": 1726839805171, - "htmlId": "articles--psu-macadmins-confer--175629dfbd", - "sectionRelativeRepoPath": "psu-macadmins-conference-2023.md", - "meta": { - "category": "announcements", - "authorGitHubUsername": "spokanemac", - "authorFullName": "JD Strong", - "publishedOn": "2023-07-13", - "articleTitle": "Mac admins summer camp ⛺ at PSU MacAdmins Conference 2023", - "articleImageUrl": "/images/articles/psu-macadmins-conference-2023@2x.png", - "description": "A look ahead to PSU MacAdmin Conference July 18-21, 2023" - } - }, - { - "url": "/guides/puppet-module", - "title": "Puppet module", - "lastModifiedAt": 1726839805172, - "htmlId": "articles--puppet-module--c01ecdf2b6", - "sectionRelativeRepoPath": "puppet-module.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "noahtalerman", - "authorFullName": "Noah Talerman", - "publishedOn": "2024-05-24", - "articleTitle": "Puppet module", - "description": "Learn how to use Fleet's Puppet module to automatically assign custom configuration profiles on your macOS hosts." - } - }, - { - "url": "/guides/queries", - "title": "Queries", - "lastModifiedAt": 1726839805173, - "htmlId": "articles--queries--ce5c1e3c99", - "sectionRelativeRepoPath": "queries.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "noahtalerman", - "authorFullName": "Noah Talerman", - "publishedOn": "2024-08-09", - "articleTitle": "Queries", - "description": "Learn how to create, run, and schedule queries, as well as update agent options in the Fleet user interface." - } - }, - { - "url": "/guides/querying-process-file-events-table-on-centos-7", - "title": "Querying process file events table on centos 7", - "lastModifiedAt": 1726839805174, - "htmlId": "articles--querying-process-fil--5587f39199", - "sectionRelativeRepoPath": "querying-process-file-events-table-on-centos-7.md", - "meta": { - "articleTitle": "Querying process_file_events on CentOS 7", - "description": "Learn how to configure and query the process_file_events table on CentOS 7 with Fleet.", - "category": "guides", - "authorGitHubUsername": "lucasmrod", - "authorFullName": "Lucas Rodriguez", - "publishedOn": "2023-07-17" - } - }, - { - "url": "/guides/role-based-access", - "title": "Role based access", - "lastModifiedAt": 1726839805177, - "htmlId": "articles--role-based-access--92a94667b2", - "sectionRelativeRepoPath": "role-based-access.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "noahtalerman", - "authorFullName": "Noah Talerman", - "publishedOn": "2024-08-10", - "articleTitle": "Role-based access", - "description": "Learn about the different roles and permissions in Fleet." - } - }, - { - "url": "/engineering/saving-over-100x-on-egress-switching-from-aws-to-hetzner", - "title": "Saving over 100x on egress switching from aws to hetzner", - "lastModifiedAt": 1726839805179, - "htmlId": "articles--saving-over-100x-on---a46f112fc0", - "sectionRelativeRepoPath": "saving-over-100x-on-egress-switching-from-aws-to-hetzner.md", - "meta": { - "category": "engineering", - "authorGitHubUsername": "zwass", - "authorFullName": "Zach Wasserman", - "publishedOn": "2022-01-25", - "articleTitle": "Saving over 100x on egress switching from AWS to Hetzner", - "articleImageUrl": "/images/articles/saving-over-100x-on-egress-switching-from-aws-to-hetzner-cover-1600x900@2x.jpg" - } - }, - { - "url": "/guides/scripts", - "title": "Scripts", - "lastModifiedAt": 1726839805179, - "htmlId": "articles--scripts--3a91ba655e", - "sectionRelativeRepoPath": "scripts.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "noahtalerman", - "authorFullName": "Noah Talerman", - "publishedOn": "2024-06-04", - "articleTitle": "Scripts", - "description": "Learn how to execute a custom script on macOS, Windows, and Linux hosts in Fleet." - } - }, - { - "url": "/guides/seamless-mdm-migration", - "title": "Seamless mdm migration", - "lastModifiedAt": 1726839805182, - "htmlId": "articles--seamless-mdm-migrati--f0abcf2f23", - "sectionRelativeRepoPath": "seamless-mdm-migration.md", - "meta": { - "category": "guides", - "authorFullName": "Zach Wasserman", - "authorGitHubUsername": "zwass", - "publishedOn": "2024-08-08", - "articleTitle": "Seamless MDM migrations to Fleet", - "articleImageUrl": "/images/articles/seamless-mdm-migration-1600x900@2x.png", - "description": "This guide provides a process for seamlessly migrating macOS devices from an existing MDM solution to Fleet." - } - }, - { - "url": "/announcements/seattle-bellevue-cyber-security-summit-march-8-2023", - "title": "Seattle bellevue cyber security summit march 8 2023", - "lastModifiedAt": 1726839805183, - "htmlId": "articles--seattle-bellevue-cyb--3b5ca28169", - "sectionRelativeRepoPath": "seattle-bellevue-cyber-security-summit-march-8-2023.md", - "meta": { - "category": "announcements", - "authorGitHubUsername": "spokanemac", - "authorFullName": "JD Strong", - "publishedOn": "2023-03-02", - "articleTitle": "Join Fleet at Cyber Security Summit Seattle/Bellevue", - "articleImageUrl": "/images/articles/seattle-bellevue-cyber-security-summit-social-post-1200x628@2x.png" - } - }, - { - "url": "/securing/security-testing-at-fleet-fleet-pentest", - "title": "Security testing at Fleet Fleet pentest", - "lastModifiedAt": 1726839805184, - "htmlId": "articles--security-testing-at---106e7f1999", - "sectionRelativeRepoPath": "security-testing-at-fleet-fleet-pentest.md", - "meta": { - "category": "security", - "authorGitHubUsername": "GuillaumeRoss", - "authorFullName": "Guillaume Ross", - "publishedOn": "2022-05-10", - "articleTitle": "Penetration testing of Fleet (April 2022)", - "articleImageUrl": "/images/articles/security-testing-at-fleet-fleet-pentest-cover-1600x900@2x.jpg" - } - }, - { - "url": "/securing/security-testing-at-fleet-orbit-auto-updater-audit", - "title": "Security testing at Fleet orbit auto updater audit", - "lastModifiedAt": 1726839805185, - "htmlId": "articles--security-testing-at---f487015e45", - "sectionRelativeRepoPath": "security-testing-at-fleet-orbit-auto-updater-audit.md", - "meta": { - "category": "security", - "authorGitHubUsername": "GuillaumeRoss", - "authorFullName": "Guillaume Ross", - "publishedOn": "2022-03-30", - "articleTitle": "Security testing at Fleet/Orbit auto-updater audit", - "articleImageUrl": "/images/articles/security-testing-at-fleet-orbit-auto-updater-audit-cover-1600x900@2x.jpg" - } - }, - { - "url": "/guides/software-self-service", - "title": "Software self service", - "lastModifiedAt": 1726839805186, - "htmlId": "articles--software-self-servic--9047e7f63d", - "sectionRelativeRepoPath": "software-self-service.md", - "meta": { - "articleTitle": "Software self-service", - "authorFullName": "Jahziel Villasana-Espinoza", - "authorGitHubUsername": "jahzielv", - "category": "guides", - "publishedOn": "2024-08-06", - "articleImageUrl": "/images/articles/software-self-service-1600x900@2x.png", - "description": "This guide will walk you through adding apps to Fleet for user self-service." - } - }, - { - "url": "/guides/standard-query-library", - "title": "Standard query library", - "lastModifiedAt": 1726839805187, - "htmlId": "articles--standard-query-libra--dccfaa84b4", - "sectionRelativeRepoPath": "standard-query-library.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "noahtalerman", - "authorFullName": "Noah Talerman", - "publishedOn": "2024-04-04", - "articleTitle": "Standard query library", - "description": "Learn how to use and contribute to Fleet's standard query library." - } - }, - { - "url": "/report/state-of-device-management", - "title": "State of device management", - "lastModifiedAt": 1726839805187, - "htmlId": "articles--state-of-device-mana--f6254cc69c", - "sectionRelativeRepoPath": "state-of-device-management.md", - "meta": { - "category": "report", - "authorFullName": "Mike McNeil", - "authorGitHubUsername": "mikermcneil", - "publishedOn": "2022-07-07", - "articleTitle": "State of Device Management report 2022", - "articleImageUrl": "/images/articles/state-of-device-management-report-1600x900@2x.png" - } - }, - { - "url": "/securing/stay-on-course-with-your-security-compliance-goals", - "title": "Stay on course with your security compliance goals", - "lastModifiedAt": 1726839805188, - "htmlId": "articles--stay-on-course-with---a487f310dc", - "sectionRelativeRepoPath": "stay-on-course-with-your-security-compliance-goals.md", - "meta": { - "category": "security", - "authorFullName": "Chris McGillicuddy", - "authorGitHubUsername": "chris-mcgillicuddy", - "publishedOn": "2022-07-18", - "articleTitle": "Stay on course with your security compliance goals", - "articleImageUrl": "/images/articles/security-compliance-goals-cover-800x450@2x.jpg" - } - }, - { - "url": "/guides/sysadmin-diaries-device-enrollment", - "title": "Sysadmin diaries device enrollment", - "lastModifiedAt": 1726839805189, - "htmlId": "articles--sysadmin-diaries-dev--abfda23f04", - "sectionRelativeRepoPath": "sysadmin-diaries-device-enrollment.md", - "meta": { - "articleTitle": "Sysadmin diaries: device enrollment", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "category": "guides", - "publishedOn": "2024-05-03", - "articleImageUrl": "/images/articles/sysadmin-diaries-1600x900@2x.png", - "description": "In this sysadmin diary, we explore a the differences in device enrollment." - } - }, - { - "url": "/guides/sysadmin-diaries-exporting-policies", - "title": "Sysadmin diaries exporting policies", - "lastModifiedAt": 1726839805190, - "htmlId": "articles--sysadmin-diaries-exp--a101d98c97", - "sectionRelativeRepoPath": "sysadmin-diaries-exporting-policies.md", - "meta": { - "articleTitle": "Sysadmin diaries: exporting policies", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "category": "guides", - "publishedOn": "2024-06-28", - "articleImageUrl": "/images/articles/sysadmin-diaries-1600x900@2x.png", - "description": "In this sysadmin diary, we explore extracting existing policies to enable gitops." - } - }, - { - "url": "/guides/sysadmin-diaries-lost-device", - "title": "Sysadmin diaries lost device", - "lastModifiedAt": 1726839805191, - "htmlId": "articles--sysadmin-diaries-los--3bcb909203", - "sectionRelativeRepoPath": "sysadmin-diaries-lost-device.md", - "meta": { - "articleTitle": "Sysadmin diaries: lost device", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "category": "guides", - "publishedOn": "2024-07-09", - "articleImageUrl": "/images/articles/sysadmin-diaries-1600x900@2x.png", - "description": "In this sysadmin diary, we explore what actions can be taken with Fleet when a device is lost." - } - }, - { - "url": "/guides/sysadmin-diaries-passcode-profiles", - "title": "Sysadmin diaries passcode profiles", - "lastModifiedAt": 1726839805192, - "htmlId": "articles--sysadmin-diaries-pas--883471875d", - "sectionRelativeRepoPath": "sysadmin-diaries-passcode-profiles.md", - "meta": { - "articleTitle": "Sysadmin diaries: passcode profiles", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "category": "guides", - "publishedOn": "2024-04-01", - "articleImageUrl": "/images/articles/sysadmin-diaries-1600x900@2x.png", - "description": "In this sysadmin diary, we explore a missapplied passcode policy." - } - }, - { - "url": "/guides/sysadmin-diaries-restoring-fleetd", - "title": "Sysadmin diaries restoring fleetd", - "lastModifiedAt": 1726839805193, - "htmlId": "articles--sysadmin-diaries-res--96c547e138", - "sectionRelativeRepoPath": "sysadmin-diaries-restoring-fleetd.md", - "meta": { - "articleTitle": "Sysadmin diaries: restoring fleetd", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "category": "guides", - "publishedOn": "2024-06-14", - "articleImageUrl": "/images/articles/sysadmin-diaries-1600x900@2x.png", - "description": "In this sysadmin diary, we explore restoring fleetd deleted by a surly employee." - } - }, - { - "url": "/securing/tales-from-fleet-security-github-configuration-and-openssf-scorecards", - "title": "Tales from Fleet security github configuration and openssf scorecards", - "lastModifiedAt": 1726839805194, - "htmlId": "articles--tales-from-fleet-sec--035c3d6474", - "sectionRelativeRepoPath": "tales-from-fleet-security-github-configuration-and-openssf-scorecards.md", - "meta": { - "category": "security", - "authorFullName": "Guillaume Ross", - "authorGitHubUsername": "GuillaumeRoss", - "publishedOn": "2022-04-15", - "articleTitle": "Tales from Fleet security: GitHub configuration and OpenSSF Scorecards", - "articleImageUrl": "/images/articles/tales-from-fleet-security-github-configuration-and-openssf-scorecards-cover-1600x900@2x.jpg" - } - }, - { - "url": "/securing/tales-from-fleet-security-google-groups-scams", - "title": "Tales from Fleet security google groups scams", - "lastModifiedAt": 1726839805195, - "htmlId": "articles--tales-from-fleet-sec--841598a71f", - "sectionRelativeRepoPath": "tales-from-fleet-security-google-groups-scams.md", - "meta": { - "category": "security", - "authorFullName": "Guillaume Ross", - "authorGitHubUsername": "GuillaumeRoss", - "publishedOn": "2022-08-05", - "articleTitle": "Tales from Fleet security: scams targeting Google Groups", - "articleImageUrl": "/images/articles/tales-from-fleet-security-google-groups-scams-cover-1600x900@2x.jpg" - } - }, - { - "url": "/securing/tales-from-fleet-security-securing-1password", - "title": "Tales from Fleet security securing 1password", - "lastModifiedAt": 1726839805196, - "htmlId": "articles--tales-from-fleet-sec--d172f39898", - "sectionRelativeRepoPath": "tales-from-fleet-security-securing-1password.md", - "meta": { - "category": "security", - "authorFullName": "Guillaume Ross", - "authorGitHubUsername": "GuillaumeRoss", - "publishedOn": "2022-05-06", - "articleTitle": "Tales from Fleet security: securing 1Password", - "articleImageUrl": "/images/articles/tales-from-fleet-security-securing-1password-cover-1600x900@2x.jpg" - } - }, - { - "url": "/securing/tales-from-fleet-security-securing-bank-accounts-from-business-email-compromise", - "title": "Tales from Fleet security securing bank accounts from business email compromise", - "lastModifiedAt": 1726839805198, - "htmlId": "articles--tales-from-fleet-sec--f60a0becab", - "sectionRelativeRepoPath": "tales-from-fleet-security-securing-bank-accounts-from-business-email-compromise.md", - "meta": { - "category": "security", - "authorFullName": "Guillaume Ross", - "authorGitHubUsername": "GuillaumeRoss", - "publishedOn": "2022-07-15", - "articleTitle": "Tales from Fleet security: securing bank accounts from business email compromise", - "articleImageUrl": "/images/articles/securing-bank-accounts-from-business-email-compromise-1600x900@2x.jpg" - } - }, - { - "url": "/securing/tales-from-fleet-security-securing-google-workspace", - "title": "Tales from Fleet security securing google workspace", - "lastModifiedAt": 1726839805199, - "htmlId": "articles--tales-from-fleet-sec--72efc9f80f", - "sectionRelativeRepoPath": "tales-from-fleet-security-securing-google-workspace.md", - "meta": { - "category": "security", - "authorFullName": "Guillaume Ross", - "authorGitHubUsername": "GuillaumeRoss", - "publishedOn": "2022-03-25", - "articleTitle": "Tales from Fleet security: securing Google Workspace", - "articleImageUrl": "/images/articles/tales-from-fleet-security-securing-google-workspace-cover-1600x900@2x.jpg" - } - }, - { - "url": "/securing/tales-from-fleet-security-securing-the-startup", - "title": "Tales from Fleet security securing the startup", - "lastModifiedAt": 1726839805200, - "htmlId": "articles--tales-from-fleet-sec--a25132f487", - "sectionRelativeRepoPath": "tales-from-fleet-security-securing-the-startup.md", - "meta": { - "category": "security", - "authorFullName": "Guillaume Ross", - "authorGitHubUsername": "GuillaumeRoss", - "publishedOn": "2022-03-17", - "articleTitle": "Tales from Fleet security: securing the startup", - "articleImageUrl": "/images/articles/tales-from-fleet-security-securing-the-startup-cover-1600x900@2x.jpg" - } - }, - { - "url": "/securing/tales-from-fleet-security-soc2", - "title": "Tales from Fleet security soc2", - "lastModifiedAt": 1726839805202, - "htmlId": "articles--tales-from-fleet-sec--f537169e1e", - "sectionRelativeRepoPath": "tales-from-fleet-security-soc2.md", - "meta": { - "category": "security", - "authorGitHubUsername": "GuillaumeRoss", - "authorFullName": "Guillaume Ross", - "publishedOn": "2022-06-24", - "articleTitle": "Tales from Fleet security: how we achieved our SOC 2 type 1 rapidly", - "articleImageUrl": "/images/articles/tales-from-fleet-soc2-type1-report-cover-1600x900@2x.jpg" - } - }, - { - "url": "/securing/tales-from-fleet-security-speeding-up-macos-updates-with-nudge", - "title": "Tales from Fleet security speeding up macos updates with nudge", - "lastModifiedAt": 1726839805203, - "htmlId": "articles--tales-from-fleet-sec--41bc496d3c", - "sectionRelativeRepoPath": "tales-from-fleet-security-speeding-up-macos-updates-with-nudge.md", - "meta": { - "category": "security", - "authorFullName": "Guillaume Ross", - "authorGitHubUsername": "GuillaumeRoss", - "publishedOn": "2022-07-05", - "articleTitle": "Tales from Fleet security: speeding up macOS updates with Nudge", - "articleImageUrl": "/images/articles/tales-from-fleet-nudge-cover-1600x900@2x.jpg" - } - }, - { - "url": "/guides/teams", - "title": "Teams", - "lastModifiedAt": 1726839805204, - "htmlId": "articles--teams--a6aba53335", - "sectionRelativeRepoPath": "teams.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "noahtalerman", - "authorFullName": "Noah Talerman", - "publishedOn": "2024-07-11", - "articleTitle": "Teams", - "description": "Learn how to group hosts in Fleet to apply specific queries, policies, and agent options using teams." - } - }, - { - "url": "/announcements/the-device-security-tightrope-balancing-cost-and-protection-in-k-12-schools", - "title": "The device security tightrope balancing cost and protection in k 12 schools", - "lastModifiedAt": 1726839805205, - "htmlId": "articles--the-device-security---cba806f3e1", - "sectionRelativeRepoPath": "the-device-security-tightrope-balancing-cost-and-protection-in-K-12-schools.md", - "meta": { - "category": "announcements", - "authorFullName": "Keith Barnes", - "authorGitHubUsername": "KAB703", - "publishedOn": "2024-03-01", - "articleTitle": "The device security tightrope: balancing cost and protection in K-12 schools", - "articleImageUrl": "/images/articles/the-device-security-tightrope-balancing-cost-and-protection-in-K-12-schools-1600x900@2x.png" - } - }, - { - "url": "/podcasts/the-future-of-device-management-ep1", - "title": "The future of device management ep1", - "lastModifiedAt": 1726839805207, - "htmlId": "articles--the-future-of-device--e424a67517", - "sectionRelativeRepoPath": "the-future-of-device-management-ep1.md", - "meta": { - "category": "podcasts", - "authorGitHubUsername": "zwass", - "authorFullName": "Zach Wasserman", - "publishedOn": "2022-06-06", - "articleTitle": "Future of device management episode 1", - "articleImageUrl": "/images/articles/future-of-device-management-ep1-cover-1600x900@2x.jpg" - } - }, - { - "url": "/podcasts/the-future-of-device-management-ep2", - "title": "The future of device management ep2", - "lastModifiedAt": 1726839805208, - "htmlId": "articles--the-future-of-device--0b4ec299db", - "sectionRelativeRepoPath": "the-future-of-device-management-ep2.md", - "meta": { - "category": "podcasts", - "authorGitHubUsername": "zwass", - "authorFullName": "Zach Wasserman", - "publishedOn": "2022-06-30", - "articleTitle": "Future of device management episode 2", - "articleImageUrl": "/images/articles/future-of-device-management-ep2-cover-1600x900@2x.jpg" - } - }, - { - "url": "/podcasts/the-future-of-device-management-ep3", - "title": "The future of device management ep3", - "lastModifiedAt": 1726839805209, - "htmlId": "articles--the-future-of-device--d7b8d1fbfe", - "sectionRelativeRepoPath": "the-future-of-device-management-ep3.md", - "meta": { - "category": "podcasts", - "authorGitHubUsername": "zwass", - "authorFullName": "Zach Wasserman", - "publishedOn": "2022-07-21", - "articleTitle": "Future of device management episode 3", - "articleImageUrl": "/images/articles/future-of-device-management-ep3-cover-1600x900@2x.jpg" - } - }, - { - "url": "/podcasts/the-future-of-device-management-ep4", - "title": "The future of device management ep4", - "lastModifiedAt": 1726839805210, - "htmlId": "articles--the-future-of-device--bd6c88c590", - "sectionRelativeRepoPath": "the-future-of-device-management-ep4.md", - "meta": { - "category": "podcasts", - "authorGitHubUsername": "zwass", - "authorFullName": "Zach Wasserman", - "publishedOn": "2022-08-12", - "articleTitle": "Future of device management episode 4", - "articleImageUrl": "/images/articles/future-of-device-management-ep4-cover-1600x900@2x.jpg" - } - }, - { - "url": "/podcasts/the-future-of-device-management-ep5", - "title": "The future of device management ep5", - "lastModifiedAt": 1726839805210, - "htmlId": "articles--the-future-of-device--c5ce4719fa", - "sectionRelativeRepoPath": "the-future-of-device-management-ep5.md", - "meta": { - "category": "podcasts", - "authorGitHubUsername": "zwass", - "authorFullName": "Zach Wasserman", - "publishedOn": "2022-09-01", - "articleTitle": "Future of device management episode 5", - "articleImageUrl": "/images/articles/future-of-device-management-ep5-cover-1600x900@2x.jpg" - } - }, - { - "url": "/podcasts/the-future-of-device-management-ep6", - "title": "The future of device management ep6", - "lastModifiedAt": 1726839805211, - "htmlId": "articles--the-future-of-device--141153d341", - "sectionRelativeRepoPath": "the-future-of-device-management-ep6.md", - "meta": { - "category": "podcasts", - "authorGitHubUsername": "zwass", - "authorFullName": "Zach Wasserman", - "publishedOn": "2022-09-23", - "articleTitle": "Future of device management episode 6", - "articleImageUrl": "/images/articles/future-of-device-management-ep6-cover-1600x900@2x.jpg" - } - }, - { - "url": "/podcasts/the-future-of-device-management-ep7", - "title": "The future of device management ep7", - "lastModifiedAt": 1726839805212, - "htmlId": "articles--the-future-of-device--52a1db0bde", - "sectionRelativeRepoPath": "the-future-of-device-management-ep7.md", - "meta": { - "category": "podcasts", - "authorGitHubUsername": "zwass", - "authorFullName": "Zach Wasserman", - "publishedOn": "2022-11-03", - "articleTitle": "Future of device management episode 7", - "articleImageUrl": "/images/articles/future-of-device-management-ep7-cover-1600x900@2x.jpg" - } - }, - { - "url": "/guides/understanding-the-intricacies-of-fleet-policies", - "title": "Understanding the intricacies of Fleet policies", - "lastModifiedAt": 1726839805213, - "htmlId": "articles--understanding-the-in--edae4ca064", - "sectionRelativeRepoPath": "understanding-the-intricacies-of-fleet-policies.md", - "meta": { - "articleTitle": "Understanding the intricacies of Fleet policies", - "authorFullName": "Victor Lyuboslavsky", - "authorGitHubUsername": "getvictor", - "category": "guides", - "publishedOn": "2023-12-29", - "description": "Learn how Fleet policies work behind the scenes." - } - }, - { - "url": "/guides/using-elasticsearch-and-kibana-to-visualize-osquery-performance", - "title": "Using elasticsearch and kibana to visualize osquery performance", - "lastModifiedAt": 1726839805215, - "htmlId": "articles--using-elasticsearch---55019ee35a", - "sectionRelativeRepoPath": "using-elasticsearch-and-kibana-to-visualize-osquery-performance.md", - "meta": { - "category": "guides", - "authorFullName": "Zach Wasserman", - "authorGitHubUsername": "zwass", - "publishedOn": "2021-05-26", - "articleTitle": "Using Elasticsearch and Kibana to visualize osquery performance", - "articleImageUrl": "/images/articles/using-elasticsearch-and-kibana-to-visualize-osquery-performance-cover-700x393@2x.jpeg" - } - }, - { - "url": "/guides/using-fleet-and-okta-workflows-to-generate-a-daily-os-report", - "title": "Using Fleet and okta workflows to generate a daily os report", - "lastModifiedAt": 1726839805218, - "htmlId": "articles--using-fleet-and-okta--a4676a8577", - "sectionRelativeRepoPath": "using-fleet-and-okta-workflows-to-generate-a-daily-os-report.md", - "meta": { - "articleTitle": "Using Fleet and Okta Workflows to generate a daily OS report", - "authorFullName": "Harrison Ravazzolo", - "authorGitHubUsername": "harrisonravazzolo", - "category": "guides", - "publishedOn": "2023-05-09", - "articleImageUrl": "/images/articles/using-fleet-and-okta-workflows-to-generate-a-daily-os-report@2x.jpg", - "description": "Learn how to use Fleet to query device OS information through the Fleet REST API and automate daily Slack notifications using Okta Workflows." - } - }, - { - "url": "/guides/using-fleet-and-tines-together", - "title": "Using Fleet and tines together", - "lastModifiedAt": 1726839805219, - "htmlId": "articles--using-fleet-and-tine--3606f85672", - "sectionRelativeRepoPath": "using-fleet-and-tines-together.md", - "meta": { - "category": "guides", - "authorFullName": "Dave Herder", - "authorGitHubUsername": "dherder", - "publishedOn": "2023-03-08", - "articleTitle": "Using Fleet and Tines together", - "articleImageUrl": "/images/articles/using-fleet-and-tines-together-1600x900@2x.png" - } - }, - { - "url": "/guides/using-github-actions-to-apply-configuration-profiles-with-fleet", - "title": "Using github actions to apply configuration profiles with Fleet", - "lastModifiedAt": 1726839805220, - "htmlId": "articles--using-github-actions--d966ed0177", - "sectionRelativeRepoPath": "using-github-actions-to-apply-configuration-profiles-with-fleet.md", - "meta": { - "articleTitle": "Using GitHub Actions to apply configuration profiles with Fleet", - "authorFullName": "JD Strong", - "authorGitHubUsername": "spokanemac", - "category": "guides", - "publishedOn": "2023-05-31", - "articleImageUrl": "/images/articles/using-github-actions-to-apply-configuration-profiles-with-fleet@2x.jpg", - "description": "A guide on using GitHub Actions with Fleet for efficient and automated application of the latest configuration profiles for a GitOps workflow." - } - }, - { - "url": "/securing/vulnerability-management-the-advantages-of-fleet-to-support-government-agencies", - "title": "Vulnerability management the advantages of Fleet to support government agencies", - "lastModifiedAt": 1726839805221, - "htmlId": "articles--vulnerability-manage--fae19ad566", - "sectionRelativeRepoPath": "vulnerability-management-the-advantages-of-fleet-to-support-government-agencies.md", - "meta": { - "category": "security", - "authorFullName": "Keith Barnes", - "authorGitHubUsername": "KAB703", - "publishedOn": "2023-12-26", - "articleTitle": "Vulnerability management: advantages of Fleet to support government agencies", - "articleImageUrl": "/images/articles/vulnerability-management-advantages-of-fleet-to-support-government-agencies-1600x900@2x.png" - } - }, - { - "url": "/guides/vulnerability-processing", - "title": "Vulnerability processing", - "lastModifiedAt": 1726839805222, - "htmlId": "articles--vulnerability-proces--244a2b70ee", - "sectionRelativeRepoPath": "vulnerability-processing.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "noahtalerman", - "authorFullName": "Noah Talerman", - "publishedOn": "2024-07-12", - "articleTitle": "Vulnerability processing", - "description": "Find out how Fleet detects vulnerabilities and what software it covers." - } - }, - { - "url": "/guides/what-api-endpoints-to-expose-to-the-public-internet", - "title": "What API endpoints to expose to the public internet", - "lastModifiedAt": 1726839805223, - "htmlId": "articles--what-api-endpoints-t--cd0552d444", - "sectionRelativeRepoPath": "what-api-endpoints-to-expose-to-the-public-internet.md", - "meta": { - "category": "guides", - "authorGitHubUsername": "mike-j-thomas", - "authorFullName": "Mike Thomas", - "publishedOn": "2023-11-13", - "articleTitle": "Which API endpoints to expose to the public internet?" - } - }, - { - "url": "/securing/what-are-fleet-policies", - "title": "What are Fleet policies", - "lastModifiedAt": 1726839805224, - "htmlId": "articles--what-are-fleet-polic--d8ca2da611", - "sectionRelativeRepoPath": "what-are-fleet-policies.md", - "meta": { - "category": "security", - "authorGitHubUsername": "Drew-P-drawers", - "authorFullName": "Andrew Baker", - "publishedOn": "2022-05-20", - "articleTitle": "What are Fleet policies?", - "articleImageUrl": "/images/articles/what-are-fleet-policies-cover-1600x900@2x.jpg" - } - }, - { - "url": "/guides/windows-mdm-setup", - "title": "Windows mdm setup", - "lastModifiedAt": 1726839805226, - "htmlId": "articles--windows-mdm-setup--ebf4ebf0ba", - "sectionRelativeRepoPath": "windows-mdm-setup.md", - "meta": { - "articleTitle": "Windows MDM setup", - "authorFullName": "Noah Talerman", - "authorGitHubUsername": "noahtalerman", - "category": "guides", - "publishedOn": "2023-10-23", - "articleImageUrl": "/images/articles/windows-mdm-fleet-1600x900@2x.png", - "description": "Configuring Windows MDM in Fleet." - } - }, - { - "url": "/guides/zero-trust-attestation-with-fleet", - "title": "Zero trust attestation with Fleet", - "lastModifiedAt": 1726839805227, - "htmlId": "articles--zero-trust-attestati--b892a54252", - "sectionRelativeRepoPath": "zero-trust-attestation-with-fleet.md", - "meta": { - "articleTitle": "How to use Fleet for zero trust attestation", - "authorFullName": "Mo Zhu", - "authorGitHubUsername": "zhumo", - "category": "guides", - "publishedOn": "2022-10-14", - "articleImageUrl": "/images/articles/fleet-for-zero-trust-attestation-800x450@2x.jpg" - } - }, - { - "url": "/securing/work-may-be-watching-but-it-might-not-be-as-bad-as-you-think", - "title": "Work may be watching but it might not be as bad as you think", - "lastModifiedAt": 1726839805227, - "htmlId": "articles--work-may-be-watching--420e065d2f", - "sectionRelativeRepoPath": "work-may-be-watching-but-it-might-not-be-as-bad-as-you-think.md", - "meta": { - "category": "security", - "authorFullName": "Mike Thomas", - "authorGitHubUsername": "mike-j-thomas", - "publishedOn": "2021-10-22", - "articleTitle": "Work may be watching, but it might not be as bad as you think.", - "articleImageUrl": "/images/articles/work-may-be-watching-but-it-might-not-be-as-bad-as-you-think-cover-1600x900@2x.jpg" - } - }, - { - "url": "/handbook/company/open-positions/software-engineer", - "title": "🚀 Software Engineer", - "lastModifiedAt": 1726839805228, - "htmlId": "handbook--software-engineer--be50029cfb", - "sectionRelativeRepoPath": "company/open-positions.yml", - "meta": { - "maintainedBy": "LukeHeath" - } - }, - { - "url": "/handbook/company/open-positions/account-executive", - "title": "🐋 Account Executive", - "lastModifiedAt": 1726839805228, - "htmlId": "handbook--account-executive--d5def7dc8f", - "sectionRelativeRepoPath": "company/open-positions.yml", - "meta": { - "maintainedBy": "alexmitchelliii" - } - }, - { - "url": "/tables/account_policy_data", - "title": "account_policy_data", - "htmlId": "table--accountpolicydata--31df68b22b", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "account_policy_data", - "creation_time", - "failed_login_count", - "failed_login_timestamp", - "password_last_set_time", - "uid" - ], - "sectionRelativeRepoPath": "account_policy_data", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/account_policy_data.yml" - }, - { - "url": "/tables/ad_config", - "title": "ad_config", - "htmlId": "table--adconfig--39d2211d09", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "ad_config", - "domain", - "name", - "option", - "value" - ], - "sectionRelativeRepoPath": "ad_config", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/ad_config.yml" - }, - { - "url": "/tables/alf", - "title": "alf", - "htmlId": "table--alf--4c28031b0f", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "alf", - "allow_signed_enabled", - "firewall_unload", - "global_state", - "logging_enabled", - "logging_option", - "stealth_enabled", - "version" - ], - "sectionRelativeRepoPath": "alf", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/alf.yml" - }, - { - "url": "/tables/alf_exceptions", - "title": "alf_exceptions", - "htmlId": "table--alfexceptions--1fbd2a6157", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "alf_exceptions", - "path", - "state" - ], - "sectionRelativeRepoPath": "alf_exceptions", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/alf_exceptions.yml" - }, - { - "url": "/tables/alf_explicit_auths", - "title": "alf_explicit_auths", - "htmlId": "table--alfexplicitauths--4b47436520", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "alf_explicit_auths", - "process" - ], - "sectionRelativeRepoPath": "alf_explicit_auths", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/alf_explicit_auths.yml" - }, - { - "url": "/tables/apfs_physical_stores", - "title": "apfs_physical_stores", - "htmlId": "table--apfsphysicalstores--30af4e1d13", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "apfs_physical_stores", - "container_capacity_ceiling", - "container_capacity_free", - "container_designated_physical_store", - "container_fusion", - "container_reference", - "container_uuid", - "identifier", - "size", - "uuid" - ], - "sectionRelativeRepoPath": "apfs_physical_stores", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/apfs_physical_stores.yml" - }, - { - "url": "/tables/apfs_volumes", - "title": "apfs_volumes", - "htmlId": "table--apfsvolumes--d8e8cc281d", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "apfs_volumes", - "capacity_in_use", - "capacity_quota", - "capacity_reserve", - "container_capacity_ceiling", - "container_capacity_free", - "container_designated_physical_store", - "container_fusion", - "container_reference", - "container_uuid", - "crypto_migration_on", - "device_identifier", - "encryption", - "filevault", - "locked", - "name", - "role", - "uuid" - ], - "sectionRelativeRepoPath": "apfs_volumes", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/apfs_volumes.yml" - }, - { - "url": "/tables/app_icons", - "title": "app_icons", - "htmlId": "table--appicons--93bed0002f", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "app_icons", - "hash", - "icon", - "path" - ], - "sectionRelativeRepoPath": "app_icons", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/app_icons.yml" - }, - { - "url": "/tables/app_schemes", - "title": "app_schemes", - "htmlId": "table--appschemes--e75c685f8d", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "app_schemes", - "enabled", - "external", - "handler", - "protected", - "scheme" - ], - "sectionRelativeRepoPath": "app_schemes", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/app_schemes.yml" - }, - { - "url": "/tables/apparmor_events", - "title": "apparmor_events", - "htmlId": "table--apparmorevents--1b9b1af186", - "evented": true, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "apparmor_events", - "apparmor", - "capability", - "capname", - "comm", - "denied_mask", - "eid", - "error", - "fsuid", - "info", - "label", - "message", - "name", - "namespace", - "operation", - "ouid", - "parent", - "pid", - "profile", - "requested_mask", - "time", - "type", - "uptime" - ], - "sectionRelativeRepoPath": "apparmor_events", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fapparmor_events.yml&value=name%3A%20apparmor_events%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/apparmor_profiles", - "title": "apparmor_profiles", - "htmlId": "table--apparmorprofiles--49f0f69437", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "apparmor_profiles", - "attach", - "mode", - "name", - "path", - "sha1" - ], - "sectionRelativeRepoPath": "apparmor_profiles", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fapparmor_profiles.yml&value=name%3A%20apparmor_profiles%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/appcompat_shims", - "title": "appcompat_shims", - "htmlId": "table--appcompatshims--33b5da402f", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "appcompat_shims", - "description", - "executable", - "install_time", - "path", - "sdb_id", - "type" - ], - "sectionRelativeRepoPath": "appcompat_shims", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fappcompat_shims.yml&value=name%3A%20appcompat_shims%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/apps", - "title": "apps", - "htmlId": "table--apps--ccdee150a9", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "apps", - "applescript_enabled", - "bundle_executable", - "bundle_identifier", - "bundle_name", - "bundle_package_type", - "bundle_short_version", - "bundle_version", - "category", - "compiler", - "copyright", - "development_region", - "display_name", - "element", - "environment", - "info_string", - "last_opened_time", - "minimum_system_version", - "name", - "path" - ], - "sectionRelativeRepoPath": "apps", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/apps.yml" - }, - { - "url": "/tables/apt_sources", - "title": "apt_sources", - "htmlId": "table--aptsources--a209051a90", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "apt_sources", - "architectures", - "base_uri", - "components", - "maintainer", - "name", - "pid_with_namespace", - "release", - "source", - "version" - ], - "sectionRelativeRepoPath": "apt_sources", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/apt_sources.yml" - }, - { - "url": "/tables/arp_cache", - "title": "arp_cache", - "htmlId": "table--arpcache--83f95510b6", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "arp_cache", - "address", - "interface", - "mac", - "permanent" - ], - "sectionRelativeRepoPath": "arp_cache", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/arp_cache.yml" - }, - { - "url": "/tables/asl", - "title": "asl", - "htmlId": "table--asl--d2accdbfe3", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "asl", - "extra", - "facility", - "gid", - "host", - "level", - "message", - "pid", - "ref_pid", - "ref_proc", - "sender", - "time", - "time_nano_sec", - "uid" - ], - "sectionRelativeRepoPath": "asl", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/asl.yml" - }, - { - "url": "/tables/augeas", - "title": "augeas", - "htmlId": "table--augeas--b316cda7a7", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "augeas", - "label", - "node", - "path", - "value" - ], - "sectionRelativeRepoPath": "augeas", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/augeas.yml" - }, - { - "url": "/tables/authdb", - "title": "authdb", - "htmlId": "table--authdb--a304d751e5", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "authdb", - "json_result", - "right_name" - ], - "sectionRelativeRepoPath": "authdb", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/authdb.yml" - }, - { - "url": "/tables/authenticode", - "title": "authenticode", - "htmlId": "table--authenticode--0de9da48eb", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "authenticode", - "issuer_name", - "original_program_name", - "path", - "result", - "serial_number", - "subject_name" - ], - "sectionRelativeRepoPath": "authenticode", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fauthenticode.yml&value=name%3A%20authenticode%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/authorization_mechanisms", - "title": "authorization_mechanisms", - "htmlId": "table--authorizationmechanisms--d2490cb436", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "authorization_mechanisms", - "entry", - "label", - "mechanism", - "plugin", - "privileged" - ], - "sectionRelativeRepoPath": "authorization_mechanisms", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/authorization_mechanisms.yml" - }, - { - "url": "/tables/authorizations", - "title": "authorizations", - "htmlId": "table--authorizations--7fb6b733e8", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "authorizations", - "allow_root", - "authenticate_user", - "class", - "comment", - "created", - "label", - "modified", - "session_owner", - "shared", - "timeout", - "tries", - "version" - ], - "sectionRelativeRepoPath": "authorizations", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/authorizations.yml" - }, - { - "url": "/tables/authorized_keys", - "title": "authorized_keys", - "htmlId": "table--authorizedkeys--5108700dee", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "authorized_keys", - "algorithm", - "comment", - "key", - "key_file", - "options", - "pid_with_namespace", - "uid" - ], - "sectionRelativeRepoPath": "authorized_keys", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/authorized_keys.yml" - }, - { - "url": "/tables/autoexec", - "title": "autoexec", - "htmlId": "table--autoexec--ab98111b94", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "autoexec", - "name", - "path", - "source" - ], - "sectionRelativeRepoPath": "autoexec", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fautoexec.yml&value=name%3A%20autoexec%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/azure_instance_metadata", - "title": "azure_instance_metadata", - "htmlId": "table--azureinstancemetadata--01df1dde23", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "azure_instance_metadata", - "location", - "name", - "offer", - "os_type", - "placement_group_id", - "platform_fault_domain", - "platform_update_domain", - "publisher", - "resource_group_name", - "sku", - "subscription_id", - "version", - "vm_id", - "vm_scale_set_name", - "vm_size", - "zone" - ], - "sectionRelativeRepoPath": "azure_instance_metadata", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/azure_instance_metadata.yml" - }, - { - "url": "/tables/azure_instance_tags", - "title": "azure_instance_tags", - "htmlId": "table--azureinstancetags--166e2b6f18", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "azure_instance_tags", - "key", - "value", - "vm_id" - ], - "sectionRelativeRepoPath": "azure_instance_tags", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/azure_instance_tags.yml" - }, - { - "url": "/tables/background_activities_moderator", - "title": "background_activities_moderator", - "htmlId": "table--backgroundactivitiesmoderator--12072ab407", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "background_activities_moderator", - "last_execution_time", - "path", - "sid" - ], - "sectionRelativeRepoPath": "background_activities_moderator", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fbackground_activities_moderator.yml&value=name%3A%20background_activities_moderator%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/battery", - "title": "battery", - "htmlId": "table--battery--e54a7e368b", - "evented": false, - "platforms": [ - "darwin", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "battery", - "amperage", - "charged", - "charging", - "chemistry", - "condition", - "current_capacity", - "cycle_count", - "designed_capacity", - "health", - "manufacture_date", - "manufacturer", - "max_capacity", - "minutes_to_full_charge", - "minutes_until_empty", - "model", - "percent_remaining", - "serial_number", - "state", - "voltage" - ], - "sectionRelativeRepoPath": "battery", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/battery.yml" - }, - { - "url": "/tables/bitlocker_info", - "title": "bitlocker_info", - "htmlId": "table--bitlockerinfo--277b4f7713", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "bitlocker_info", - "conversion_status", - "device_id", - "drive_letter", - "encryption_method", - "lock_status", - "percentage_encrypted", - "persistent_volume_id", - "protection_status", - "version" - ], - "sectionRelativeRepoPath": "bitlocker_info", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/bitlocker_info.yml" - }, - { - "url": "/tables/block_devices", - "title": "block_devices", - "htmlId": "table--blockdevices--3db1d23d7b", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "block_devices", - "block_size", - "label", - "model", - "name", - "parent", - "size", - "type", - "uuid", - "vendor" - ], - "sectionRelativeRepoPath": "block_devices", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/block_devices.yml" - }, - { - "url": "/tables/bpf_process_events", - "title": "bpf_process_events", - "htmlId": "table--bpfprocessevents--f98d50f0c4", - "evented": true, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "bpf_process_events", - "cid", - "cmdline", - "cwd", - "duration", - "eid", - "exit_code", - "gid", - "json_cmdline", - "ntime", - "parent", - "path", - "pid", - "probe_error", - "syscall", - "tid", - "time", - "uid" - ], - "sectionRelativeRepoPath": "bpf_process_events", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fbpf_process_events.yml&value=name%3A%20bpf_process_events%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/bpf_socket_events", - "title": "bpf_socket_events", - "htmlId": "table--bpfsocketevents--2bbe58be1b", - "evented": true, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "bpf_socket_events", - "cid", - "duration", - "eid", - "exit_code", - "family", - "fd", - "gid", - "local_address", - "local_port", - "ntime", - "parent", - "path", - "pid", - "probe_error", - "protocol", - "remote_address", - "remote_port", - "syscall", - "tid", - "time", - "type", - "uid" - ], - "sectionRelativeRepoPath": "bpf_socket_events", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fbpf_socket_events.yml&value=name%3A%20bpf_socket_events%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/carbon_black_info", - "title": "carbon_black_info", - "htmlId": "table--carbonblackinfo--1a7333701d", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "carbon_black_info", - "binary_queue", - "collect_cross_processes", - "collect_data_file_writes", - "collect_emet_events", - "collect_file_mods", - "collect_module_info", - "collect_module_loads", - "collect_net_conns", - "collect_process_user_context", - "collect_processes", - "collect_reg_mods", - "collect_sensor_operations", - "collect_store_files", - "config_name", - "event_queue", - "log_file_disk_quota_mb", - "log_file_disk_quota_percentage", - "protection_disabled", - "sensor_backend_server", - "sensor_id", - "sensor_ip_addr" - ], - "sectionRelativeRepoPath": "carbon_black_info", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/carbon_black_info.yml" - }, - { - "url": "/tables/carves", - "title": "carves", - "htmlId": "table--carves--faab2a865e", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "carves", - "carve", - "carve_guid", - "path", - "request_id", - "sha256", - "size", - "status", - "time" - ], - "sectionRelativeRepoPath": "carves", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fcarves.yml&value=name%3A%20carves%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/certificates", - "title": "certificates", - "htmlId": "table--certificates--e853dcf612", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "certificates", - "authority_key_id", - "ca", - "common_name", - "issuer", - "issuer2", - "key_algorithm", - "key_strength", - "key_usage", - "not_valid_after", - "not_valid_before", - "path", - "self_signed", - "serial", - "sha1", - "sid", - "signing_algorithm", - "store", - "store_id", - "store_location", - "subject", - "subject2", - "subject_key_id", - "username" - ], - "sectionRelativeRepoPath": "certificates", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/certificates.yml" - }, - { - "url": "/tables/chassis_info", - "title": "chassis_info", - "htmlId": "table--chassisinfo--b4f2a373fd", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "chassis_info", - "audible_alarm", - "breach_description", - "chassis_types", - "description", - "lock", - "manufacturer", - "model", - "security_breach", - "serial", - "sku", - "smbios_tag", - "status", - "visible_alarm" - ], - "sectionRelativeRepoPath": "chassis_info", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fchassis_info.yml&value=name%3A%20chassis_info%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/chocolatey_packages", - "title": "chocolatey_packages", - "htmlId": "table--chocolateypackages--a948b45942", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "chocolatey_packages", - "author", - "license", - "name", - "path", - "summary", - "version" - ], - "sectionRelativeRepoPath": "chocolatey_packages", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fchocolatey_packages.yml&value=name%3A%20chocolatey_packages%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/chrome_extension_content_scripts", - "title": "chrome_extension_content_scripts", - "htmlId": "table--chromeextensioncontentscripts--90dfc8f7b0", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "chrome_extension_content_scripts", - "browser_type", - "identifier", - "match", - "path", - "profile_path", - "referenced", - "script", - "uid", - "version" - ], - "sectionRelativeRepoPath": "chrome_extension_content_scripts", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/chrome_extension_content_scripts.yml" - }, - { - "url": "/tables/chrome_extensions", - "title": "chrome_extensions", - "htmlId": "table--chromeextensions--0b832601b4", - "evented": false, - "platforms": [ - "darwin", - "windows", - "linux", - "chrome" - ], - "keywordsForSyntaxHighlighting": [ - "chrome_extensions", - "author", - "browser_type", - "current_locale", - "default_locale", - "description", - "from_webstore", - "identifier", - "install_time", - "install_timestamp", - "key", - "manifest_hash", - "manifest_json", - "name", - "optional_permissions", - "optional_permissions_json", - "path", - "permissions", - "permissions_json", - "persistent", - "profile", - "profile_path", - "referenced", - "referenced_identifier", - "state", - "uid", - "update_url", - "version" - ], - "sectionRelativeRepoPath": "chrome_extensions", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/chrome_extensions.yml" - }, - { - "url": "/tables/cis_audit", - "title": "cis_audit", - "htmlId": "table--cisaudit--021dcf9746", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "cis_audit", - "item", - "value" - ], - "sectionRelativeRepoPath": "cis_audit", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/cis_audit.yml" - }, - { - "url": "/tables/connected_displays", - "title": "connected_displays", - "htmlId": "table--connecteddisplays--f57653bc5b", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "connected_displays", - "ambient_brightness_enabled", - "connection_type", - "display_id", - "display_type", - "main", - "manufactured_week", - "manufactured_year", - "mirror", - "name", - "online", - "pixels", - "product_id", - "resolution", - "rotation", - "serial_number", - "vendor_id" - ], - "sectionRelativeRepoPath": "connected_displays", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fconnected_displays.yml&value=name%3A%20connected_displays%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/connectivity", - "title": "connectivity", - "htmlId": "table--connectivity--9bd961f435", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "connectivity", - "disconnected", - "ipv4_internet", - "ipv4_local_network", - "ipv4_no_traffic", - "ipv4_subnet", - "ipv6_internet", - "ipv6_local_network", - "ipv6_no_traffic", - "ipv6_subnet" - ], - "sectionRelativeRepoPath": "connectivity", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fconnectivity.yml&value=name%3A%20connectivity%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/corestorage_logical_volume_families", - "title": "corestorage_logical_volume_families", - "htmlId": "table--corestoragelogicalvolumefamilies--c844b6943f", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "corestorage_logical_volume_families", - "EncryptionStatus", - "EncryptionType", - "HasVisibleUsers", - "HasVolumeKey", - "IsAcceptingNewUsers", - "IsFullySecure", - "MayHaveEncryptedEvents", - "RequiresPasswordUnlock", - "UUID", - "vg_FreeSpace", - "vg_FusionDrive", - "vg_Name", - "vg_Sequence", - "vg_Size", - "vg_Sparse", - "vg_Status", - "vg_UUID", - "vg_Version" - ], - "sectionRelativeRepoPath": "corestorage_logical_volume_families", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/corestorage_logical_volume_families.yml" - }, - { - "url": "/tables/corestorage_logical_volumes", - "title": "corestorage_logical_volumes", - "htmlId": "table--corestoragelogicalvolumes--b32c10c6c2", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "corestorage_logical_volumes", - "ContentHint", - "ConversionState", - "ConverstionProgressPercent", - "DesignatedPhysicalVolume", - "DesignatedPhysicalVolumeIdentifier", - "Identifier", - "Name", - "Sequence", - "Size", - "Status", - "UUID", - "Version", - "VolumeName", - "lvf_EncryptionStatus", - "lvf_EncryptionType", - "lvf_HasVisibleUsers", - "lvf_HasVolumeKey", - "lvf_IsAcceptingNewUsers", - "lvf_IsFullySecure", - "lvf_MayHaveEncryptedEvents", - "lvf_RequiresPasswordUnlock", - "lvf_UUID", - "vg_FreeSpace", - "vg_FusionDrive", - "vg_Name", - "vg_Sequence", - "vg_Size", - "vg_Sparse", - "vg_Status", - "vg_UUID", - "vg_Version" - ], - "sectionRelativeRepoPath": "corestorage_logical_volumes", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/corestorage_logical_volumes.yml" - }, - { - "url": "/tables/cpu_info", - "title": "cpu_info", - "htmlId": "table--cpuinfo--aa3c0cfb0c", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "cpu_info", - "address_width", - "availability", - "cpu_status", - "current_clock_speed", - "device_id", - "load_percentage", - "logical_processors", - "manufacturer", - "max_clock_speed", - "model", - "number_of_cores", - "number_of_efficiency_cores", - "number_of_performance_cores", - "processor_type", - "socket_designation" - ], - "sectionRelativeRepoPath": "cpu_info", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/cpu_info.yml" - }, - { - "url": "/tables/cpu_time", - "title": "cpu_time", - "htmlId": "table--cputime--8f68637ee3", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "cpu_time", - "core", - "guest", - "guest_nice", - "idle", - "iowait", - "irq", - "nice", - "softirq", - "steal", - "system", - "user" - ], - "sectionRelativeRepoPath": "cpu_time", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/cpu_time.yml" - }, - { - "url": "/tables/cpuid", - "title": "cpuid", - "htmlId": "table--cpuid--68704a46e7", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "cpuid", - "feature", - "input_eax", - "output_bit", - "output_register", - "value" - ], - "sectionRelativeRepoPath": "cpuid", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/cpuid.yml" - }, - { - "url": "/tables/crashes", - "title": "crashes", - "htmlId": "table--crashes--6bccea7c2f", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "crashes", - "crash_path", - "crashed_thread", - "datetime", - "exception_codes", - "exception_notes", - "exception_type", - "identifier", - "parent", - "path", - "pid", - "registers", - "responsible", - "stack_trace", - "type", - "uid", - "version" - ], - "sectionRelativeRepoPath": "crashes", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/crashes.yml" - }, - { - "url": "/tables/crontab", - "title": "crontab", - "htmlId": "table--crontab--a8fe1b5316", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "crontab", - "command", - "day_of_month", - "day_of_week", - "event", - "hour", - "minute", - "month", - "path", - "pid_with_namespace" - ], - "sectionRelativeRepoPath": "crontab", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/crontab.yml" - }, - { - "url": "/tables/cryptoinfo", - "title": "cryptoinfo", - "htmlId": "table--cryptoinfo--5e90627c08", - "evented": false, - "platforms": [ - "darwin", - "windows", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "cryptoinfo", - "fullkey", - "key", - "parent", - "passphrase", - "path", - "query", - "value" - ], - "sectionRelativeRepoPath": "cryptoinfo", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/cryptoinfo.yml" - }, - { - "url": "/tables/cryptsetup_status", - "title": "cryptsetup_status", - "htmlId": "table--cryptsetupstatus--3aa1264a26", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "cryptsetup_status", - "fullkey", - "key", - "name", - "parent", - "query", - "value" - ], - "sectionRelativeRepoPath": "cryptsetup_status", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/cryptsetup_status.yml" - }, - { - "url": "/tables/csrutil_info", - "title": "csrutil_info", - "htmlId": "table--csrutilinfo--959d823b8e", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "csrutil_info", - "ssv_enabled" - ], - "sectionRelativeRepoPath": "csrutil_info", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/csrutil_info.yml" - }, - { - "url": "/tables/cups_destinations", - "title": "cups_destinations", - "htmlId": "table--cupsdestinations--8ccb3721f2", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "cups_destinations", - "name", - "option_name", - "option_value" - ], - "sectionRelativeRepoPath": "cups_destinations", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/cups_destinations.yml" - }, - { - "url": "/tables/cups_jobs", - "title": "cups_jobs", - "htmlId": "table--cupsjobs--3268465efb", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "cups_jobs", - "completed_time", - "creation_time", - "destination", - "format", - "processing_time", - "size", - "title", - "user" - ], - "sectionRelativeRepoPath": "cups_jobs", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/cups_jobs.yml" - }, - { - "url": "/tables/curl", - "title": "curl", - "htmlId": "table--curl--2ab03fa14d", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "curl", - "bytes", - "method", - "response_code", - "result", - "round_trip_time", - "url", - "user_agent" - ], - "sectionRelativeRepoPath": "curl", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/curl.yml" - }, - { - "url": "/tables/curl_certificate", - "title": "curl_certificate", - "htmlId": "table--curlcertificate--6d52c798b0", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "curl_certificate", - "authority_key_identifier", - "basic_constraint", - "common_name", - "dump_certificate", - "extended_key_usage", - "has_expired", - "hostname", - "info_access", - "issuer_alternative_names", - "issuer_common_name", - "issuer_organization", - "issuer_organization_unit", - "key_usage", - "name_constraints", - "organization", - "organization_unit", - "pem", - "policies", - "policy_constraints", - "policy_mappings", - "serial_number", - "sha1_fingerprint", - "sha256_fingerprint", - "signature", - "signature_algorithm", - "subject_alternative_names", - "subject_info_access", - "subject_key_identifier", - "timeout", - "valid_from", - "valid_to", - "version" - ], - "sectionRelativeRepoPath": "curl_certificate", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/curl_certificate.yml" - }, - { - "url": "/tables/deb_packages", - "title": "deb_packages", - "htmlId": "table--debpackages--f9f4ca0355", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "deb_packages", - "admindir", - "arch", - "maintainer", - "mount_namespace_id", - "name", - "pid_with_namespace", - "priority", - "revision", - "section", - "size", - "source", - "status", - "version" - ], - "sectionRelativeRepoPath": "deb_packages", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/deb_packages.yml" - }, - { - "url": "/tables/default_environment", - "title": "default_environment", - "htmlId": "table--defaultenvironment--ccbaea6671", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "default_environment", - "expand", - "value", - "variable" - ], - "sectionRelativeRepoPath": "default_environment", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fdefault_environment.yml&value=name%3A%20default_environment%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/device_file", - "title": "device_file", - "htmlId": "table--devicefile--e5267d9f3e", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "device_file", - "atime", - "block_size", - "ctime", - "device", - "filename", - "gid", - "hard_links", - "inode", - "mode", - "mtime", - "partition", - "path", - "size", - "type", - "uid" - ], - "sectionRelativeRepoPath": "device_file", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fdevice_file.yml&value=name%3A%20device_file%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/device_firmware", - "title": "device_firmware", - "htmlId": "table--devicefirmware--ab4ba7dd63", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "device_firmware", - "device", - "type", - "version" - ], - "sectionRelativeRepoPath": "device_firmware", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/device_firmware.yml" - }, - { - "url": "/tables/device_hash", - "title": "device_hash", - "htmlId": "table--devicehash--c839a630b0", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "device_hash", - "device", - "inode", - "md5", - "partition", - "sha1", - "sha256" - ], - "sectionRelativeRepoPath": "device_hash", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fdevice_hash.yml&value=name%3A%20device_hash%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/device_partitions", - "title": "device_partitions", - "htmlId": "table--devicepartitions--3489019e85", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "device_partitions", - "blocks", - "blocks_size", - "device", - "flags", - "inodes", - "label", - "offset", - "partition", - "type" - ], - "sectionRelativeRepoPath": "device_partitions", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fdevice_partitions.yml&value=name%3A%20device_partitions%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/disk_encryption", - "title": "disk_encryption", - "htmlId": "table--diskencryption--26d5b55253", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "disk_encryption", - "encrypted", - "encryption_status", - "filevault_status", - "name", - "type", - "uid", - "user_uuid", - "uuid" - ], - "sectionRelativeRepoPath": "disk_encryption", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/disk_encryption.yml" - }, - { - "url": "/tables/disk_events", - "title": "disk_events", - "htmlId": "table--diskevents--737534006f", - "evented": true, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "disk_events", - "action", - "checksum", - "content", - "device", - "eid", - "ejectable", - "filesystem", - "media_name", - "mountable", - "name", - "path", - "size", - "time", - "uuid", - "vendor", - "writable" - ], - "sectionRelativeRepoPath": "disk_events", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/disk_events.yml" - }, - { - "url": "/tables/disk_info", - "title": "disk_info", - "htmlId": "table--diskinfo--e7393d4e29", - "evented": false, - "platforms": [ - "windows", - "chrome" - ], - "keywordsForSyntaxHighlighting": [ - "disk_info", - "description", - "disk_index", - "disk_size", - "hardware_model", - "id", - "manufacturer", - "name", - "partitions", - "pnp_device_id", - "serial", - "type" - ], - "sectionRelativeRepoPath": "disk_info", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/disk_info.yml" - }, - { - "url": "/tables/dns_cache", - "title": "dns_cache", - "htmlId": "table--dnscache--dc0e67fdcf", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "dns_cache", - "flags", - "name", - "type" - ], - "sectionRelativeRepoPath": "dns_cache", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/dns_cache.yml" - }, - { - "url": "/tables/dns_resolvers", - "title": "dns_resolvers", - "htmlId": "table--dnsresolvers--2c8fb31e5d", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "dns_resolvers", - "address", - "id", - "netmask", - "options", - "pid_with_namespace", - "type" - ], - "sectionRelativeRepoPath": "dns_resolvers", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/dns_resolvers.yml" - }, - { - "url": "/tables/docker_container_envs", - "title": "docker_container_envs", - "htmlId": "table--dockercontainerenvs--3f92fabef8", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "docker_container_envs", - "id", - "key", - "value" - ], - "sectionRelativeRepoPath": "docker_container_envs", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/docker_container_envs.yml" - }, - { - "url": "/tables/docker_container_fs_changes", - "title": "docker_container_fs_changes", - "htmlId": "table--dockercontainerfschanges--e8a13529f8", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "docker_container_fs_changes", - "change_type", - "id", - "path" - ], - "sectionRelativeRepoPath": "docker_container_fs_changes", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fdocker_container_fs_changes.yml&value=name%3A%20docker_container_fs_changes%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/docker_container_labels", - "title": "docker_container_labels", - "htmlId": "table--dockercontainerlabels--525f815a85", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "docker_container_labels", - "id", - "key", - "value" - ], - "sectionRelativeRepoPath": "docker_container_labels", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/docker_container_labels.yml" - }, - { - "url": "/tables/docker_container_mounts", - "title": "docker_container_mounts", - "htmlId": "table--dockercontainermounts--feffa9b278", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "docker_container_mounts", - "destination", - "driver", - "id", - "mode", - "name", - "propagation", - "rw", - "source", - "type" - ], - "sectionRelativeRepoPath": "docker_container_mounts", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/docker_container_mounts.yml" - }, - { - "url": "/tables/docker_container_networks", - "title": "docker_container_networks", - "htmlId": "table--dockercontainernetworks--7482838a3b", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "docker_container_networks", - "endpoint_id", - "gateway", - "id", - "ip_address", - "ip_prefix_len", - "ipv6_address", - "ipv6_gateway", - "ipv6_prefix_len", - "mac_address", - "name", - "network_id" - ], - "sectionRelativeRepoPath": "docker_container_networks", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/docker_container_networks.yml" - }, - { - "url": "/tables/docker_container_ports", - "title": "docker_container_ports", - "htmlId": "table--dockercontainerports--8fa613bed0", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "docker_container_ports", - "host_ip", - "host_port", - "id", - "port", - "type" - ], - "sectionRelativeRepoPath": "docker_container_ports", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/docker_container_ports.yml" - }, - { - "url": "/tables/docker_container_processes", - "title": "docker_container_processes", - "htmlId": "table--dockercontainerprocesses--3790b40e9b", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "docker_container_processes", - "cmdline", - "cpu", - "egid", - "euid", - "gid", - "id", - "mem", - "name", - "nice", - "parent", - "pgroup", - "pid", - "resident_size", - "sgid", - "start_time", - "state", - "suid", - "threads", - "time", - "total_size", - "uid", - "user", - "wired_size" - ], - "sectionRelativeRepoPath": "docker_container_processes", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fdocker_container_processes.yml&value=name%3A%20docker_container_processes%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/docker_container_stats", - "title": "docker_container_stats", - "htmlId": "table--dockercontainerstats--55f8d1f434", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "docker_container_stats", - "cpu_kernelmode_usage", - "cpu_total_usage", - "cpu_usermode_usage", - "disk_read", - "disk_write", - "id", - "interval", - "memory_cached", - "memory_limit", - "memory_max_usage", - "memory_usage", - "name", - "network_rx_bytes", - "network_tx_bytes", - "num_procs", - "online_cpus", - "pids", - "pre_cpu_kernelmode_usage", - "pre_cpu_total_usage", - "pre_cpu_usermode_usage", - "pre_online_cpus", - "pre_system_cpu_usage", - "preread", - "read", - "system_cpu_usage" - ], - "sectionRelativeRepoPath": "docker_container_stats", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fdocker_container_stats.yml&value=name%3A%20docker_container_stats%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/docker_containers", - "title": "docker_containers", - "htmlId": "table--dockercontainers--e586f60cb7", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "docker_containers", - "cgroup_namespace", - "command", - "config_entrypoint", - "created", - "env_variables", - "finished_at", - "id", - "image", - "image_id", - "ipc_namespace", - "mnt_namespace", - "name", - "net_namespace", - "path", - "pid", - "pid_namespace", - "privileged", - "readonly_rootfs", - "security_options", - "started_at", - "state", - "status", - "user_namespace", - "uts_namespace" - ], - "sectionRelativeRepoPath": "docker_containers", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/docker_containers.yml" - }, - { - "url": "/tables/docker_image_history", - "title": "docker_image_history", - "htmlId": "table--dockerimagehistory--77b04426fe", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "docker_image_history", - "comment", - "created", - "created_by", - "id", - "size", - "tags" - ], - "sectionRelativeRepoPath": "docker_image_history", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fdocker_image_history.yml&value=name%3A%20docker_image_history%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/docker_image_labels", - "title": "docker_image_labels", - "htmlId": "table--dockerimagelabels--14e0871386", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "docker_image_labels", - "id", - "key", - "value" - ], - "sectionRelativeRepoPath": "docker_image_labels", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fdocker_image_labels.yml&value=name%3A%20docker_image_labels%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/docker_image_layers", - "title": "docker_image_layers", - "htmlId": "table--dockerimagelayers--91693c4e4c", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "docker_image_layers", - "id", - "layer_id", - "layer_order" - ], - "sectionRelativeRepoPath": "docker_image_layers", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fdocker_image_layers.yml&value=name%3A%20docker_image_layers%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/docker_images", - "title": "docker_images", - "htmlId": "table--dockerimages--6819d40071", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "docker_images", - "created", - "id", - "size_bytes", - "tags" - ], - "sectionRelativeRepoPath": "docker_images", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/docker_images.yml" - }, - { - "url": "/tables/docker_info", - "title": "docker_info", - "htmlId": "table--dockerinfo--2f30b285cd", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "docker_info", - "architecture", - "bridge_nf_ip6tables", - "bridge_nf_iptables", - "cgroup_driver", - "containers", - "containers_paused", - "containers_running", - "containers_stopped", - "cpu_cfs_period", - "cpu_cfs_quota", - "cpu_set", - "cpu_shares", - "cpus", - "http_proxy", - "https_proxy", - "id", - "images", - "ipv4_forwarding", - "kernel_memory", - "kernel_version", - "logging_driver", - "memory", - "memory_limit", - "name", - "no_proxy", - "oom_kill_disable", - "os", - "os_type", - "root_dir", - "server_version", - "storage_driver", - "swap_limit" - ], - "sectionRelativeRepoPath": "docker_info", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fdocker_info.yml&value=name%3A%20docker_info%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/docker_network_labels", - "title": "docker_network_labels", - "htmlId": "table--dockernetworklabels--1f827dc474", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "docker_network_labels", - "id", - "key", - "value" - ], - "sectionRelativeRepoPath": "docker_network_labels", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fdocker_network_labels.yml&value=name%3A%20docker_network_labels%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/docker_networks", - "title": "docker_networks", - "htmlId": "table--dockernetworks--2ae40ea518", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "docker_networks", - "created", - "driver", - "enable_ipv6", - "gateway", - "id", - "name", - "subnet" - ], - "sectionRelativeRepoPath": "docker_networks", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fdocker_networks.yml&value=name%3A%20docker_networks%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/docker_version", - "title": "docker_version", - "htmlId": "table--dockerversion--d5c8b11df6", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "docker_version", - "api_version", - "arch", - "build_time", - "git_commit", - "go_version", - "kernel_version", - "min_api_version", - "os", - "version" - ], - "sectionRelativeRepoPath": "docker_version", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fdocker_version.yml&value=name%3A%20docker_version%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/docker_volume_labels", - "title": "docker_volume_labels", - "htmlId": "table--dockervolumelabels--45adc74ba3", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "docker_volume_labels", - "key", - "name", - "value" - ], - "sectionRelativeRepoPath": "docker_volume_labels", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fdocker_volume_labels.yml&value=name%3A%20docker_volume_labels%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/docker_volumes", - "title": "docker_volumes", - "htmlId": "table--dockervolumes--cee95f6b90", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "docker_volumes", - "driver", - "mount_point", - "name", - "type" - ], - "sectionRelativeRepoPath": "docker_volumes", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/docker_volumes.yml" - }, - { - "url": "/tables/drivers", - "title": "drivers", - "htmlId": "table--drivers--58290d489f", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "drivers", - "class", - "date", - "description", - "device_id", - "device_name", - "driver_key", - "image", - "inf", - "manufacturer", - "provider", - "service", - "service_key", - "signed", - "version" - ], - "sectionRelativeRepoPath": "drivers", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fdrivers.yml&value=name%3A%20drivers%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/dscl", - "title": "dscl", - "htmlId": "table--dscl--54e7060384", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "dscl", - "command", - "key", - "path", - "value" - ], - "sectionRelativeRepoPath": "dscl", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/dscl.yml" - }, - { - "url": "/tables/ec2_instance_metadata", - "title": "ec2_instance_metadata", - "htmlId": "table--ec2instancemetadata--8b1828d8f6", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "ec2_instance_metadata", - "account_id", - "ami_id", - "architecture", - "availability_zone", - "iam_arn", - "instance_id", - "instance_type", - "local_hostname", - "local_ipv4", - "mac", - "region", - "reservation_id", - "security_groups", - "ssh_public_key" - ], - "sectionRelativeRepoPath": "ec2_instance_metadata", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fec2_instance_metadata.yml&value=name%3A%20ec2_instance_metadata%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/ec2_instance_tags", - "title": "ec2_instance_tags", - "htmlId": "table--ec2instancetags--450384158f", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "ec2_instance_tags", - "instance_id", - "key", - "value" - ], - "sectionRelativeRepoPath": "ec2_instance_tags", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fec2_instance_tags.yml&value=name%3A%20ec2_instance_tags%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/es_process_events", - "title": "es_process_events", - "htmlId": "table--esprocessevents--d79d694750", - "evented": true, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "es_process_events", - "cdhash", - "child_pid", - "cmdline", - "cmdline_count", - "codesigning_flags", - "cwd", - "egid", - "eid", - "env", - "env_count", - "euid", - "event_type", - "exit_code", - "gid", - "global_seq_num", - "original_parent", - "parent", - "path", - "pid", - "platform_binary", - "seq_num", - "signing_id", - "team_id", - "time", - "uid", - "username", - "version" - ], - "sectionRelativeRepoPath": "es_process_events", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fes_process_events.yml&value=name%3A%20es_process_events%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/es_process_file_events", - "title": "es_process_file_events", - "htmlId": "table--esprocessfileevents--e28968a0e8", - "evented": true, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "es_process_file_events", - "dest_filename", - "eid", - "event_type", - "filename", - "global_seq_num", - "parent", - "path", - "pid", - "seq_num", - "time", - "version" - ], - "sectionRelativeRepoPath": "es_process_file_events", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fes_process_file_events.yml&value=name%3A%20es_process_file_events%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/etc_hosts", - "title": "etc_hosts", - "htmlId": "table--etchosts--a56205b3f9", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "etc_hosts", - "address", - "hostnames", - "pid_with_namespace" - ], - "sectionRelativeRepoPath": "etc_hosts", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/etc_hosts.yml" - }, - { - "url": "/tables/etc_protocols", - "title": "etc_protocols", - "htmlId": "table--etcprotocols--b5ffb257d1", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "etc_protocols", - "alias", - "comment", - "name", - "number" - ], - "sectionRelativeRepoPath": "etc_protocols", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fetc_protocols.yml&value=name%3A%20etc_protocols%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/etc_services", - "title": "etc_services", - "htmlId": "table--etcservices--454572c18c", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "etc_services", - "aliases", - "comment", - "name", - "port", - "protocol" - ], - "sectionRelativeRepoPath": "etc_services", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/etc_services.yml" - }, - { - "url": "/tables/event_taps", - "title": "event_taps", - "htmlId": "table--eventtaps--b2afde9ecc", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "event_taps", - "enabled", - "event_tap_id", - "event_tapped", - "process_being_tapped", - "tapping_process" - ], - "sectionRelativeRepoPath": "event_taps", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/event_taps.yml" - }, - { - "url": "/tables/extended_attributes", - "title": "extended_attributes", - "htmlId": "table--extendedattributes--9dea030217", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "extended_attributes", - "base64", - "directory", - "key", - "path", - "value" - ], - "sectionRelativeRepoPath": "extended_attributes", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fextended_attributes.yml&value=name%3A%20extended_attributes%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/falcon_kernel_check", - "title": "falcon_kernel_check", - "htmlId": "table--falconkernelcheck--5479232641", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "falcon_kernel_check", - "kernel", - "sensor_version", - "supported" - ], - "sectionRelativeRepoPath": "falcon_kernel_check", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/falcon_kernel_check.yml" - }, - { - "url": "/tables/falconctl_options", - "title": "falconctl_options", - "htmlId": "table--falconctloptions--7106491b65", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "falconctl_options", - "options" - ], - "sectionRelativeRepoPath": "falconctl_options", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/falconctl_options.yml" - }, - { - "url": "/tables/fan_speed_sensors", - "title": "fan_speed_sensors", - "htmlId": "table--fanspeedsensors--32417c8bf6", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "fan_speed_sensors", - "actual", - "fan", - "max", - "min", - "name", - "target" - ], - "sectionRelativeRepoPath": "fan_speed_sensors", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Ffan_speed_sensors.yml&value=name%3A%20fan_speed_sensors%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/file", - "title": "file", - "htmlId": "table--file--5f21761417", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "file", - "atime", - "attributes", - "block_size", - "bsd_flags", - "btime", - "ctime", - "device", - "directory", - "file_id", - "file_version", - "filename", - "gid", - "hard_links", - "inode", - "mode", - "mount_namespace_id", - "mtime", - "original_filename", - "path", - "pid_with_namespace", - "product_version", - "shortcut_comment", - "shortcut_run", - "shortcut_start_in", - "shortcut_target_location", - "shortcut_target_path", - "shortcut_target_type", - "size", - "symlink", - "type", - "uid", - "volume_serial" - ], - "sectionRelativeRepoPath": "file", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/file.yml" - }, - { - "url": "/tables/file_events", - "title": "file_events", - "htmlId": "table--fileevents--7d5b0a2d3e", - "evented": true, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "file_events", - "action", - "atime", - "category", - "ctime", - "eid", - "gid", - "hashed", - "inode", - "md5", - "mode", - "mtime", - "sha1", - "sha256", - "size", - "target_path", - "time", - "transaction_id", - "uid" - ], - "sectionRelativeRepoPath": "file_events", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Ffile_events.yml&value=name%3A%20file_events%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/file_lines", - "title": "file_lines", - "htmlId": "table--filelines--66f7e5497f", - "evented": false, - "platforms": [ - "darwin", - "windows", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "file_lines", - "line", - "path" - ], - "sectionRelativeRepoPath": "file_lines", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/file_lines.yml" - }, - { - "url": "/tables/filevault_prk", - "title": "filevault_prk", - "htmlId": "table--filevaultprk--4327326014", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "filevault_prk", - "base64_encrypted" - ], - "sectionRelativeRepoPath": "filevault_prk", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/filevault_prk.yml" - }, - { - "url": "/tables/filevault_status", - "title": "filevault_status", - "htmlId": "table--filevaultstatus--808666eddb", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "filevault_status", - "status" - ], - "sectionRelativeRepoPath": "filevault_status", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/filevault_status.yml" - }, - { - "url": "/tables/filevault_users", - "title": "filevault_users", - "htmlId": "table--filevaultusers--283a958213", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "filevault_users", - "username", - "uuid" - ], - "sectionRelativeRepoPath": "filevault_users", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/filevault_users.yml" - }, - { - "url": "/tables/find_cmd", - "title": "find_cmd", - "htmlId": "table--findcmd--6d09c7cd5f", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "find_cmd", - "directory", - "path", - "perm", - "type" - ], - "sectionRelativeRepoPath": "find_cmd", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/find_cmd.yml" - }, - { - "url": "/tables/firefox_addons", - "title": "firefox_addons", - "htmlId": "table--firefoxaddons--9eabc39fea", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "firefox_addons", - "active", - "autoupdate", - "creator", - "description", - "disabled", - "identifier", - "location", - "name", - "path", - "source_url", - "type", - "uid", - "version", - "visible" - ], - "sectionRelativeRepoPath": "firefox_addons", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/firefox_addons.yml" - }, - { - "url": "/tables/firefox_preferences", - "title": "firefox_preferences", - "htmlId": "table--firefoxpreferences--2366a56fa1", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "firefox_preferences", - "fullkey", - "key", - "parent", - "path", - "query", - "value" - ], - "sectionRelativeRepoPath": "firefox_preferences", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/firefox_preferences.yml" - }, - { - "url": "/tables/firmware_eficheck_integrity_check", - "title": "firmware_eficheck_integrity_check", - "htmlId": "table--firmwareeficheckintegritycheck--88da320790", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "firmware_eficheck_integrity_check", - "chip", - "output" - ], - "sectionRelativeRepoPath": "firmware_eficheck_integrity_check", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/firmware_eficheck_integrity_check.yml" - }, - { - "url": "/tables/firmwarepasswd", - "title": "firmwarepasswd", - "htmlId": "table--firmwarepasswd--34c47d2dc2", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "firmwarepasswd", - "mode", - "option_roms_allowed", - "password_enabled" - ], - "sectionRelativeRepoPath": "firmwarepasswd", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/firmwarepasswd.yml" - }, - { - "url": "/tables/fleetd_logs", - "title": "fleetd_logs", - "htmlId": "table--fleetdlogs--04f95fb2e5", - "evented": false, - "platforms": [ - "darwin", - "windows", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "fleetd_logs", - "error", - "level", - "message", - "payload", - "time" - ], - "sectionRelativeRepoPath": "fleetd_logs", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/fleetd_logs.yml" - }, - { - "url": "/tables/gatekeeper", - "title": "gatekeeper", - "htmlId": "table--gatekeeper--c48826d081", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "gatekeeper", - "assessments_enabled", - "dev_id_enabled", - "opaque_version", - "version" - ], - "sectionRelativeRepoPath": "gatekeeper", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/gatekeeper.yml" - }, - { - "url": "/tables/gatekeeper_approved_apps", - "title": "gatekeeper_approved_apps", - "htmlId": "table--gatekeeperapprovedapps--ccb2041adc", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "gatekeeper_approved_apps", - "ctime", - "mtime", - "path", - "requirement" - ], - "sectionRelativeRepoPath": "gatekeeper_approved_apps", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fgatekeeper_approved_apps.yml&value=name%3A%20gatekeeper_approved_apps%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/geolocation", - "title": "geolocation", - "htmlId": "table--geolocation--0338bc3ba9", - "evented": false, - "platforms": [ - "chrome" - ], - "keywordsForSyntaxHighlighting": [ - "geolocation", - "city", - "country", - "ip", - "region" - ], - "sectionRelativeRepoPath": "geolocation", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/geolocation.yml" - }, - { - "url": "/tables/google_chrome_profiles", - "title": "google_chrome_profiles", - "htmlId": "table--googlechromeprofiles--bc22157648", - "evented": false, - "platforms": [ - "darwin", - "windows", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "google_chrome_profiles", - "email", - "ephemeral", - "name", - "username" - ], - "sectionRelativeRepoPath": "google_chrome_profiles", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/google_chrome_profiles.yml" - }, - { - "url": "/tables/groups", - "title": "groups", - "htmlId": "table--groups--05fec1d6ce", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "groups", - "comment", - "gid", - "gid_signed", - "group_sid", - "groupname", - "is_hidden", - "pid_with_namespace" - ], - "sectionRelativeRepoPath": "groups", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/groups.yml" - }, - { - "url": "/tables/hardware_events", - "title": "hardware_events", - "htmlId": "table--hardwareevents--f7cce3883a", - "evented": true, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "hardware_events", - "action", - "driver", - "eid", - "model", - "model_id", - "path", - "revision", - "serial", - "time", - "type", - "vendor", - "vendor_id" - ], - "sectionRelativeRepoPath": "hardware_events", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fhardware_events.yml&value=name%3A%20hardware_events%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/hash", - "title": "hash", - "htmlId": "table--hash--c08ce91512", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "hash", - "directory", - "md5", - "mount_namespace_id", - "path", - "pid_with_namespace", - "sha1", - "sha256" - ], - "sectionRelativeRepoPath": "hash", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/hash.yml" - }, - { - "url": "/tables/homebrew_packages", - "title": "homebrew_packages", - "htmlId": "table--homebrewpackages--9c26173ba7", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "homebrew_packages", - "name", - "path", - "prefix", - "type", - "version" - ], - "sectionRelativeRepoPath": "homebrew_packages", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/homebrew_packages.yml" - }, - { - "url": "/tables/hvci_status", - "title": "hvci_status", - "htmlId": "table--hvcistatus--46a3ee08e5", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "hvci_status", - "code_integrity_policy_enforcement_status", - "instance_identifier", - "umci_policy_status", - "vbs_status", - "version" - ], - "sectionRelativeRepoPath": "hvci_status", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fhvci_status.yml&value=name%3A%20hvci_status%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/ibridge_info", - "title": "ibridge_info", - "htmlId": "table--ibridgeinfo--38f5f5d7eb", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "ibridge_info", - "boot_uuid", - "coprocessor_version", - "firmware_version", - "unique_chip_id" - ], - "sectionRelativeRepoPath": "ibridge_info", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fibridge_info.yml&value=name%3A%20ibridge_info%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/icloud_private_relay", - "title": "icloud_private_relay", - "htmlId": "table--icloudprivaterelay--7cbb9c575c", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "icloud_private_relay", - "status" - ], - "sectionRelativeRepoPath": "icloud_private_relay", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/icloud_private_relay.yml" - }, - { - "url": "/tables/ie_extensions", - "title": "ie_extensions", - "htmlId": "table--ieextensions--412b814817", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "ie_extensions", - "name", - "path", - "registry_path", - "version" - ], - "sectionRelativeRepoPath": "ie_extensions", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/ie_extensions.yml" - }, - { - "url": "/tables/intel_me_info", - "title": "intel_me_info", - "htmlId": "table--intelmeinfo--fd5eb9626f", - "evented": false, - "platforms": [ - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "intel_me_info", - "version" - ], - "sectionRelativeRepoPath": "intel_me_info", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fintel_me_info.yml&value=name%3A%20intel_me_info%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/interface_addresses", - "title": "interface_addresses", - "htmlId": "table--interfaceaddresses--4163068693", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "interface_addresses", - "address", - "broadcast", - "friendly_name", - "interface", - "mask", - "point_to_point", - "type" - ], - "sectionRelativeRepoPath": "interface_addresses", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/interface_addresses.yml" - }, - { - "url": "/tables/interface_details", - "title": "interface_details", - "htmlId": "table--interfacedetails--c8234f77ad", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "interface_details", - "collisions", - "connection_id", - "connection_status", - "description", - "dhcp_enabled", - "dhcp_lease_expires", - "dhcp_lease_obtained", - "dhcp_server", - "dns_domain", - "dns_domain_suffix_search_order", - "dns_host_name", - "dns_server_search_order", - "enabled", - "flags", - "friendly_name", - "ibytes", - "idrops", - "ierrors", - "interface", - "ipackets", - "last_change", - "link_speed", - "mac", - "manufacturer", - "metric", - "mtu", - "obytes", - "odrops", - "oerrors", - "opackets", - "pci_slot", - "physical_adapter", - "service", - "speed", - "type" - ], - "sectionRelativeRepoPath": "interface_details", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/interface_details.yml" - }, - { - "url": "/tables/interface_ipv6", - "title": "interface_ipv6", - "htmlId": "table--interfaceipv6--48a78776ae", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "interface_ipv6", - "forwarding_enabled", - "hop_limit", - "interface", - "redirect_accept", - "rtadv_accept" - ], - "sectionRelativeRepoPath": "interface_ipv6", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/interface_ipv6.yml" - }, - { - "url": "/tables/iokit_devicetree", - "title": "iokit_devicetree", - "htmlId": "table--iokitdevicetree--475d23de81", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "iokit_devicetree", - "busy_state", - "class", - "depth", - "device_path", - "id", - "name", - "parent", - "retain_count", - "service" - ], - "sectionRelativeRepoPath": "iokit_devicetree", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/iokit_devicetree.yml" - }, - { - "url": "/tables/iokit_registry", - "title": "iokit_registry", - "htmlId": "table--iokitregistry--213523c85d", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "iokit_registry", - "busy_state", - "class", - "depth", - "id", - "name", - "parent", - "retain_count" - ], - "sectionRelativeRepoPath": "iokit_registry", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/iokit_registry.yml" - }, - { - "url": "/tables/ioreg", - "title": "ioreg", - "htmlId": "table--ioreg--64934c5b2c", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "ioreg", - "c", - "d", - "fullkey", - "k", - "key", - "n", - "p", - "parent", - "query", - "r", - "value" - ], - "sectionRelativeRepoPath": "ioreg", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/ioreg.yml" - }, - { - "url": "/tables/iptables", - "title": "iptables", - "htmlId": "table--iptables--73fe23ccfd", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "iptables", - "bytes", - "chain", - "dst_ip", - "dst_mask", - "dst_port", - "filter_name", - "iniface", - "iniface_mask", - "match", - "outiface", - "outiface_mask", - "packets", - "policy", - "protocol", - "src_ip", - "src_mask", - "src_port", - "target" - ], - "sectionRelativeRepoPath": "iptables", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/iptables.yml" - }, - { - "url": "/tables/kernel_extensions", - "title": "kernel_extensions", - "htmlId": "table--kernelextensions--015ed33cfc", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "kernel_extensions", - "idx", - "linked_against", - "name", - "path", - "refs", - "size", - "version" - ], - "sectionRelativeRepoPath": "kernel_extensions", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/kernel_extensions.yml" - }, - { - "url": "/tables/kernel_info", - "title": "kernel_info", - "htmlId": "table--kernelinfo--e02ab4d886", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "kernel_info", - "arguments", - "device", - "path", - "version" - ], - "sectionRelativeRepoPath": "kernel_info", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/kernel_info.yml" - }, - { - "url": "/tables/kernel_keys", - "title": "kernel_keys", - "htmlId": "table--kernelkeys--c3a84244c8", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "kernel_keys", - "description", - "flags", - "gid", - "permissions", - "serial_number", - "timeout", - "type", - "uid", - "usage" - ], - "sectionRelativeRepoPath": "kernel_keys", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fkernel_keys.yml&value=name%3A%20kernel_keys%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/kernel_modules", - "title": "kernel_modules", - "htmlId": "table--kernelmodules--c9051ad100", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "kernel_modules", - "address", - "name", - "size", - "status", - "used_by" - ], - "sectionRelativeRepoPath": "kernel_modules", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fkernel_modules.yml&value=name%3A%20kernel_modules%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/kernel_panics", - "title": "kernel_panics", - "htmlId": "table--kernelpanics--c6cb2cce6e", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "kernel_panics", - "dependencies", - "frame_backtrace", - "kernel_version", - "last_loaded", - "last_unloaded", - "module_backtrace", - "name", - "os_version", - "path", - "registers", - "system_model", - "time", - "uptime" - ], - "sectionRelativeRepoPath": "kernel_panics", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/kernel_panics.yml" - }, - { - "url": "/tables/keychain_acls", - "title": "keychain_acls", - "htmlId": "table--keychainacls--e46564a1f0", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "keychain_acls", - "authorizations", - "description", - "keychain_path", - "label", - "path" - ], - "sectionRelativeRepoPath": "keychain_acls", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/keychain_acls.yml" - }, - { - "url": "/tables/keychain_items", - "title": "keychain_items", - "htmlId": "table--keychainitems--ceb19aa966", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "keychain_items", - "account", - "comment", - "created", - "description", - "label", - "modified", - "path", - "pk_hash", - "type" - ], - "sectionRelativeRepoPath": "keychain_items", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/keychain_items.yml" - }, - { - "url": "/tables/known_hosts", - "title": "known_hosts", - "htmlId": "table--knownhosts--2c508bc3c8", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "known_hosts", - "key", - "key_file", - "uid" - ], - "sectionRelativeRepoPath": "known_hosts", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/known_hosts.yml" - }, - { - "url": "/tables/kva_speculative_info", - "title": "kva_speculative_info", - "htmlId": "table--kvaspeculativeinfo--aa9ff39cc2", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "kva_speculative_info", - "bp_microcode_disabled", - "bp_mitigations", - "bp_system_pol_disabled", - "cpu_pred_cmd_supported", - "cpu_spec_ctrl_supported", - "ibrs_support_enabled", - "kva_shadow_enabled", - "kva_shadow_inv_pcid", - "kva_shadow_pcid", - "kva_shadow_user_global", - "stibp_support_enabled" - ], - "sectionRelativeRepoPath": "kva_speculative_info", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fkva_speculative_info.yml&value=name%3A%20kva_speculative_info%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/last", - "title": "last", - "htmlId": "table--last--81b773b51e", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "last", - "host", - "pid", - "time", - "tty", - "type", - "type_name", - "username" - ], - "sectionRelativeRepoPath": "last", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/last.yml" - }, - { - "url": "/tables/launchd", - "title": "launchd", - "htmlId": "table--launchd--e309e31831", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "launchd", - "disabled", - "groupname", - "inetd_compatibility", - "keep_alive", - "label", - "name", - "on_demand", - "path", - "process_type", - "program", - "program_arguments", - "queue_directories", - "root_directory", - "run_at_load", - "start_interval", - "start_on_mount", - "stderr_path", - "stdout_path", - "username", - "watch_paths", - "working_directory" - ], - "sectionRelativeRepoPath": "launchd", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/launchd.yml" - }, - { - "url": "/tables/launchd_overrides", - "title": "launchd_overrides", - "htmlId": "table--launchdoverrides--89410cb367", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "launchd_overrides", - "key", - "label", - "path", - "uid", - "value" - ], - "sectionRelativeRepoPath": "launchd_overrides", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Flaunchd_overrides.yml&value=name%3A%20launchd_overrides%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/listening_ports", - "title": "listening_ports", - "htmlId": "table--listeningports--de6bf76ec3", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "listening_ports", - "address", - "family", - "fd", - "net_namespace", - "path", - "pid", - "port", - "protocol", - "socket" - ], - "sectionRelativeRepoPath": "listening_ports", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/listening_ports.yml" - }, - { - "url": "/tables/load_average", - "title": "load_average", - "htmlId": "table--loadaverage--f5f080e140", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "load_average", - "average", - "period" - ], - "sectionRelativeRepoPath": "load_average", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/load_average.yml" - }, - { - "url": "/tables/location_services", - "title": "location_services", - "htmlId": "table--locationservices--f22473f4be", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "location_services", - "enabled" - ], - "sectionRelativeRepoPath": "location_services", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/location_services.yml" - }, - { - "url": "/tables/logged_in_users", - "title": "logged_in_users", - "htmlId": "table--loggedinusers--bd140b0e93", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "logged_in_users", - "host", - "pid", - "registry_hive", - "sid", - "time", - "tty", - "type", - "user" - ], - "sectionRelativeRepoPath": "logged_in_users", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/logged_in_users.yml" - }, - { - "url": "/tables/logical_drives", - "title": "logical_drives", - "htmlId": "table--logicaldrives--e69b777f6c", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "logical_drives", - "boot_partition", - "description", - "device_id", - "file_system", - "free_space", - "size", - "type" - ], - "sectionRelativeRepoPath": "logical_drives", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Flogical_drives.yml&value=name%3A%20logical_drives%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/logon_sessions", - "title": "logon_sessions", - "htmlId": "table--logonsessions--54d10b59e8", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "logon_sessions", - "authentication_package", - "dns_domain_name", - "home_directory", - "home_directory_drive", - "logon_domain", - "logon_id", - "logon_script", - "logon_server", - "logon_sid", - "logon_time", - "logon_type", - "profile_path", - "session_id", - "upn", - "user" - ], - "sectionRelativeRepoPath": "logon_sessions", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Flogon_sessions.yml&value=name%3A%20logon_sessions%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/lxd_certificates", - "title": "lxd_certificates", - "htmlId": "table--lxdcertificates--06e045fa14", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "lxd_certificates", - "certificate", - "fingerprint", - "name", - "type" - ], - "sectionRelativeRepoPath": "lxd_certificates", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Flxd_certificates.yml&value=name%3A%20lxd_certificates%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/lxd_cluster", - "title": "lxd_cluster", - "htmlId": "table--lxdcluster--a8491b6203", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "lxd_cluster", - "enabled", - "member_config_description", - "member_config_entity", - "member_config_key", - "member_config_name", - "member_config_value", - "server_name" - ], - "sectionRelativeRepoPath": "lxd_cluster", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Flxd_cluster.yml&value=name%3A%20lxd_cluster%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/lxd_cluster_members", - "title": "lxd_cluster_members", - "htmlId": "table--lxdclustermembers--7d6e6837d2", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "lxd_cluster_members", - "database", - "message", - "server_name", - "status", - "url" - ], - "sectionRelativeRepoPath": "lxd_cluster_members", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Flxd_cluster_members.yml&value=name%3A%20lxd_cluster_members%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/lxd_images", - "title": "lxd_images", - "htmlId": "table--lxdimages--55db6fdd97", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "lxd_images", - "aliases", - "architecture", - "auto_update", - "cached", - "created_at", - "description", - "expires_at", - "filename", - "id", - "last_used_at", - "os", - "public", - "release", - "size", - "update_source_alias", - "update_source_certificate", - "update_source_protocol", - "update_source_server", - "uploaded_at" - ], - "sectionRelativeRepoPath": "lxd_images", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Flxd_images.yml&value=name%3A%20lxd_images%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/lxd_instance_config", - "title": "lxd_instance_config", - "htmlId": "table--lxdinstanceconfig--54469816ca", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "lxd_instance_config", - "key", - "name", - "value" - ], - "sectionRelativeRepoPath": "lxd_instance_config", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Flxd_instance_config.yml&value=name%3A%20lxd_instance_config%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/lxd_instance_devices", - "title": "lxd_instance_devices", - "htmlId": "table--lxdinstancedevices--f28caba867", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "lxd_instance_devices", - "device", - "device_type", - "key", - "name", - "value" - ], - "sectionRelativeRepoPath": "lxd_instance_devices", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Flxd_instance_devices.yml&value=name%3A%20lxd_instance_devices%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/lxd_instances", - "title": "lxd_instances", - "htmlId": "table--lxdinstances--77d953ad3e", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "lxd_instances", - "architecture", - "base_image", - "created_at", - "description", - "ephemeral", - "name", - "os", - "pid", - "processes", - "stateful", - "status" - ], - "sectionRelativeRepoPath": "lxd_instances", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Flxd_instances.yml&value=name%3A%20lxd_instances%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/lxd_networks", - "title": "lxd_networks", - "htmlId": "table--lxdnetworks--7dd5f10782", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "lxd_networks", - "bytes_received", - "bytes_sent", - "hwaddr", - "ipv4_address", - "ipv6_address", - "managed", - "mtu", - "name", - "packets_received", - "packets_sent", - "state", - "type", - "used_by" - ], - "sectionRelativeRepoPath": "lxd_networks", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Flxd_networks.yml&value=name%3A%20lxd_networks%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/lxd_storage_pools", - "title": "lxd_storage_pools", - "htmlId": "table--lxdstoragepools--950b575e61", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "lxd_storage_pools", - "driver", - "inodes_total", - "inodes_used", - "name", - "size", - "source", - "space_total", - "space_used" - ], - "sectionRelativeRepoPath": "lxd_storage_pools", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Flxd_storage_pools.yml&value=name%3A%20lxd_storage_pools%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/macadmins_unified_log", - "title": "macadmins_unified_log", - "htmlId": "table--macadminsunifiedlog--e036df9e57", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "macadmins_unified_log", - "activity_identifier", - "boot_uuid", - "category", - "event_message", - "event_type", - "format_string", - "log_level", - "parent_activity_identifier", - "process_id", - "process_image_path", - "sender_image_path", - "sender_image_uuid", - "sender_program_counter", - "subsystem", - "thread_id", - "timestamp", - "trace_id" - ], - "sectionRelativeRepoPath": "macadmins_unified_log", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/macadmins_unified_log.yml" - }, - { - "url": "/tables/macos_profiles", - "title": "macos_profiles", - "htmlId": "table--macosprofiles--cae047dfff", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "macos_profiles", - "description", - "display_name", - "identifier", - "install_date", - "organization", - "type", - "uuid", - "verification_state" - ], - "sectionRelativeRepoPath": "macos_profiles", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/macos_profiles.yml" - }, - { - "url": "/tables/macos_rsr", - "title": "macos_rsr", - "htmlId": "table--macosrsr--9c9ef590fd", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "macos_rsr", - "full_macos_version", - "macos_version", - "rsr_supported", - "rsr_version" - ], - "sectionRelativeRepoPath": "macos_rsr", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/macos_rsr.yml" - }, - { - "url": "/tables/magic", - "title": "magic", - "htmlId": "table--magic--2b54571c80", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "magic", - "data", - "magic_db_files", - "mime_encoding", - "mime_type", - "path" - ], - "sectionRelativeRepoPath": "magic", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fmagic.yml&value=name%3A%20magic%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/managed_policies", - "title": "managed_policies", - "htmlId": "table--managedpolicies--494a329dfb", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "managed_policies", - "domain", - "manual", - "name", - "username", - "uuid", - "value" - ], - "sectionRelativeRepoPath": "managed_policies", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/managed_policies.yml" - }, - { - "url": "/tables/md_devices", - "title": "md_devices", - "htmlId": "table--mddevices--cc18ebf22a", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "md_devices", - "active_disks", - "bitmap_chunk_size", - "bitmap_external_file", - "bitmap_on_mem", - "check_array_finish", - "check_array_progress", - "check_array_speed", - "chunk_size", - "device_name", - "failed_disks", - "nr_raid_disks", - "other", - "raid_disks", - "raid_level", - "recovery_finish", - "recovery_progress", - "recovery_speed", - "reshape_finish", - "reshape_progress", - "reshape_speed", - "resync_finish", - "resync_progress", - "resync_speed", - "size", - "spare_disks", - "status", - "superblock_state", - "superblock_update_time", - "superblock_version", - "unused_devices", - "working_disks" - ], - "sectionRelativeRepoPath": "md_devices", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fmd_devices.yml&value=name%3A%20md_devices%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/md_drives", - "title": "md_drives", - "htmlId": "table--mddrives--f529358f7d", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "md_drives", - "drive_name", - "md_device_name", - "slot", - "state" - ], - "sectionRelativeRepoPath": "md_drives", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fmd_drives.yml&value=name%3A%20md_drives%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/md_personalities", - "title": "md_personalities", - "htmlId": "table--mdpersonalities--6234b42367", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "md_personalities", - "name" - ], - "sectionRelativeRepoPath": "md_personalities", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fmd_personalities.yml&value=name%3A%20md_personalities%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/mdfind", - "title": "mdfind", - "htmlId": "table--mdfind--2061531fab", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "mdfind", - "path", - "query" - ], - "sectionRelativeRepoPath": "mdfind", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fmdfind.yml&value=name%3A%20mdfind%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/mdls", - "title": "mdls", - "htmlId": "table--mdls--8826cff54e", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "mdls", - "key", - "path", - "value", - "valuetype" - ], - "sectionRelativeRepoPath": "mdls", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/mdls.yml" - }, - { - "url": "/tables/mdm", - "title": "mdm", - "htmlId": "table--mdm--4e74952c0b", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "mdm", - "access_rights", - "checkin_url", - "dep_capable", - "enrolled", - "has_scep_payload", - "identity_certificate_uuid", - "install_date", - "installed_from_dep", - "payload_identifier", - "server_url", - "sign_message", - "topic", - "user_approved" - ], - "sectionRelativeRepoPath": "mdm", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/mdm.yml" - }, - { - "url": "/tables/mdm_bridge", - "title": "mdm_bridge", - "htmlId": "table--mdmbridge--6dff726888", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "mdm_bridge", - "enrolled_user", - "enrollment_status", - "mdm_command_input", - "mdm_command_output", - "raw_mdm_command_output" - ], - "sectionRelativeRepoPath": "mdm_bridge", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/mdm_bridge.yml" - }, - { - "url": "/tables/memory_array_mapped_addresses", - "title": "memory_array_mapped_addresses", - "htmlId": "table--memoryarraymappedaddresses--6f656395f7", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "memory_array_mapped_addresses", - "ending_address", - "handle", - "memory_array_handle", - "partition_width", - "starting_address" - ], - "sectionRelativeRepoPath": "memory_array_mapped_addresses", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fmemory_array_mapped_addresses.yml&value=name%3A%20memory_array_mapped_addresses%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/memory_arrays", - "title": "memory_arrays", - "htmlId": "table--memoryarrays--abd1487b4b", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "memory_arrays", - "handle", - "location", - "max_capacity", - "memory_error_correction", - "memory_error_info_handle", - "number_memory_devices", - "use" - ], - "sectionRelativeRepoPath": "memory_arrays", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fmemory_arrays.yml&value=name%3A%20memory_arrays%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/memory_device_mapped_addresses", - "title": "memory_device_mapped_addresses", - "htmlId": "table--memorydevicemappedaddresses--21aa4bee51", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "memory_device_mapped_addresses", - "ending_address", - "handle", - "interleave_data_depth", - "interleave_position", - "memory_array_mapped_address_handle", - "memory_device_handle", - "partition_row_position", - "starting_address" - ], - "sectionRelativeRepoPath": "memory_device_mapped_addresses", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fmemory_device_mapped_addresses.yml&value=name%3A%20memory_device_mapped_addresses%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/memory_devices", - "title": "memory_devices", - "htmlId": "table--memorydevices--8e8226757f", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "memory_devices", - "array_handle", - "asset_tag", - "bank_locator", - "configured_clock_speed", - "configured_voltage", - "data_width", - "device_locator", - "form_factor", - "handle", - "manufacturer", - "max_speed", - "max_voltage", - "memory_type", - "memory_type_details", - "min_voltage", - "part_number", - "serial_number", - "set", - "size", - "total_width" - ], - "sectionRelativeRepoPath": "memory_devices", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fmemory_devices.yml&value=name%3A%20memory_devices%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/memory_error_info", - "title": "memory_error_info", - "htmlId": "table--memoryerrorinfo--0e04980533", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "memory_error_info", - "device_error_address", - "error_granularity", - "error_operation", - "error_resolution", - "error_type", - "handle", - "memory_array_error_address", - "vendor_syndrome" - ], - "sectionRelativeRepoPath": "memory_error_info", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fmemory_error_info.yml&value=name%3A%20memory_error_info%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/memory_info", - "title": "memory_info", - "htmlId": "table--memoryinfo--84feac1e17", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "memory_info", - "active", - "buffers", - "cached", - "inactive", - "memory_available", - "memory_free", - "memory_total", - "swap_cached", - "swap_free", - "swap_total" - ], - "sectionRelativeRepoPath": "memory_info", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fmemory_info.yml&value=name%3A%20memory_info%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/memory_map", - "title": "memory_map", - "htmlId": "table--memorymap--dbdfd30e2f", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "memory_map", - "end", - "name", - "start" - ], - "sectionRelativeRepoPath": "memory_map", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fmemory_map.yml&value=name%3A%20memory_map%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/mounts", - "title": "mounts", - "htmlId": "table--mounts--9bd193b227", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "mounts", - "blocks", - "blocks_available", - "blocks_free", - "blocks_size", - "device", - "device_alias", - "flags", - "inodes", - "inodes_free", - "path", - "type" - ], - "sectionRelativeRepoPath": "mounts", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/mounts.yml" - }, - { - "url": "/tables/msr", - "title": "msr", - "htmlId": "table--msr--ff9484332b", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "msr", - "feature_control", - "perf_ctl", - "perf_status", - "platform_info", - "processor_number", - "rapl_energy_status", - "rapl_power_limit", - "rapl_power_units", - "turbo_disabled", - "turbo_ratio_limit" - ], - "sectionRelativeRepoPath": "msr", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fmsr.yml&value=name%3A%20msr%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/munki_info", - "title": "munki_info", - "htmlId": "table--munkiinfo--2e4b112369", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "munki_info", - "console_user", - "end_time", - "errors", - "manifest_name", - "problem_installs", - "start_time", - "success", - "version", - "warnings" - ], - "sectionRelativeRepoPath": "munki_info", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/munki_info.yml" - }, - { - "url": "/tables/munki_installs", - "title": "munki_installs", - "htmlId": "table--munkiinstalls--b403c42531", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "munki_installs", - "end_time", - "installed", - "installed_version", - "name" - ], - "sectionRelativeRepoPath": "munki_installs", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/munki_installs.yml" - }, - { - "url": "/tables/network_interfaces", - "title": "network_interfaces", - "htmlId": "table--networkinterfaces--ea6f795816", - "evented": false, - "platforms": [ - "chrome" - ], - "keywordsForSyntaxHighlighting": [ - "network_interfaces", - "ipv4", - "ipv6", - "mac" - ], - "sectionRelativeRepoPath": "network_interfaces", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/network_interfaces.yml" - }, - { - "url": "/tables/nfs_shares", - "title": "nfs_shares", - "htmlId": "table--nfsshares--b4f614d51e", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "nfs_shares", - "options", - "readonly", - "share" - ], - "sectionRelativeRepoPath": "nfs_shares", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/nfs_shares.yml" - }, - { - "url": "/tables/npm_packages", - "title": "npm_packages", - "htmlId": "table--npmpackages--b2a26bbba0", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "npm_packages", - "author", - "description", - "directory", - "homepage", - "license", - "mount_namespace_id", - "name", - "path", - "pid_with_namespace", - "version" - ], - "sectionRelativeRepoPath": "npm_packages", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/npm_packages.yml" - }, - { - "url": "/tables/ntdomains", - "title": "ntdomains", - "htmlId": "table--ntdomains--57ef982364", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "ntdomains", - "client_site_name", - "dc_site_name", - "dns_forest_name", - "domain_controller_address", - "domain_controller_name", - "domain_name", - "name", - "status" - ], - "sectionRelativeRepoPath": "ntdomains", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/ntdomains.yml" - }, - { - "url": "/tables/ntfs_acl_permissions", - "title": "ntfs_acl_permissions", - "htmlId": "table--ntfsaclpermissions--2d66c6c45e", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "ntfs_acl_permissions", - "access", - "inherited_from", - "path", - "principal", - "type" - ], - "sectionRelativeRepoPath": "ntfs_acl_permissions", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fntfs_acl_permissions.yml&value=name%3A%20ntfs_acl_permissions%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/ntfs_journal_events", - "title": "ntfs_journal_events", - "htmlId": "table--ntfsjournalevents--2369d84275", - "evented": true, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "ntfs_journal_events", - "action", - "category", - "drive_letter", - "eid", - "file_attributes", - "node_ref_number", - "old_path", - "parent_ref_number", - "partial", - "path", - "record_timestamp", - "record_usn", - "time" - ], - "sectionRelativeRepoPath": "ntfs_journal_events", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fntfs_journal_events.yml&value=name%3A%20ntfs_journal_events%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/nvram", - "title": "nvram", - "htmlId": "table--nvram--450a99f968", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "nvram", - "name", - "type", - "value" - ], - "sectionRelativeRepoPath": "nvram", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/nvram.yml" - }, - { - "url": "/tables/nvram_info", - "title": "nvram_info", - "htmlId": "table--nvraminfo--a99cb280af", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "nvram_info", - "amfi_enabled" - ], - "sectionRelativeRepoPath": "nvram_info", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/nvram_info.yml" - }, - { - "url": "/tables/oem_strings", - "title": "oem_strings", - "htmlId": "table--oemstrings--89f170ddda", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "oem_strings", - "handle", - "number", - "value" - ], - "sectionRelativeRepoPath": "oem_strings", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Foem_strings.yml&value=name%3A%20oem_strings%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/office_mru", - "title": "office_mru", - "htmlId": "table--officemru--11e1929c70", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "office_mru", - "application", - "last_opened_time", - "path", - "sid", - "version" - ], - "sectionRelativeRepoPath": "office_mru", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Foffice_mru.yml&value=name%3A%20office_mru%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/orbit_info", - "title": "orbit_info", - "htmlId": "table--orbitinfo--98fca7c408", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "orbit_info", - "desktop_channel", - "desktop_version", - "device_auth_token", - "enrolled", - "last_recorded_error", - "orbit_channel", - "osqueryd_channel", - "scripts_enabled", - "uptime", - "version" - ], - "sectionRelativeRepoPath": "orbit_info", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/orbit_info.yml" - }, - { - "url": "/tables/os_version", - "title": "os_version", - "htmlId": "table--osversion--95451301c8", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows", - "chrome" - ], - "keywordsForSyntaxHighlighting": [ - "os_version", - "arch", - "build", - "codename", - "extra", - "install_date", - "major", - "minor", - "mount_namespace_id", - "name", - "patch", - "pid_with_namespace", - "platform", - "platform_like", - "revision", - "version" - ], - "sectionRelativeRepoPath": "os_version", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/os_version.yml" - }, - { - "url": "/tables/osquery_events", - "title": "osquery_events", - "htmlId": "table--osqueryevents--3bff81a1b8", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "osquery_events", - "active", - "events", - "name", - "publisher", - "refreshes", - "subscriptions", - "type" - ], - "sectionRelativeRepoPath": "osquery_events", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/osquery_events.yml" - }, - { - "url": "/tables/osquery_extensions", - "title": "osquery_extensions", - "htmlId": "table--osqueryextensions--56dea82216", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "osquery_extensions", - "name", - "path", - "sdk_version", - "type", - "uuid", - "version" - ], - "sectionRelativeRepoPath": "osquery_extensions", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/osquery_extensions.yml" - }, - { - "url": "/tables/osquery_flags", - "title": "osquery_flags", - "htmlId": "table--osqueryflags--27972ebab6", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "osquery_flags", - "default_value", - "description", - "name", - "shell_only", - "type", - "value" - ], - "sectionRelativeRepoPath": "osquery_flags", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/osquery_flags.yml" - }, - { - "url": "/tables/osquery_info", - "title": "osquery_info", - "htmlId": "table--osqueryinfo--99ebd3222b", - "evented": false, - "platforms": [ - "darwin", - "windows", - "linux", - "chrome" - ], - "keywordsForSyntaxHighlighting": [ - "osquery_info", - "build_distro", - "build_platform", - "config_hash", - "config_valid", - "extensions", - "instance_id", - "pid", - "platform_mask", - "start_time", - "uuid", - "version", - "watcher" - ], - "sectionRelativeRepoPath": "osquery_info", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/osquery_info.yml" - }, - { - "url": "/tables/osquery_packs", - "title": "osquery_packs", - "htmlId": "table--osquerypacks--c2f0293ed5", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "osquery_packs", - "active", - "discovery_cache_hits", - "discovery_executions", - "name", - "platform", - "shard", - "version" - ], - "sectionRelativeRepoPath": "osquery_packs", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/osquery_packs.yml" - }, - { - "url": "/tables/osquery_registry", - "title": "osquery_registry", - "htmlId": "table--osqueryregistry--723b93f998", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "osquery_registry", - "active", - "internal", - "name", - "owner_uuid", - "registry" - ], - "sectionRelativeRepoPath": "osquery_registry", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/osquery_registry.yml" - }, - { - "url": "/tables/osquery_schedule", - "title": "osquery_schedule", - "htmlId": "table--osqueryschedule--81eadaf536", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "osquery_schedule", - "average_memory", - "denylisted", - "executions", - "interval", - "last_executed", - "last_memory", - "last_system_time", - "last_user_time", - "last_wall_time_ms", - "name", - "output_size", - "query", - "system_time", - "user_time", - "wall_time", - "wall_time_ms" - ], - "sectionRelativeRepoPath": "osquery_schedule", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/osquery_schedule.yml" - }, - { - "url": "/tables/package_bom", - "title": "package_bom", - "htmlId": "table--packagebom--8182ed768f", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "package_bom", - "filepath", - "gid", - "mode", - "modified_time", - "path", - "size", - "uid" - ], - "sectionRelativeRepoPath": "package_bom", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/package_bom.yml" - }, - { - "url": "/tables/package_install_history", - "title": "package_install_history", - "htmlId": "table--packageinstallhistory--988f999553", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "package_install_history", - "content_type", - "name", - "package_id", - "source", - "time", - "version" - ], - "sectionRelativeRepoPath": "package_install_history", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/package_install_history.yml" - }, - { - "url": "/tables/package_receipts", - "title": "package_receipts", - "htmlId": "table--packagereceipts--4d830b5b2d", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "package_receipts", - "install_time", - "installer_name", - "location", - "package_filename", - "package_id", - "path", - "version" - ], - "sectionRelativeRepoPath": "package_receipts", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/package_receipts.yml" - }, - { - "url": "/tables/parse_ini", - "title": "parse_ini", - "htmlId": "table--parseini--4de2377a57", - "evented": false, - "platforms": [ - "darwin", - "windows", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "parse_ini", - "fullkey", - "key", - "parent", - "path", - "value" - ], - "sectionRelativeRepoPath": "parse_ini", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/parse_ini.yml" - }, - { - "url": "/tables/parse_json", - "title": "parse_json", - "htmlId": "table--parsejson--c3c9947479", - "evented": false, - "platforms": [ - "darwin", - "windows", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "parse_json", - "fullkey", - "key", - "parent", - "path", - "value" - ], - "sectionRelativeRepoPath": "parse_json", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/parse_json.yml" - }, - { - "url": "/tables/parse_jsonl", - "title": "parse_jsonl", - "htmlId": "table--parsejsonl--b71d789467", - "evented": false, - "platforms": [ - "darwin", - "windows", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "parse_jsonl", - "fullkey", - "key", - "parent", - "path", - "value" - ], - "sectionRelativeRepoPath": "parse_jsonl", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/parse_jsonl.yml" - }, - { - "url": "/tables/parse_xml", - "title": "parse_xml", - "htmlId": "table--parsexml--15ed589727", - "evented": false, - "platforms": [ - "darwin", - "windows", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "parse_xml", - "fullkey", - "key", - "parent", - "path", - "value" - ], - "sectionRelativeRepoPath": "parse_xml", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/parse_xml.yml" - }, - { - "url": "/tables/password_policy", - "title": "password_policy", - "htmlId": "table--passwordpolicy--9a2e1051b8", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "password_policy", - "policy_content", - "policy_description", - "policy_identifier", - "uid" - ], - "sectionRelativeRepoPath": "password_policy", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/password_policy.yml" - }, - { - "url": "/tables/patches", - "title": "patches", - "htmlId": "table--patches--b3f61813f5", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "patches", - "caption", - "csname", - "description", - "fix_comments", - "hotfix_id", - "install_date", - "installed_by", - "installed_on" - ], - "sectionRelativeRepoPath": "patches", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/patches.yml" - }, - { - "url": "/tables/pci_devices", - "title": "pci_devices", - "htmlId": "table--pcidevices--b00adf6d59", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "pci_devices", - "driver", - "model", - "model_id", - "pci_class", - "pci_class_id", - "pci_slot", - "pci_subclass", - "pci_subclass_id", - "subsystem_model", - "subsystem_model_id", - "subsystem_vendor", - "subsystem_vendor_id", - "vendor", - "vendor_id" - ], - "sectionRelativeRepoPath": "pci_devices", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/pci_devices.yml" - }, - { - "url": "/tables/physical_disk_performance", - "title": "physical_disk_performance", - "htmlId": "table--physicaldiskperformance--21ffb96328", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "physical_disk_performance", - "avg_disk_bytes_per_read", - "avg_disk_bytes_per_write", - "avg_disk_read_queue_length", - "avg_disk_sec_per_read", - "avg_disk_sec_per_write", - "avg_disk_write_queue_length", - "current_disk_queue_length", - "name", - "percent_disk_read_time", - "percent_disk_time", - "percent_disk_write_time", - "percent_idle_time" - ], - "sectionRelativeRepoPath": "physical_disk_performance", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fphysical_disk_performance.yml&value=name%3A%20physical_disk_performance%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/pipes", - "title": "pipes", - "htmlId": "table--pipes--6c348a0bda", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "pipes", - "flags", - "instances", - "max_instances", - "name", - "pid" - ], - "sectionRelativeRepoPath": "pipes", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/pipes.yml" - }, - { - "url": "/tables/platform_info", - "title": "platform_info", - "htmlId": "table--platforminfo--606b0b07f8", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "platform_info", - "address", - "date", - "extra", - "firmware_type", - "revision", - "size", - "vendor", - "version", - "volume_size" - ], - "sectionRelativeRepoPath": "platform_info", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/platform_info.yml" - }, - { - "url": "/tables/plist", - "title": "plist", - "htmlId": "table--plist--10bd270ccc", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "plist", - "key", - "path", - "subkey", - "value" - ], - "sectionRelativeRepoPath": "plist", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/plist.yml" - }, - { - "url": "/tables/pmset", - "title": "pmset", - "htmlId": "table--pmset--5f7c05dca3", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "pmset", - "getting", - "json_result" - ], - "sectionRelativeRepoPath": "pmset", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/pmset.yml" - }, - { - "url": "/tables/portage_keywords", - "title": "portage_keywords", - "htmlId": "table--portagekeywords--16048373f7", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "portage_keywords", - "keyword", - "mask", - "package", - "unmask", - "version" - ], - "sectionRelativeRepoPath": "portage_keywords", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fportage_keywords.yml&value=name%3A%20portage_keywords%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/portage_packages", - "title": "portage_packages", - "htmlId": "table--portagepackages--af336b6b49", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "portage_packages", - "build_time", - "eapi", - "package", - "repository", - "size", - "slot", - "version", - "world" - ], - "sectionRelativeRepoPath": "portage_packages", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fportage_packages.yml&value=name%3A%20portage_packages%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/portage_use", - "title": "portage_use", - "htmlId": "table--portageuse--61384aa618", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "portage_use", - "package", - "use", - "version" - ], - "sectionRelativeRepoPath": "portage_use", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fportage_use.yml&value=name%3A%20portage_use%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/power_sensors", - "title": "power_sensors", - "htmlId": "table--powersensors--27bd8387f6", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "power_sensors", - "category", - "key", - "name", - "value" - ], - "sectionRelativeRepoPath": "power_sensors", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/power_sensors.yml" - }, - { - "url": "/tables/powershell_events", - "title": "powershell_events", - "htmlId": "table--powershellevents--728605e870", - "evented": true, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "powershell_events", - "cosine_similarity", - "datetime", - "script_block_count", - "script_block_id", - "script_name", - "script_path", - "script_text", - "time" - ], - "sectionRelativeRepoPath": "powershell_events", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fpowershell_events.yml&value=name%3A%20powershell_events%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/preferences", - "title": "preferences", - "htmlId": "table--preferences--96fcf226b3", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "preferences", - "domain", - "forced", - "host", - "key", - "subkey", - "username", - "value" - ], - "sectionRelativeRepoPath": "preferences", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/preferences.yml" - }, - { - "url": "/tables/prefetch", - "title": "prefetch", - "htmlId": "table--prefetch--8592ee7112", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "prefetch", - "accessed_directories", - "accessed_directories_count", - "accessed_files", - "accessed_files_count", - "filename", - "hash", - "last_run_time", - "other_run_times", - "path", - "run_count", - "size", - "volume_creation", - "volume_serial" - ], - "sectionRelativeRepoPath": "prefetch", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fprefetch.yml&value=name%3A%20prefetch%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/privacy_preferences", - "title": "privacy_preferences", - "htmlId": "table--privacypreferences--927ea3e9b3", - "evented": false, - "platforms": [ - "chrome" - ], - "keywordsForSyntaxHighlighting": [ - "privacy_preferences", - "ad_measurement_enabled", - "autofill_address_enabled", - "autofill_credit_card_enabled", - "autofill_enabled", - "do_not_track_enabled", - "fledge_enabled", - "hyperlink_auditing_enabled", - "network_prediction_enabled", - "privacy_sandbox_enabled", - "protected_content_enabled", - "referrers_enabled", - "safe_browsing_enabled", - "safe_browsing_extended_reporting_enabled", - "save_passwords_enabled", - "search_suggest_enabled", - "spelling_service_enabled", - "third_party_cookies_allowed", - "topics_enabled", - "translation_service_enabled", - "web_rtc_ip_handling_policy" - ], - "sectionRelativeRepoPath": "privacy_preferences", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/privacy_preferences.yml" - }, - { - "url": "/tables/process_envs", - "title": "process_envs", - "htmlId": "table--processenvs--586b20fc53", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "process_envs", - "key", - "pid", - "value" - ], - "sectionRelativeRepoPath": "process_envs", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/process_envs.yml" - }, - { - "url": "/tables/process_etw_events", - "title": "process_etw_events", - "htmlId": "table--processetwevents--61143eacfc", - "evented": true, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "process_etw_events", - "cmdline", - "datetime", - "eid", - "exit_code", - "flags", - "header_pid", - "mandatory_label", - "parent_process_sequence_number", - "path", - "pid", - "ppid", - "process_sequence_number", - "session_id", - "time", - "time_windows", - "token_elevation_status", - "token_elevation_type", - "type", - "username" - ], - "sectionRelativeRepoPath": "process_etw_events", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fprocess_etw_events.yml&value=name%3A%20process_etw_events%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/process_events", - "title": "process_events", - "htmlId": "table--processevents--6ae8ba2267", - "evented": true, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "process_events", - "atime", - "auid", - "btime", - "cmdline", - "cmdline_size", - "ctime", - "cwd", - "egid", - "eid", - "env", - "env_count", - "env_size", - "euid", - "fsgid", - "fsuid", - "gid", - "mode", - "mtime", - "overflows", - "owner_gid", - "owner_uid", - "parent", - "path", - "pid", - "sgid", - "status", - "suid", - "syscall", - "time", - "uid", - "uptime" - ], - "sectionRelativeRepoPath": "process_events", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/process_events.yml" - }, - { - "url": "/tables/process_file_events", - "title": "process_file_events", - "htmlId": "table--processfileevents--67c363ae55", - "evented": true, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "process_file_events", - "auid", - "cwd", - "dest_path", - "egid", - "eid", - "euid", - "executable", - "fsgid", - "fsuid", - "gid", - "operation", - "partial", - "path", - "pid", - "ppid", - "sgid", - "suid", - "time", - "uid", - "uptime" - ], - "sectionRelativeRepoPath": "process_file_events", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/process_file_events.yml" - }, - { - "url": "/tables/process_memory_map", - "title": "process_memory_map", - "htmlId": "table--processmemorymap--6bf8d10644", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "process_memory_map", - "device", - "end", - "inode", - "offset", - "path", - "permissions", - "pid", - "pseudo", - "start" - ], - "sectionRelativeRepoPath": "process_memory_map", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/process_memory_map.yml" - }, - { - "url": "/tables/process_namespaces", - "title": "process_namespaces", - "htmlId": "table--processnamespaces--d1156621d4", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "process_namespaces", - "cgroup_namespace", - "ipc_namespace", - "mnt_namespace", - "net_namespace", - "pid", - "pid_namespace", - "user_namespace", - "uts_namespace" - ], - "sectionRelativeRepoPath": "process_namespaces", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fprocess_namespaces.yml&value=name%3A%20process_namespaces%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/process_open_files", - "title": "process_open_files", - "htmlId": "table--processopenfiles--43c8c6bba0", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "process_open_files", - "fd", - "path", - "pid" - ], - "sectionRelativeRepoPath": "process_open_files", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/process_open_files.yml" - }, - { - "url": "/tables/process_open_pipes", - "title": "process_open_pipes", - "htmlId": "table--processopenpipes--0f49c83994", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "process_open_pipes", - "fd", - "inode", - "mode", - "partner_fd", - "partner_mode", - "partner_pid", - "pid", - "type" - ], - "sectionRelativeRepoPath": "process_open_pipes", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fprocess_open_pipes.yml&value=name%3A%20process_open_pipes%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/process_open_sockets", - "title": "process_open_sockets", - "htmlId": "table--processopensockets--9dc2c99a67", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "process_open_sockets", - "family", - "fd", - "local_address", - "local_port", - "net_namespace", - "path", - "pid", - "protocol", - "remote_address", - "remote_port", - "socket", - "state" - ], - "sectionRelativeRepoPath": "process_open_sockets", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/process_open_sockets.yml" - }, - { - "url": "/tables/processes", - "title": "processes", - "htmlId": "table--processes--3a54ed4992", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "processes", - "cgroup_path", - "cmdline", - "cpu_subtype", - "cpu_type", - "cwd", - "disk_bytes_read", - "disk_bytes_written", - "egid", - "elapsed_time", - "elevated_token", - "euid", - "gid", - "handle_count", - "name", - "nice", - "on_disk", - "parent", - "path", - "percent_processor_time", - "pgroup", - "pid", - "protection_type", - "resident_size", - "root", - "secure_process", - "sgid", - "start_time", - "state", - "suid", - "system_time", - "threads", - "total_size", - "translated", - "uid", - "upid", - "uppid", - "user_time", - "virtual_process", - "wired_size" - ], - "sectionRelativeRepoPath": "processes", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/processes.yml" - }, - { - "url": "/tables/programs", - "title": "programs", - "htmlId": "table--programs--f7f76d14a9", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "programs", - "identifying_number", - "install_date", - "install_location", - "install_source", - "language", - "name", - "publisher", - "uninstall_string", - "version" - ], - "sectionRelativeRepoPath": "programs", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/programs.yml" - }, - { - "url": "/tables/prometheus_metrics", - "title": "prometheus_metrics", - "htmlId": "table--prometheusmetrics--f6ce409d91", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "prometheus_metrics", - "metric_name", - "metric_value", - "target_name", - "timestamp_ms" - ], - "sectionRelativeRepoPath": "prometheus_metrics", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fprometheus_metrics.yml&value=name%3A%20prometheus_metrics%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/puppet_info", - "title": "puppet_info", - "htmlId": "table--puppetinfo--ce553a89eb", - "evented": false, - "platforms": [ - "darwin", - "windows", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "puppet_info", - "cached_catalog_status", - "catalog_uuid", - "code_id", - "configuration_version", - "corrective_change", - "environment", - "host", - "kind", - "master_used", - "noop", - "noop_pending", - "puppet_version", - "report_format", - "status", - "time", - "transaction_completed", - "transaction_uuid" - ], - "sectionRelativeRepoPath": "puppet_info", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/puppet_info.yml" - }, - { - "url": "/tables/puppet_logs", - "title": "puppet_logs", - "htmlId": "table--puppetlogs--c81bf10f91", - "evented": false, - "platforms": [ - "darwin", - "windows", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "puppet_logs", - "file", - "level", - "line", - "message", - "source", - "time" - ], - "sectionRelativeRepoPath": "puppet_logs", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/puppet_logs.yml" - }, - { - "url": "/tables/puppet_state", - "title": "puppet_state", - "htmlId": "table--puppetstate--802f52e922", - "evented": false, - "platforms": [ - "darwin", - "windows", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "puppet_state", - "change_count", - "changed", - "corrective_change", - "evaluation_time", - "failed", - "file", - "line", - "out_of_sync", - "out_of_sync_count", - "resource", - "resource_type", - "skipped", - "title" - ], - "sectionRelativeRepoPath": "puppet_state", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/puppet_state.yml" - }, - { - "url": "/tables/pwd_policy", - "title": "pwd_policy", - "htmlId": "table--pwdpolicy--b862a98afa", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "pwd_policy", - "days_to_expiration", - "expires_every_n_days", - "history_depth", - "max_failed_attempts", - "min_mixed_case_characters" - ], - "sectionRelativeRepoPath": "pwd_policy", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/pwd_policy.yml" - }, - { - "url": "/tables/python_packages", - "title": "python_packages", - "htmlId": "table--pythonpackages--31ae8c2370", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "python_packages", - "author", - "directory", - "license", - "name", - "path", - "pid_with_namespace", - "summary", - "version" - ], - "sectionRelativeRepoPath": "python_packages", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/python_packages.yml" - }, - { - "url": "/tables/quicklook_cache", - "title": "quicklook_cache", - "htmlId": "table--quicklookcache--19ae561620", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "quicklook_cache", - "cache_path", - "fs_id", - "hit_count", - "icon_mode", - "inode", - "label", - "last_hit_date", - "mtime", - "path", - "rowid", - "size", - "volume_id" - ], - "sectionRelativeRepoPath": "quicklook_cache", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fquicklook_cache.yml&value=name%3A%20quicklook_cache%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/registry", - "title": "registry", - "htmlId": "table--registry--415b2b1c89", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "registry", - "data", - "key", - "mtime", - "name", - "path", - "type" - ], - "sectionRelativeRepoPath": "registry", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/registry.yml" - }, - { - "url": "/tables/routes", - "title": "routes", - "htmlId": "table--routes--ed00beba43", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "routes", - "destination", - "flags", - "gateway", - "hopcount", - "interface", - "metric", - "mtu", - "netmask", - "source", - "type" - ], - "sectionRelativeRepoPath": "routes", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/routes.yml" - }, - { - "url": "/tables/rpm_package_files", - "title": "rpm_package_files", - "htmlId": "table--rpmpackagefiles--96e530c921", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "rpm_package_files", - "groupname", - "mode", - "package", - "path", - "sha256", - "size", - "username" - ], - "sectionRelativeRepoPath": "rpm_package_files", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Frpm_package_files.yml&value=name%3A%20rpm_package_files%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/rpm_packages", - "title": "rpm_packages", - "htmlId": "table--rpmpackages--e4da8f9f41", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "rpm_packages", - "arch", - "epoch", - "install_time", - "mount_namespace_id", - "name", - "package_group", - "pid_with_namespace", - "release", - "sha1", - "size", - "source", - "vendor", - "version" - ], - "sectionRelativeRepoPath": "rpm_packages", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/rpm_packages.yml" - }, - { - "url": "/tables/running_apps", - "title": "running_apps", - "htmlId": "table--runningapps--c9443711d8", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "running_apps", - "bundle_identifier", - "is_active", - "pid" - ], - "sectionRelativeRepoPath": "running_apps", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/running_apps.yml" - }, - { - "url": "/tables/safari_extensions", - "title": "safari_extensions", - "htmlId": "table--safariextensions--75748b2d43", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "safari_extensions", - "author", - "bundle_version", - "copyright", - "description", - "developer_id", - "extension_type", - "identifier", - "name", - "path", - "sdk", - "uid", - "update_url", - "version" - ], - "sectionRelativeRepoPath": "safari_extensions", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/safari_extensions.yml" - }, - { - "url": "/tables/sandboxes", - "title": "sandboxes", - "htmlId": "table--sandboxes--c68d00ef55", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "sandboxes", - "build_id", - "bundle_path", - "enabled", - "label", - "path", - "user" - ], - "sectionRelativeRepoPath": "sandboxes", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fsandboxes.yml&value=name%3A%20sandboxes%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/scheduled_tasks", - "title": "scheduled_tasks", - "htmlId": "table--scheduledtasks--a69b6b604d", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "scheduled_tasks", - "action", - "enabled", - "hidden", - "last_run_code", - "last_run_message", - "last_run_time", - "name", - "next_run_time", - "path", - "state" - ], - "sectionRelativeRepoPath": "scheduled_tasks", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/scheduled_tasks.yml" - }, - { - "url": "/tables/screenlock", - "title": "screenlock", - "htmlId": "table--screenlock--91a400ed71", - "evented": false, - "platforms": [ - "darwin", - "chrome" - ], - "keywordsForSyntaxHighlighting": [ - "screenlock", - "enabled", - "grace_period" - ], - "sectionRelativeRepoPath": "screenlock", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/screenlock.yml" - }, - { - "url": "/tables/seccomp_events", - "title": "seccomp_events", - "htmlId": "table--seccompevents--5cf6060bd9", - "evented": true, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "seccomp_events", - "arch", - "auid", - "code", - "comm", - "compat", - "exe", - "gid", - "ip", - "pid", - "ses", - "sig", - "syscall", - "time", - "uid", - "uptime" - ], - "sectionRelativeRepoPath": "seccomp_events", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fseccomp_events.yml&value=name%3A%20seccomp_events%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/secureboot", - "title": "secureboot", - "htmlId": "table--secureboot--299ca9c718", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "secureboot", - "description", - "kernel_extensions", - "mdm_operations", - "secure_boot", - "secure_mode", - "setup_mode" - ], - "sectionRelativeRepoPath": "secureboot", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/secureboot.yml" - }, - { - "url": "/tables/security_profile_info", - "title": "security_profile_info", - "htmlId": "table--securityprofileinfo--17121d5fa6", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "security_profile_info", - "audit_account_logon", - "audit_account_manage", - "audit_ds_access", - "audit_logon_events", - "audit_object_access", - "audit_policy_change", - "audit_privilege_use", - "audit_process_tracking", - "audit_system_events", - "clear_text_password", - "enable_admin_account", - "enable_guest_account", - "force_logoff_when_expire", - "lockout_bad_count", - "logon_to_change_password", - "lsa_anonymous_name_lookup", - "maximum_password_age", - "minimum_password_age", - "minimum_password_length", - "new_administrator_name", - "new_guest_name", - "password_complexity", - "password_history_size" - ], - "sectionRelativeRepoPath": "security_profile_info", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fsecurity_profile_info.yml&value=name%3A%20security_profile_info%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/selinux_events", - "title": "selinux_events", - "htmlId": "table--selinuxevents--cfc47c5cc9", - "evented": true, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "selinux_events", - "eid", - "message", - "time", - "type", - "uptime" - ], - "sectionRelativeRepoPath": "selinux_events", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fselinux_events.yml&value=name%3A%20selinux_events%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/selinux_settings", - "title": "selinux_settings", - "htmlId": "table--selinuxsettings--392476076c", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "selinux_settings", - "key", - "scope", - "value" - ], - "sectionRelativeRepoPath": "selinux_settings", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fselinux_settings.yml&value=name%3A%20selinux_settings%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/services", - "title": "services", - "htmlId": "table--services--a7e374154f", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "services", - "description", - "display_name", - "module_path", - "name", - "path", - "pid", - "service_exit_code", - "service_type", - "start_type", - "status", - "user_account", - "win32_exit_code" - ], - "sectionRelativeRepoPath": "services", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fservices.yml&value=name%3A%20services%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/shadow", - "title": "shadow", - "htmlId": "table--shadow--2a5e749131", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "shadow", - "expire", - "flag", - "hash_alg", - "inactive", - "last_change", - "max", - "min", - "password_status", - "username", - "warning" - ], - "sectionRelativeRepoPath": "shadow", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fshadow.yml&value=name%3A%20shadow%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/shared_folders", - "title": "shared_folders", - "htmlId": "table--sharedfolders--edd6c29f21", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "shared_folders", - "name", - "path" - ], - "sectionRelativeRepoPath": "shared_folders", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/shared_folders.yml" - }, - { - "url": "/tables/shared_memory", - "title": "shared_memory", - "htmlId": "table--sharedmemory--4632a169c9", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "shared_memory", - "atime", - "attached", - "creator_pid", - "creator_uid", - "ctime", - "dtime", - "locked", - "owner_uid", - "permissions", - "pid", - "shmid", - "size", - "status" - ], - "sectionRelativeRepoPath": "shared_memory", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fshared_memory.yml&value=name%3A%20shared_memory%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/shared_resources", - "title": "shared_resources", - "htmlId": "table--sharedresources--1eedd340fb", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "shared_resources", - "allow_maximum", - "description", - "install_date", - "maximum_allowed", - "name", - "path", - "status", - "type", - "type_name" - ], - "sectionRelativeRepoPath": "shared_resources", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/shared_resources.yml" - }, - { - "url": "/tables/sharing_preferences", - "title": "sharing_preferences", - "htmlId": "table--sharingpreferences--435a39048e", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "sharing_preferences", - "bluetooth_sharing", - "content_caching", - "disc_sharing", - "file_sharing", - "internet_sharing", - "printer_sharing", - "remote_apple_events", - "remote_login", - "remote_management", - "screen_sharing" - ], - "sectionRelativeRepoPath": "sharing_preferences", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/sharing_preferences.yml" - }, - { - "url": "/tables/shell_history", - "title": "shell_history", - "htmlId": "table--shellhistory--487890df4c", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "shell_history", - "command", - "history_file", - "time", - "uid" - ], - "sectionRelativeRepoPath": "shell_history", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/shell_history.yml" - }, - { - "url": "/tables/shellbags", - "title": "shellbags", - "htmlId": "table--shellbags--ea58c94fcb", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "shellbags", - "accessed_time", - "created_time", - "mft_entry", - "mft_sequence", - "modified_time", - "path", - "sid", - "source" - ], - "sectionRelativeRepoPath": "shellbags", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fshellbags.yml&value=name%3A%20shellbags%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/shimcache", - "title": "shimcache", - "htmlId": "table--shimcache--78c1808f2a", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "shimcache", - "entry", - "execution_flag", - "modified_time", - "path" - ], - "sectionRelativeRepoPath": "shimcache", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/shimcache.yml" - }, - { - "url": "/tables/signature", - "title": "signature", - "htmlId": "table--signature--651b5e1a16", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "signature", - "arch", - "authority", - "cdhash", - "hash_resources", - "identifier", - "path", - "signed", - "team_identifier" - ], - "sectionRelativeRepoPath": "signature", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/signature.yml" - }, - { - "url": "/tables/sip_config", - "title": "sip_config", - "htmlId": "table--sipconfig--72a4d07300", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "sip_config", - "config_flag", - "enabled", - "enabled_nvram" - ], - "sectionRelativeRepoPath": "sip_config", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/sip_config.yml" - }, - { - "url": "/tables/smbios_tables", - "title": "smbios_tables", - "htmlId": "table--smbiostables--14a3086ac5", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "smbios_tables", - "description", - "handle", - "header_size", - "md5", - "number", - "size", - "type" - ], - "sectionRelativeRepoPath": "smbios_tables", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/smbios_tables.yml" - }, - { - "url": "/tables/smc_keys", - "title": "smc_keys", - "htmlId": "table--smckeys--65a180be47", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "smc_keys", - "hidden", - "key", - "size", - "type", - "value" - ], - "sectionRelativeRepoPath": "smc_keys", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/smc_keys.yml" - }, - { - "url": "/tables/sntp_request", - "title": "sntp_request", - "htmlId": "table--sntprequest--31b3965f95", - "evented": false, - "platforms": [ - "darwin", - "windows", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "sntp_request", - "clock_offset_ms", - "server", - "timestamp_ms" - ], - "sectionRelativeRepoPath": "sntp_request", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/sntp_request.yml" - }, - { - "url": "/tables/socket_events", - "title": "socket_events", - "htmlId": "table--socketevents--45972f7f3b", - "evented": true, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "socket_events", - "action", - "auid", - "eid", - "family", - "fd", - "local_address", - "local_port", - "path", - "pid", - "protocol", - "remote_address", - "remote_port", - "socket", - "status", - "success", - "time", - "uptime" - ], - "sectionRelativeRepoPath": "socket_events", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fsocket_events.yml&value=name%3A%20socket_events%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/sofa_security_release_info", - "title": "sofa_security_release_info", - "htmlId": "table--sofasecurityreleaseinfo--b23bdf9329", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "sofa_security_release_info", - "days_since_previous_release", - "os_version", - "product_version", - "release_date", - "security_info", - "unique_cves_count", - "update_name" - ], - "sectionRelativeRepoPath": "sofa_security_release_info", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/sofa_security_release_info.yml" - }, - { - "url": "/tables/sofa_unpatched_cves", - "title": "sofa_unpatched_cves", - "htmlId": "table--sofaunpatchedcves--680ab849b7", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "sofa_unpatched_cves", - "actively_exploited", - "cve", - "os_version", - "patched_version" - ], - "sectionRelativeRepoPath": "sofa_unpatched_cves", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/sofa_unpatched_cves.yml" - }, - { - "url": "/tables/software_update", - "title": "software_update", - "htmlId": "table--softwareupdate--6cb5e63ee6", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "software_update", - "software_update_required" - ], - "sectionRelativeRepoPath": "software_update", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/software_update.yml" - }, - { - "url": "/tables/ssh_configs", - "title": "ssh_configs", - "htmlId": "table--sshconfigs--084b9832a4", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "ssh_configs", - "block", - "option", - "ssh_config_file", - "uid" - ], - "sectionRelativeRepoPath": "ssh_configs", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/ssh_configs.yml" - }, - { - "url": "/tables/startup_items", - "title": "startup_items", - "htmlId": "table--startupitems--f212a6ad4e", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "startup_items", - "args", - "name", - "path", - "source", - "status", - "type", - "username" - ], - "sectionRelativeRepoPath": "startup_items", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/startup_items.yml" - }, - { - "url": "/tables/sudo_info", - "title": "sudo_info", - "htmlId": "table--sudoinfo--91f0750d0d", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "sudo_info", - "json_result" - ], - "sectionRelativeRepoPath": "sudo_info", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/sudo_info.yml" - }, - { - "url": "/tables/sudoers", - "title": "sudoers", - "htmlId": "table--sudoers--53cbb8caa7", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "sudoers", - "header", - "rule_details", - "source" - ], - "sectionRelativeRepoPath": "sudoers", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/sudoers.yml" - }, - { - "url": "/tables/suid_bin", - "title": "suid_bin", - "htmlId": "table--suidbin--12efbe4810", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "suid_bin", - "groupname", - "path", - "permissions", - "pid_with_namespace", - "username" - ], - "sectionRelativeRepoPath": "suid_bin", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/suid_bin.yml" - }, - { - "url": "/tables/syslog_events", - "title": "syslog_events", - "htmlId": "table--syslogevents--cc5c3d702f", - "evented": true, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "syslog_events", - "datetime", - "eid", - "facility", - "host", - "message", - "severity", - "tag", - "time" - ], - "sectionRelativeRepoPath": "syslog_events", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fsyslog_events.yml&value=name%3A%20syslog_events%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/system_controls", - "title": "system_controls", - "htmlId": "table--systemcontrols--bc070f5bb2", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "system_controls", - "config_value", - "current_value", - "field_name", - "name", - "oid", - "subsystem", - "type" - ], - "sectionRelativeRepoPath": "system_controls", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/system_controls.yml" - }, - { - "url": "/tables/system_extensions", - "title": "system_extensions", - "htmlId": "table--systemextensions--59019bbb28", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "system_extensions", - "UUID", - "bundle_path", - "category", - "identifier", - "mdm_managed", - "path", - "state", - "team", - "version" - ], - "sectionRelativeRepoPath": "system_extensions", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/system_extensions.yml" - }, - { - "url": "/tables/system_info", - "title": "system_info", - "htmlId": "table--systeminfo--4f963da54a", - "evented": false, - "platforms": [ - "windows", - "darwin", - "linux", - "chrome" - ], - "keywordsForSyntaxHighlighting": [ - "system_info", - "board_model", - "board_serial", - "board_vendor", - "board_version", - "computer_name", - "cpu_brand", - "cpu_logical_cores", - "cpu_microcode", - "cpu_physical_cores", - "cpu_sockets", - "cpu_subtype", - "cpu_type", - "hardware_model", - "hardware_serial", - "hardware_vendor", - "hardware_version", - "hostname", - "local_hostname", - "physical_memory", - "uuid" - ], - "sectionRelativeRepoPath": "system_info", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/system_info.yml" - }, - { - "url": "/tables/system_state", - "title": "system_state", - "htmlId": "table--systemstate--d1ce3bbb0e", - "evented": false, - "platforms": [ - "chrome" - ], - "keywordsForSyntaxHighlighting": [ - "system_state", - "idle_state" - ], - "sectionRelativeRepoPath": "system_state", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/system_state.yml" - }, - { - "url": "/tables/systemd_units", - "title": "systemd_units", - "htmlId": "table--systemdunits--cc47585fcb", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "systemd_units", - "active_state", - "description", - "following", - "fragment_path", - "id", - "job_id", - "job_path", - "job_type", - "load_state", - "object_path", - "source_path", - "sub_state", - "unit_file_state", - "user" - ], - "sectionRelativeRepoPath": "systemd_units", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fsystemd_units.yml&value=name%3A%20systemd_units%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/tcc_access", - "title": "tcc_access", - "htmlId": "table--tccaccess--103e029af3", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "tcc_access", - "auth_reason", - "auth_value", - "client", - "client_type", - "indirect_object_identifier", - "indirect_object_identifier_type", - "last_modified", - "policy_id", - "service", - "source", - "uid" - ], - "sectionRelativeRepoPath": "tcc_access", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/tcc_access.yml" - }, - { - "url": "/tables/temperature_sensors", - "title": "temperature_sensors", - "htmlId": "table--temperaturesensors--952195065c", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "temperature_sensors", - "celsius", - "fahrenheit", - "key", - "name" - ], - "sectionRelativeRepoPath": "temperature_sensors", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/temperature_sensors.yml" - }, - { - "url": "/tables/time", - "title": "time", - "htmlId": "table--time--740a172c2f", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "time", - "datetime", - "day", - "hour", - "iso_8601", - "local_timezone", - "minutes", - "month", - "seconds", - "timestamp", - "timezone", - "unix_time", - "weekday", - "win_timestamp", - "year" - ], - "sectionRelativeRepoPath": "time", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/time.yml" - }, - { - "url": "/tables/time_machine_backups", - "title": "time_machine_backups", - "htmlId": "table--timemachinebackups--6a1cb2e696", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "time_machine_backups", - "backup_date", - "destination_id" - ], - "sectionRelativeRepoPath": "time_machine_backups", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/time_machine_backups.yml" - }, - { - "url": "/tables/time_machine_destinations", - "title": "time_machine_destinations", - "htmlId": "table--timemachinedestinations--8c33b4e082", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "time_machine_destinations", - "alias", - "bytes_available", - "bytes_used", - "consistency_scan_date", - "destination_id", - "encryption", - "root_volume_uuid" - ], - "sectionRelativeRepoPath": "time_machine_destinations", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/time_machine_destinations.yml" - }, - { - "url": "/tables/tpm_info", - "title": "tpm_info", - "htmlId": "table--tpminfo--086cc37696", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "tpm_info", - "activated", - "enabled", - "manufacturer_id", - "manufacturer_name", - "manufacturer_version", - "owned", - "physical_presence_version", - "product_name", - "spec_version" - ], - "sectionRelativeRepoPath": "tpm_info", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Ftpm_info.yml&value=name%3A%20tpm_info%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/ulimit_info", - "title": "ulimit_info", - "htmlId": "table--ulimitinfo--9cff90dafb", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "ulimit_info", - "hard_limit", - "soft_limit", - "type" - ], - "sectionRelativeRepoPath": "ulimit_info", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/ulimit_info.yml" - }, - { - "url": "/tables/unified_log", - "title": "unified_log", - "htmlId": "table--unifiedlog--d971aaf7c9", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "unified_log", - "activity", - "category", - "level", - "max_rows", - "message", - "pid", - "predicate", - "process", - "sender", - "storage", - "subsystem", - "tid", - "timestamp" - ], - "sectionRelativeRepoPath": "unified_log", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Funified_log.yml&value=name%3A%20unified_log%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/uptime", - "title": "uptime", - "htmlId": "table--uptime--542f2cc52b", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "uptime", - "days", - "hours", - "minutes", - "seconds", - "total_seconds" - ], - "sectionRelativeRepoPath": "uptime", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/uptime.yml" - }, - { - "url": "/tables/usb_devices", - "title": "usb_devices", - "htmlId": "table--usbdevices--12892f9cf7", - "evented": false, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "usb_devices", - "class", - "model", - "model_id", - "protocol", - "removable", - "serial", - "subclass", - "usb_address", - "usb_port", - "vendor", - "vendor_id", - "version" - ], - "sectionRelativeRepoPath": "usb_devices", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/usb_devices.yml" - }, - { - "url": "/tables/user_events", - "title": "user_events", - "htmlId": "table--userevents--8aaee70de1", - "evented": true, - "platforms": [ - "darwin", - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "user_events", - "address", - "auid", - "eid", - "message", - "path", - "pid", - "terminal", - "time", - "type", - "uid", - "uptime" - ], - "sectionRelativeRepoPath": "user_events", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fuser_events.yml&value=name%3A%20user_events%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/user_groups", - "title": "user_groups", - "htmlId": "table--usergroups--03e0b1a5e7", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "user_groups", - "gid", - "uid" - ], - "sectionRelativeRepoPath": "user_groups", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fuser_groups.yml&value=name%3A%20user_groups%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/user_interaction_events", - "title": "user_interaction_events", - "htmlId": "table--userinteractionevents--ed2ac5b181", - "evented": true, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "user_interaction_events", - "time" - ], - "sectionRelativeRepoPath": "user_interaction_events", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fuser_interaction_events.yml&value=name%3A%20user_interaction_events%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/user_login_settings", - "title": "user_login_settings", - "htmlId": "table--userloginsettings--1abbdf6e57", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "user_login_settings", - "password_hint_enabled" - ], - "sectionRelativeRepoPath": "user_login_settings", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/user_login_settings.yml" - }, - { - "url": "/tables/user_ssh_keys", - "title": "user_ssh_keys", - "htmlId": "table--usersshkeys--1ba0f20456", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "user_ssh_keys", - "encrypted", - "key_type", - "path", - "pid_with_namespace", - "uid" - ], - "sectionRelativeRepoPath": "user_ssh_keys", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/user_ssh_keys.yml" - }, - { - "url": "/tables/userassist", - "title": "userassist", - "htmlId": "table--userassist--4e3bbdb293", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "userassist", - "count", - "last_execution_time", - "path", - "sid" - ], - "sectionRelativeRepoPath": "userassist", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/userassist.yml" - }, - { - "url": "/tables/users", - "title": "users", - "htmlId": "table--users--023e2862dc", - "evented": false, - "platforms": [ - "darwin", - "windows", - "linux", - "chrome" - ], - "keywordsForSyntaxHighlighting": [ - "users", - "description", - "directory", - "email", - "gid", - "gid_signed", - "is_hidden", - "pid_with_namespace", - "shell", - "type", - "uid", - "uid_signed", - "username", - "uuid" - ], - "sectionRelativeRepoPath": "users", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/users.yml" - }, - { - "url": "/tables/video_info", - "title": "video_info", - "htmlId": "table--videoinfo--bcca78a3df", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "video_info", - "color_depth", - "driver", - "driver_date", - "driver_version", - "manufacturer", - "model", - "series", - "video_mode" - ], - "sectionRelativeRepoPath": "video_info", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fvideo_info.yml&value=name%3A%20video_info%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/virtual_memory_info", - "title": "virtual_memory_info", - "htmlId": "table--virtualmemoryinfo--4c4e71449e", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "virtual_memory_info", - "active", - "anonymous", - "compressed", - "compressor", - "copy", - "decompressed", - "faults", - "file_backed", - "free", - "inactive", - "page_ins", - "page_outs", - "purgeable", - "purged", - "reactivated", - "speculative", - "swap_ins", - "swap_outs", - "throttled", - "uncompressed", - "wired", - "zero_fill" - ], - "sectionRelativeRepoPath": "virtual_memory_info", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/virtual_memory_info.yml" - }, - { - "url": "/tables/vscode_extensions", - "title": "vscode_extensions", - "htmlId": "table--vscodeextensions--3122f67e21", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "vscode_extensions", - "installed_at", - "name", - "path", - "prerelease", - "publisher", - "publisher_id", - "uid", - "uuid", - "version" - ], - "sectionRelativeRepoPath": "vscode_extensions", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/vscode_extensions.yml" - }, - { - "url": "/tables/wifi_networks", - "title": "wifi_networks", - "htmlId": "table--wifinetworks--196d0fe380", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "wifi_networks", - "add_reason", - "added_at", - "auto_join", - "auto_login", - "captive_login_date", - "captive_portal", - "disabled", - "last_connected", - "network_name", - "passpoint", - "personal_hotspot", - "possibly_hidden", - "roaming", - "roaming_profile", - "security_type", - "ssid", - "temporarily_disabled", - "was_captive_network" - ], - "sectionRelativeRepoPath": "wifi_networks", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/wifi_networks.yml" - }, - { - "url": "/tables/wifi_status", - "title": "wifi_status", - "htmlId": "table--wifistatus--7d5af734ae", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "wifi_status", - "bssid", - "channel", - "channel_band", - "channel_width", - "country_code", - "interface", - "mode", - "network_name", - "noise", - "rssi", - "security_type", - "ssid", - "transmit_rate" - ], - "sectionRelativeRepoPath": "wifi_status", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/wifi_status.yml" - }, - { - "url": "/tables/wifi_survey", - "title": "wifi_survey", - "htmlId": "table--wifisurvey--86f4a22532", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "wifi_survey", - "bssid", - "channel", - "channel_band", - "channel_width", - "country_code", - "interface", - "network_name", - "noise", - "rssi", - "ssid" - ], - "sectionRelativeRepoPath": "wifi_survey", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/wifi_survey.yml" - }, - { - "url": "/tables/winbaseobj", - "title": "winbaseobj", - "htmlId": "table--winbaseobj--0e0dd909ed", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "winbaseobj", - "object_name", - "object_type", - "session_id" - ], - "sectionRelativeRepoPath": "winbaseobj", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fwinbaseobj.yml&value=name%3A%20winbaseobj%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/windows_crashes", - "title": "windows_crashes", - "htmlId": "table--windowscrashes--3bcda23e6b", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "windows_crashes", - "build_number", - "command_line", - "crash_path", - "current_directory", - "datetime", - "exception_address", - "exception_code", - "exception_message", - "machine_name", - "major_version", - "minor_version", - "module", - "path", - "pid", - "process_uptime", - "registers", - "stack_trace", - "tid", - "type", - "username", - "version" - ], - "sectionRelativeRepoPath": "windows_crashes", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fwindows_crashes.yml&value=name%3A%20windows_crashes%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/windows_eventlog", - "title": "windows_eventlog", - "htmlId": "table--windowseventlog--c368bc9838", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "windows_eventlog", - "channel", - "computer_name", - "data", - "datetime", - "eventid", - "keywords", - "level", - "pid", - "provider_guid", - "provider_name", - "task", - "tid", - "time_range", - "timestamp", - "xpath" - ], - "sectionRelativeRepoPath": "windows_eventlog", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/windows_eventlog.yml" - }, - { - "url": "/tables/windows_events", - "title": "windows_events", - "htmlId": "table--windowsevents--b4aae30966", - "evented": true, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "windows_events", - "computer_name", - "data", - "datetime", - "eid", - "eventid", - "keywords", - "level", - "provider_guid", - "provider_name", - "source", - "task", - "time" - ], - "sectionRelativeRepoPath": "windows_events", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fwindows_events.yml&value=name%3A%20windows_events%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/windows_firewall_rules", - "title": "windows_firewall_rules", - "htmlId": "table--windowsfirewallrules--54886746d8", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "windows_firewall_rules", - "action", - "app_name", - "direction", - "enabled", - "grouping", - "icmp_types_codes", - "local_addresses", - "local_ports", - "name", - "profile_domain", - "profile_private", - "profile_public", - "protocol", - "remote_addresses", - "remote_ports", - "service_name" - ], - "sectionRelativeRepoPath": "windows_firewall_rules", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/windows_firewall_rules.yml" - }, - { - "url": "/tables/windows_optional_features", - "title": "windows_optional_features", - "htmlId": "table--windowsoptionalfeatures--7fc389462f", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "windows_optional_features", - "caption", - "name", - "state", - "statename" - ], - "sectionRelativeRepoPath": "windows_optional_features", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/windows_optional_features.yml" - }, - { - "url": "/tables/windows_search", - "title": "windows_search", - "htmlId": "table--windowssearch--3bc557a530", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "windows_search", - "additional_properties", - "date_created", - "date_modified", - "max_results", - "name", - "owner", - "path", - "properties", - "query", - "size", - "sort", - "type" - ], - "sectionRelativeRepoPath": "windows_search", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fwindows_search.yml&value=name%3A%20windows_search%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/windows_security_center", - "title": "windows_security_center", - "htmlId": "table--windowssecuritycenter--8c6fbc78cd", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "windows_security_center", - "antispyware", - "antivirus", - "autoupdate", - "firewall", - "internet_settings", - "user_account_control", - "windows_security_center_service" - ], - "sectionRelativeRepoPath": "windows_security_center", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fwindows_security_center.yml&value=name%3A%20windows_security_center%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/windows_security_products", - "title": "windows_security_products", - "htmlId": "table--windowssecurityproducts--f74ebb0ecc", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "windows_security_products", - "name", - "remediation_path", - "signatures_up_to_date", - "state", - "state_timestamp", - "type" - ], - "sectionRelativeRepoPath": "windows_security_products", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fwindows_security_products.yml&value=name%3A%20windows_security_products%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/windows_update_history", - "title": "windows_update_history", - "htmlId": "table--windowsupdatehistory--ef7bb6c2c1", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "windows_update_history", - "client_app_id", - "date", - "description", - "hresult", - "operation", - "result_code", - "server_selection", - "service_id", - "support_url", - "title", - "update_id", - "update_revision" - ], - "sectionRelativeRepoPath": "windows_update_history", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fwindows_update_history.yml&value=name%3A%20windows_update_history%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/windows_updates", - "title": "windows_updates", - "htmlId": "table--windowsupdates--aa61957cff", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "windows_updates", - "fullkey", - "is_default", - "key", - "locale", - "parent", - "query", - "value" - ], - "sectionRelativeRepoPath": "windows_updates", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/windows_updates.yml" - }, - { - "url": "/tables/wmi_bios_info", - "title": "wmi_bios_info", - "htmlId": "table--wmibiosinfo--e665577f28", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "wmi_bios_info", - "name", - "value" - ], - "sectionRelativeRepoPath": "wmi_bios_info", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fwmi_bios_info.yml&value=name%3A%20wmi_bios_info%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/wmi_cli_event_consumers", - "title": "wmi_cli_event_consumers", - "htmlId": "table--wmiclieventconsumers--d43fbe70e9", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "wmi_cli_event_consumers", - "class", - "command_line_template", - "executable_path", - "name", - "relative_path" - ], - "sectionRelativeRepoPath": "wmi_cli_event_consumers", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fwmi_cli_event_consumers.yml&value=name%3A%20wmi_cli_event_consumers%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/wmi_event_filters", - "title": "wmi_event_filters", - "htmlId": "table--wmieventfilters--04ba1150eb", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "wmi_event_filters", - "class", - "name", - "query", - "query_language", - "relative_path" - ], - "sectionRelativeRepoPath": "wmi_event_filters", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fwmi_event_filters.yml&value=name%3A%20wmi_event_filters%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/wmi_filter_consumer_binding", - "title": "wmi_filter_consumer_binding", - "htmlId": "table--wmifilterconsumerbinding--c53468b489", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "wmi_filter_consumer_binding", - "class", - "consumer", - "filter", - "relative_path" - ], - "sectionRelativeRepoPath": "wmi_filter_consumer_binding", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fwmi_filter_consumer_binding.yml&value=name%3A%20wmi_filter_consumer_binding%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/wmi_script_event_consumers", - "title": "wmi_script_event_consumers", - "htmlId": "table--wmiscripteventconsumers--9275e5f795", - "evented": false, - "platforms": [ - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "wmi_script_event_consumers", - "class", - "name", - "relative_path", - "script_file_name", - "script_text", - "scripting_engine" - ], - "sectionRelativeRepoPath": "wmi_script_event_consumers", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fwmi_script_event_consumers.yml&value=name%3A%20wmi_script_event_consumers%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/xprotect_entries", - "title": "xprotect_entries", - "htmlId": "table--xprotectentries--82da15dfc5", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "xprotect_entries", - "filename", - "filetype", - "identity", - "launch_type", - "name", - "optional", - "uses_pattern" - ], - "sectionRelativeRepoPath": "xprotect_entries", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/xprotect_entries.yml" - }, - { - "url": "/tables/xprotect_meta", - "title": "xprotect_meta", - "htmlId": "table--xprotectmeta--d9c759b143", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "xprotect_meta", - "developer_id", - "identifier", - "min_version", - "type" - ], - "sectionRelativeRepoPath": "xprotect_meta", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/xprotect_meta.yml" - }, - { - "url": "/tables/xprotect_reports", - "title": "xprotect_reports", - "htmlId": "table--xprotectreports--ed058eba3f", - "evented": false, - "platforms": [ - "darwin" - ], - "keywordsForSyntaxHighlighting": [ - "xprotect_reports", - "name", - "time", - "user_action" - ], - "sectionRelativeRepoPath": "xprotect_reports", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/xprotect_reports.yml" - }, - { - "url": "/tables/yara", - "title": "yara", - "htmlId": "table--yara--f7412a4474", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "yara", - "count", - "matches", - "path", - "pid_with_namespace", - "sig_group", - "sigfile", - "sigrule", - "sigurl", - "strings", - "tags" - ], - "sectionRelativeRepoPath": "yara", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/yara.yml" - }, - { - "url": "/tables/yara_events", - "title": "yara_events", - "htmlId": "table--yaraevents--a3df07297e", - "evented": true, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "yara_events", - "action", - "category", - "count", - "eid", - "matches", - "strings", - "tags", - "target_path", - "time", - "transaction_id" - ], - "sectionRelativeRepoPath": "yara_events", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fyara_events.yml&value=name%3A%20yara_events%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/ycloud_instance_metadata", - "title": "ycloud_instance_metadata", - "htmlId": "table--ycloudinstancemetadata--91cc1e1945", - "evented": false, - "platforms": [ - "darwin", - "linux", - "windows" - ], - "keywordsForSyntaxHighlighting": [ - "ycloud_instance_metadata", - "cloud_id", - "description", - "folder_id", - "hostname", - "instance_id", - "metadata_endpoint", - "name", - "serial_port_enabled", - "ssh_public_key", - "zone" - ], - "sectionRelativeRepoPath": "ycloud_instance_metadata", - "githubUrl": "https://github.com/fleetdm/fleet/new/main/schema?filename=tables%2Fycloud_instance_metadata.yml&value=name%3A%20ycloud_instance_metadata%0Adescription%3A%20%7C-%20%23%20(required)%20string%20-%20The%20description%20for%20this%20table.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%23%20Add%20description%20here%0Aexamples%3A%20%7C-%20%23%20(optional)%20string%20-%20An%20example%20query%20for%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown%0A%09%23%20Add%20examples%20here%0Anotes%3A%20%7C-%20%23%20(optional)%20string%20-%20Notes%20about%20this%20table.%20Note%3A%20This%20field%20supports%20Markdown.%0A%09%23%20Add%20notes%20here%0Acolumns%3A%20%23%20(required)%0A%09-%20name%3A%20%23%20(required)%20string%20-%20The%20name%20of%20the%20column%0A%09%20%20description%3A%20%23%20(required)%20string%20-%20The%20column's%20description.%20Note%3A%20this%20field%20supports%20Markdown%0A%09%20%20type%3A%20%23%20(required)%20string%20-%20the%20column's%20data%20type%0A%09%20%20required%3A%20%23%20(required)%20boolean%20-%20whether%20or%20not%20this%20column%20is%20required%20to%20query%20this%20table." - }, - { - "url": "/tables/yum_sources", - "title": "yum_sources", - "htmlId": "table--yumsources--866cfa7193", - "evented": false, - "platforms": [ - "linux" - ], - "keywordsForSyntaxHighlighting": [ - "yum_sources", - "baseurl", - "enabled", - "gpgcheck", - "gpgkey", - "mirrorlist", - "name", - "pid_with_namespace" - ], - "sectionRelativeRepoPath": "yum_sources", - "githubUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/yum_sources.yml" - } - ], - "rituals": { - "handbook/demand/demand.rituals.yml": [ - { - "task": "Refresh event calendar", - "startedOn": "2023-12-31", - "frequency": "Quarterly", - "description": "https://fleetdm.com/handbook/demand#refresh-event-calendar", - "moreInfoUrl": "https://fleetdm.com/handbook/demand#refresh-event-calendar", - "dri": "Drew-P-drawers" - }, - { - "task": "Prioritize for next sprint", - "startedOn": "2023-09-04", - "frequency": "Triweekly", - "description": "Using your departmental kanban board, prioritize and finalize next sprint's goals for your team by draging the appropriate issues to the top of the 'Not yet' column.", - "moreInfoUrl": "https://fleetdm.com/handbook/company/why-this-way#why-make-work-visible", - "dri": "mikermcneil", - "autoIssue": { - "labels": [ - "#g-demand" - ], - "repo": "confidential" - } - }, - { - "task": "Settle event strategy", - "startedOn": "2024-01-02", - "frequency": "Quarterly (first Tuesday)", - "description": "https://fleetdm.com/handbook/demand#settle-event-strategy", - "moreInfoUrl": "https://fleetdm.com/handbook/demand#settle-event-strategy", - "dri": "Drew-P-drawers" - }, - { - "task": "🫧 Pipeline sync", - "startedOn": "2024-08-29", - "frequency": "Weekly", - "description": "Allign with CRO and AEs on pipeline processes and incoming leads", - "moreInfoUrl": "", - "dri": "Drew-P-drawers" - }, - { - "task": "Optimize ads", - "startedOn": "2024-02-26", - "frequency": "Weekly", - "description": "Remove all but the top 5 perfoming ads in each evergreen campaign. Make sure ABM campaigns are using top performing evergreen ads.", - "moreInfoUrl": "https://fleetdm.com/handbook/demand#optimize-ads-through-experimentation", - "dri": "Drew-P-drawers" - }, - { - "task": "Process pending swag requests from the website", - "startedOn": "2023-09-20", - "frequency": "Weekly", - "description": "Complete draft orders.", - "moreInfoUrl": "https://fleetdm.com/handbook/demand#process-pending-swag-requests-from-the-website", - "dri": "Drew-P-drawers" - }, - { - "task": "Engage with the community", - "startedOn": "2023-09-20", - "frequency": "Daily", - "description": "Find relevant conversations with the community and contribute", - "moreInfoUrl": "https://fleetdm.com/handbook/demand#engage-with-the-community", - "dri": "Drew-P-drawers" - }, - { - "task": "Publish ☁️🌈 Sprint demos", - "startedOn": "2023-11-03", - "frequency": "Triweekly", - "description": "Every release cycle, upload the ☁️🌈 Sprint demos video to YouTube", - "moreInfoUrl": "https://fleetdm.com/handbook/demand#upload-to-youtube", - "dri": "Drew-P-drawers" - }, - { - "task": "Measure intent signals", - "startedOn": "2024-08-09", - "frequency": "Daily", - "description": "Measure intent signals and update SalesForce", - "moreInfoUrl": "https://fleetdm.com/handbook/demand#measure-intent-signals", - "dri": "Drew-P-drawers" - }, - { - "task": "Research accounts", - "startedOn": "2024-08-09", - "frequency": "Daily", - "description": "Research SalesForce accounts and begin ABM ads", - "moreInfoUrl": "https://fleetdm.com/handbook/demand#warm-up-actions", - "dri": "Drew-P-drawers" - } - ], - "handbook/customer-success/customer-success.rituals.yml": [ - { - "task": "Prioritize for next sprint", - "startedOn": "2023-09-04", - "frequency": "Triweekly", - "description": "Using your departmental kanban board, prioritize and finalize next sprint's goals for your team by draging the appropriate issues to the top of the 'Not yet' column.", - "moreInfoUrl": "https://fleetdm.com/handbook/company/why-this-way#why-make-work-visible", - "dri": "zayhanlon", - "autoIssue": { - "labels": [ - "#g-customer-success" - ], - "repo": "confidential" - } - }, - { - "task": "Process new requests", - "startedOn": "2023-09-04", - "frequency": "Daily", - "description": "Prioritize all new requests including issues and PRs within one business day.", - "moreInfoUrl": "https://fleetdm.com/handbook/company/communications#process-new-requests", - "dri": "zayhanlon" - }, - { - "task": "Overnight customer feedback", - "startedOn": "2024-02-08", - "frequency": "Daily", - "description": "Respond to messages and alerts", - "moreInfoUrl": "https://fleetdm.com/handbook/customer-success#respond-to-messages-and-alerts", - "dri": "ksatter" - }, - { - "task": "Monitor customer Slack channels ", - "startedOn": "2024-02-08", - "frequency": "Daily", - "description": "Continuously monitor Slack for customer feedback, feature requests, reported bugs, etc., and respond in less than an hour.", - "moreInfoUrl": "https://fleetdm.com/handbook/company/communications#customer-support-service-level-agreements-slas", - "dri": "ksatter" - }, - { - "task": "Follow-up on unresolved customer questions and concerns", - "startedOn": "2024-02-08", - "frequency": "Daily", - "description": "Follow-up with and tag appropriate personnel on customer issues and bugs in progress and items that remain unresolved.", - "moreInfoUrl": "https://fleetdm.com/handbook/company/communications#customer-support-service-level-agreements-slas", - "dri": "ksatter" - }, - { - "task": "Prepare for customer voice", - "startedOn": "2024-02-23", - "frequency": "Weekly", - "description": "Prepare and review the health and latest updates from Fleet's key customers and active proof of concepts (POCs).", - "moreInfoUrl": "", - "dri": "patagonia121" - }, - { - "task": "Prepare customer requests for feature fest", - "startedOn": "2024-02-12", - "frequency": "Triweekly", - "description": "Check-in before the 🗣️ Product Feature Requests meeting to make sure that all information necessary has been gathered before presenting customer requests and feedback to the Product team.", - "moreInfoUrl": "", - "dri": "nonpunctual" - }, - { - "task": "Present customer requests at feature fest", - "startedOn": "2024-02-15", - "frequency": "Triweekly", - "description": "Present and advocate for requests and ideas brought to Fleet's attention by customers that are interesting from a product perspective.", - "moreInfoUrl": "", - "dri": "nonpunctual" - }, - { - "task": "Communicate release notes to stakeholders", - "startedOn": "2024-02-21", - "frequency": "Triweekly", - "description": "Update customers on new features and resolved bugs in an upcoming release.", - "moreInfoUrl": "", - "dri": "patagonia121" - }, - { - "task": "Upgrade Managed Cloud", - "startedOn": "2024-02-08", - "frequency": "Weekly", - "description": "Upgrade each Managed Cloud instance to the latest version of Fleet", - "moreInfoUrl": "https://github.com/fleetdm/fleet/releases", - "dri": "rfairburn" - } - ], - "handbook/digital-experience/digital-experience.rituals.yml": [ - { - "task": "Complete Digital Experience KPIs", - "startedOn": "2024-08-30", - "frequency": "Weekly", - "description": "Complete Digital Experience KPIs for this week", - "moreInfoUrl": "https://docs.google.com/spreadsheets/d/1Hso0LxqwrRVINCyW_n436bNHmoqhoLhC8bcbvLPOs9A/edit?gid=0#gid=0&range=DB1", - "dri": "SFriendLee", - "autoIssue": { - "labels": [ - "#g-digital-experience" - ], - "repo": "fleet" - } - }, - { - "task": "Prep 1:1s for OKR planning", - "startedOn": "2024-09-09", - "frequency": "Monthly", - "description": "Add ”DISCUSS: Mike: Expectations of OKR planning“ to each e-group member's 1:1 document", - "moreInfoUrl": "https://docs.google.com/spreadsheets/d/1Hso0LxqwrRVINCyW_n436bNHmoqhoLhC8bcbvLPOs9A/edit", - "dri": "SFriendLee", - "autoIssue": { - "labels": [ - "#g-digital-experience" - ], - "repo": "fleet" - } - }, - { - "task": "Check browser compatibility for fleetdm.com", - "startedOn": "2024-03-06", - "frequency": "Monthly", - "description": "Use Browserstack to manually QA pages on fleetdm.com in each of the earliest supported browser versions", - "moreInfoUrl": "https://fleetdm.com/handbook/digital-experience#check-browser-compatibility-for-fleetdm-com", - "dri": "eashaw", - "autoIssue": { - "labels": [ - "#g-digital-experience" - ], - "repo": "fleet" - } - }, - { - "task": "Regenerate messaging framework", - "startedOn": "2024-07-15", - "frequency": "Quarterly", - "description": "Run through the entire website in `?utm_content=clear` mode and build a fresh outline of the headings to make sure they all still make sense.", - "moreInfoUrl": "", - "dri": "mike-j-thomas" - }, - { - "task": "Check brand fronts are up to date", - "startedOn": "2024-08-01", - "frequency": "Quarterly", - "description": "Check all brand fronts for consistancy and update as needed with the current product pitch and graphics.", - "moreInfoUrl": "https://fleetdm.com/handbook/digital-experience#update-a-company-brand-front", - "dri": "mike-j-thomas" - }, - { - "task": "Check production dependencies of fleetdm.com", - "startedOn": "2023-11-10", - "frequency": "Weekly", - "description": "Check for vulnerabilities on the production dependencies of fleetdm.com.", - "moreInfoUrl": "https://fleetdm.com/handbook/digital-experience#check-production-dependencies-of-fleetdm-com", - "dri": "eashaw", - "autoIssue": { - "labels": [ - "#g-digital-experience" - ], - "repo": "fleet" - } - }, - { - "task": "Check osquery Slack invitation", - "startedOn": "2023-11-10", - "frequency": "Monthly", - "description": "Check the osquery Slack invitation that is linked to from Fleet and the Fleet website to make sure it is valid.", - "moreInfoUrl": "https://fleetdm.com/slack", - "dri": "eashaw", - "autoIssue": { - "labels": [ - "#g-digital-experience" - ], - "repo": "fleet" - } - }, - { - "task": "Prepare for CEO office minutes", - "startedOn": "2023-12-18", - "frequency": "Daily", - "description": "Prepare the CEO office minutes calendar event and meeting agenda", - "moreInfoUrl": "https://fleetdm.com/handbook/digital-experience#prepare-for-ceo-office-minutes", - "dri": "SFriendLee" - }, - { - "task": "Prioritize for next sprint", - "startedOn": "2023-08-09", - "frequency": "Triweekly", - "description": "Using your departmental kanban board, prioritize and finalize next sprint's goals for your team by draging the appropriate issues to the top of the 'Not yet' column.", - "moreInfoUrl": "https://fleetdm.com/handbook/company/why-this-way#why-make-work-visible", - "dri": "sampfluger88", - "autoIssue": { - "labels": [ - "#g-digital-experience" - ], - "repo": "confidential" - } - }, - { - "task": "Process the CEO's inbox", - "startedOn": "2023-07-29", - "frequency": "Daily ⏰", - "description": "Process the CEO's inbox", - "moreInfoUrl": "https://fleetdm.com/handbook/digital-experience#process-the-ceos-email", - "dri": "SFriendLee" - }, - { - "task": "Process all \"New requests\" on the #g-digital-experience kanban board", - "startedOn": "2023-07-29", - "frequency": "Daily ⏰", - "description": "Process and prioritize all new issues and PRs", - "moreInfoUrl": "https://fleetdm.com/handbook/digital-experience#process-new-requests-from-the-g-ceo-kanban-board", - "dri": "sampfluger88" - }, - { - "task": "Process the CEO's calendar", - "startedOn": "2023-07-29", - "frequency": "Daily ⏰", - "description": "Process the CEO's calendar", - "moreInfoUrl": "https://fleetdm.com/handbook/digital-experience#process-the-ceos-calendar", - "dri": "SFriendLee" - }, - { - "task": "Send weekly update", - "startedOn": "2023-09-15", - "frequency": "Weekly", - "description": "Send weekly update", - "moreInfoUrl": "https://fleetdm.com/handbook/digital-experience#send-the-weekly-update", - "dri": "SFriendLee", - "autoIssue": { - "labels": [ - "#g-digital-experience" - ], - "repo": "confidential" - } - }, - { - "task": "Process and backup E-group agenda", - "startedOn": "2023-09-20", - "frequency": "Weekly", - "description": "Process and backup E-group agenda", - "moreInfoUrl": "https://fleetdm.com/handbook/digital-experience#process-and-backup-sid-agenda", - "dri": "SFriendLee", - "autoIssue": { - "labels": [ - "#g-digital-experience" - ], - "repo": "confidential" - } - }, - { - "task": "Process and backup Sid agenda", - "startedOn": "2023-09-25", - "frequency": "Monthly", - "description": "Process and backup Sid agenda", - "moreInfoUrl": "https://fleetdm.com/handbook/digital-experience#process-and-backup-e-group-agenda", - "dri": "SFriendLee", - "autoIssue": { - "labels": [ - "#g-digital-experience" - ], - "repo": "confidential" - } - }, - { - "task": "Share recording of all hands meeting", - "startedOn": "2023-07-01", - "frequency": "Monthly", - "description": "Sharing the all hands recording", - "moreInfoUrl": "https://fleetdm.com/handbook/digital-experience#share-recording-of-all-hands-meeting", - "dri": "SFriendLee", - "autoIssue": { - "labels": [ - "#g-digital-experience" - ], - "repo": "confidential" - } - }, - { - "task": "Prepare all hands deck", - "startedOn": "2023-07-01", - "frequency": "Monthly", - "description": "Preparing the all hands deck", - "moreInfoUrl": "https://fleetdm.com/handbook/digital-experience#preparing-for-the-all-hands", - "dri": "sampfluger88", - "autoIssue": { - "labels": [ - "#g-digital-experience" - ], - "repo": "confidential" - } - }, - { - "task": "Prepare board deck", - "startedOn": "2023-09-25", - "frequency": "Quarterly", - "description": "Prepare slide deck for the next board meeting", - "dri": "sampfluger88" - }, - { - "task": "Process CEO GitHub review requests, mentions, and outstanding PRs", - "startedOn": "2023-07-29", - "frequency": "Daily", - "description": "Filter all action items from CEO's GitHub notifications", - "dri": "SFriendLee" - }, - { - "task": "Check LinkedIn for unread messages", - "startedOn": "2023-09-25", - "frequency": "Daily", - "description": "Prevent connections from slipping through the cracks", - "moreInfoUrl": "https://fleetdm.com/handbook/digital-experience#check-linkedin-for-unread-messages", - "dri": "SFriendLee" - }, - { - "task": "Downgrade unused license seats", - "startedOn": "2024-03-31", - "frequency": "Quarterly", - "description": "Downgrade unused or questionable license seats on the first Wednesday of every quarter", - "moreInfoUrl": "https://fleetdm.com/handbook/digital-experience#downgrade-an-unused-license-seat", - "dri": "sampfluger88" - }, - { - "task": "Communicate Fleet's potential energy to stakeholders", - "startedOn": "2024-05-01", - "frequency": "Monthly", - "description": "Via hand or automation, send a monthly update email to all investors that hold 4% equity or greater in Fleet who have opted in to receive emails on the company's progress.", - "moreInfoUrl": "https://fleetdm.com/handbook/digital-experience#communicate-fleets-potential-energy-to-stakeholders", - "dri": "sampfluger88", - "autoIssue": { - "labels": [ - "#g-digital-experience" - ], - "repo": "confidential" - } - }, - { - "task": "Vanta check", - "startedOn": "2024-04-01", - "frequency": "Monthly", - "description": "Look for any new actions in Vanta due in the upcoming months and create issues to ensure they're done on time.", - "moreInfoUrl": null, - "dri": "sampfluger88", - "autoIssue": { - "labels": [ - "#g-digital-experience" - ], - "repo": "confidential" - } - }, - { - "task": "Recognize and benchmark workiversaries", - "startedOn": "2024-07-15", - "frequency": "Bimonthly", - "description": "Identify workiversaries coming up in the next two months and follow the steps to ensure they're recognized and benchmarked", - "moreInfoUrl": "https://fleetdm.com/handbook/digital-experience#recognize-employee-workiversaries", - "dri": "sampfluger88" - }, - { - "task": "Quarterly grants", - "startedOn": "2024-02-01", - "frequency": "Quarterly", - "description": "Create the equity grants GitHub issue and walk through the steps.", - "moreInfoUrl": "https://fleetdm.com/handbook/digital-experience#grant-equity", - "dri": "hollidayn" - }, - { - "task": "Change password of \"Integrations admin\" Salesforce account", - "startedOn": "2024-09-10", - "frequency": "Quarterly", - "description": "Log into the \"Integrations admin\" account in Salesforce and change the password to prevent a password change being required by Salesforce.", - "moreInfoUrl": "https://fleetdm.com/handbook/digital-experience#change-the-integrations-admin-salesforce-account-password", - "dri": "eashaw" - } - ], - "handbook/finance/finance.rituals.yml": [ - { - "task": "Communicate the status of customer financial actions", - "startedOn": "2024-02-12", - "frequency": "Weekly", - "description": "At the start of every week, check the Salesforce reports for past due invoices, non-invoiced opportunities, and past due renewals. Report findings to in the `#g-sales` channel.", - "moreInfoUrl": "https://fleetdm.com/handbook/finance#communicate-the-status-of-customer-financial-actions", - "dri": "ireedy", - "autoIssue": { - "labels": [ - "#g-finance" - ], - "repo": "confidential" - } - }, - { - "task": "AP invoice monitoring", - "startedOn": "2024-04-01", - "frequency": "Weekly", - "description": "Look for new accounts payable invoices and make sure that Fleet's suppliers are paid.", - "moreInfoUrl": "https://fleetdm.com/handbook/finance#process-a-new-vendor-invoice", - "dri": "ireedy", - "autoIssue": { - "labels": [ - "#g-finance" - ], - "repo": "confidential" - } - }, - { - "task": "Complete Finance KPI inputs", - "startedOn": "2024-02-16", - "frequency": "Weekly", - "description": "Create the weekly team KPI issue, complete the finance update.", - "moreInfoUrl": "https://fleetdm.com/handbook/finance#update-weekly-kpis", - "dri": "ireedy", - "autoIssue": { - "labels": [ - "#g-finance" - ], - "repo": "confidential" - } - }, - { - "task": "Key review prep", - "startedOn": "2024-02-14", - "frequency": "Triweekly", - "description": "Prepare for this sprint's Key review meeting.", - "moreInfoUrl": "https://fleetdm.com/handbook/company/leadership#key-reviews", - "dri": "jostableford", - "autoIssue": { - "labels": [ - "#g-finance" - ], - "repo": "confidential" - } - }, - { - "task": "Prioritize for next sprint", - "startedOn": "2023-08-09", - "frequency": "Triweekly", - "description": "Using your departmental kanban board, prioritize and finalize next sprint's goals for your team by draging the appropriate issues to the top of the 'Not yet' column.", - "moreInfoUrl": "https://fleetdm.com/handbook/company/why-this-way#why-make-work-visible", - "dri": "jostableford", - "autoIssue": { - "labels": [ - "#g-finance" - ], - "repo": "confidential" - } - }, - { - "task": "Reconcile monthly recurring expenses", - "startedOn": "2024-02-28", - "frequency": "Monthly", - "description": "Each month, update the inputs in “The numbers” spreadsheet to reflect the actuals for recurring non-personnel spend, and identify any unexpected increase or decrease in spend.", - "moreInfoUrl": "https://fleetdm.com/handbook/finance#reconcile-monthly-recurring-expenses", - "dri": "jostableford", - "autoIssue": { - "labels": [ - "#g-finance" - ], - "repo": "confidential" - } - }, - { - "task": "Monthly accounting", - "startedOn": "2024-02-28", - "frequency": "Monthly", - "description": "Create the monthly close GitHub issue and walk through the steps. This process includes fulfilling the monthly reporting requirement for SVB.", - "moreInfoUrl": "https://fleetdm.com/handbook/finance#process-monthly-accounting", - "dri": "ireedy", - "autoIssue": { - "labels": [ - "#g-finance" - ], - "repo": "confidential" - } - }, - { - "task": "Run regular payroll", - "startedOn": "2024-02-24", - "frequency": "Monthly", - "description": "Verify auto-populated payroll for all full time employees is accurate, and approve for processing.", - "moreInfoUrl": "https://fleetdm.com/handbook/finance#run-payroll", - "dri": "jostableford", - "autoIssue": { - "labels": [ - "#g-finance" - ], - "repo": "confidential" - } - }, - { - "task": "Monthly mail review", - "startedOn": "2024-04-15", - "frequency": "Monthly", - "description": "Review and clear mail incurring storage fees", - "moreInfoUrl": null, - "dri": "ireedy", - "autoIssue": { - "labels": [ - "#g-finance" - ], - "repo": "confidential" - } - }, - { - "task": "Run US contractor payroll", - "startedOn": "2024-02-28", - "frequency": "Monthly", - "description": "Manually process US contractor payroll by verifying and syncing time contractor worked, then processing payment.", - "moreInfoUrl": "https://fleetdm.com/handbook/finance#run-us-contractor-payroll", - "dri": "jostableford", - "autoIssue": { - "labels": [ - "#g-finance" - ], - "repo": "confidential" - } - }, - { - "task": "Run US commission payroll", - "startedOn": "2024-01-31", - "frequency": "Monthly", - "description": "Verify closed-won deal amounts, use commission calculators to determine commissions owed, and process payroll.", - "moreInfoUrl": "https://fleetdm.com/handbook/finance#run-us-commission-payroll", - "dri": "jostableford", - "autoIssue": { - "labels": [ - "#g-finance" - ], - "repo": "confidential" - } - }, - { - "task": "Run bonus payroll", - "startedOn": "2024-01-31", - "frequency": "Quarterly", - "description": "Verify completion of any objective or outcome based bonus plans, and process payroll.", - "moreInfoUrl": "https://fleetdm.com/handbook/finance#run-us-commission-payroll", - "dri": "jostableford" - }, - { - "task": "Review state filings for the previous quarter", - "startedOn": "2024-07-19", - "frequency": "Quarterly", - "description": "Verify that state filings have been successfully submitted for the previous quarter", - "moreInfoUrl": "https://fleetdm.com/handbook/finance#review-state-employment-tax-filings-for-the-previous-quarter", - "dri": "ireedy" - }, - { - "task": "Investor reporting", - "startedOn": "2024-03-31", - "frequency": "Quarterly", - "description": "Provide updated metrics for CRV in Chronograph.", - "moreInfoUrl": "https://fleetdm.com/handbook/finance#report-quarterly-numbers-in-chronograph", - "dri": "ireedy" - }, - { - "task": "Quartlery finance check", - "startedOn": "2024-03-31", - "frequency": "Quarterly", - "description": "Every quarter, we check Quickbooks Online (QBO) for discrepancies and follow up with accounting providers for any quirks found.", - "moreInfoUrl": "https://fleetdm.com/handbook/finance#check-finances-for-quirks", - "dri": "jostableford" - }, - { - "task": "Deliver annual report for venture line", - "startedOn": "2024-12-01", - "frequency": "Annually", - "description": "Within 60 days of the new year, provide financial statements to SVB, along with board-approved projections for the new year", - "moreInfoUrl": "https://fleetdm.com/handbook/finance#deliver-annual-report-for-venture-line", - "dri": "jostableford" - }, - { - "task": "Tax preparation", - "startedOn": "2024-02-01", - "frequency": "Annually", - "description": "Provide information to tax team with Deloitte and assist with filing and paying state and federal returns", - "moreInfoUrl": null, - "dri": "jostableford" - } - ], - "handbook/engineering/engineering.rituals.yml": [ - { - "task": "Pull request review", - "startedOn": "2023-08-09", - "frequency": "Daily", - "description": "Engineers go through pull requests for which their review has been requested.", - "moreInfoUrl": "https://fleetdm.com/handbook/company/why-this-way#why-make-work-visible", - "dri": "lukeheath" - }, - { - "task": "Engineering group discussions", - "startedOn": "2023-08-09", - "frequency": "Daily", - "description": "Engineers go through pull requests for which their review has been requested.", - "moreInfoUrl": null, - "dri": "lukeheath" - }, - { - "task": "Oncall handoff", - "startedOn": "2023-08-09", - "frequency": "Weekly", - "description": "Hand off the oncall engineering responsibilities to the next oncall engineer.", - "moreInfoUrl": null, - "dri": "lukeheath" - }, - { - "task": "Vulnerability alerts (fleetdm.com)", - "startedOn": "2023-08-09", - "frequency": "Weekly", - "description": "Review and remediate or dismiss vulnerability alerts for the fleetdm.com codebase on GitHub.", - "moreInfoUrl": "https://github.com/fleetdm/fleet/security", - "dri": "eashaw" - }, - { - "task": "Vulnerability alerts (frontend)", - "startedOn": "2023-08-09", - "frequency": "Weekly", - "description": "Review and remediate or dismiss vulnerability alerts for the Fleet frontend codebase (and related JS) on GitHub.", - "moreInfoUrl": "https://github.com/fleetdm/fleet/security", - "dri": "lukeheath" - }, - { - "task": "Vulnerability alerts (backend)", - "startedOn": "2023-08-09", - "frequency": "Weekly", - "description": "Review and remediate or dismiss vulnerability alerts for the Fleet backend codebase (and all Go code) on GitHub.", - "moreInfoUrl": "https://github.com/fleetdm/fleet/security", - "dri": "lukeheath" - }, - { - "task": "Release candidate ritual", - "startedOn": "2023-08-09", - "frequency": "Triweekly", - "description": "Go through the process of creating a release candidate.", - "moreInfoUrl": "https://github.com/fleetdm/fleet/blob/main/tools/release/README.md#minor-release-typically-end-of-sprint", - "dri": "lukeheath" - }, - { - "task": "Release ritual", - "startedOn": "2023-08-09", - "frequency": "Triweekly", - "description": "Go through the process of releasing the next iteration of Fleet.", - "moreInfoUrl": "https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Releasing-Fleet.md", - "dri": "lukeheath" - }, - { - "task": "Create patch release branch", - "startedOn": "2023-08-09", - "frequency": "Every patch release", - "description": "Go through the process of creating a patch release branch, cherry picking commits, and pushing the branch to github.com/fleetdm/fleet.", - "moreInfoUrl": "https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Releasing-Fleet.md#patch-releases", - "dri": "lukeheath" - }, - { - "task": "Bug review", - "startedOn": "2023-08-09", - "frequency": "Weekly", - "description": "Review bugs that are in QA's inbox.", - "moreInfoUrl": "https://www.fleetdm.com/handbook/company/product-groups#inbox", - "dri": "xpkoala" - }, - { - "task": "QA report", - "startedOn": "2023-08-09", - "frequency": "Triweekly", - "description": "Every release cycle, on the Monday of release week, update the DRI for the release ritual on status of testing.", - "moreInfoUrl": null, - "dri": "xpkoala" - }, - { - "task": "Release QA", - "startedOn": "2023-08-09", - "frequency": "Triweekly", - "description": "Every release cycle, by end of day Friday of release week, move all issues to the ”✅ Ready for release” column on the #g-mdm and #g-endpoint-ops sprint boards.", - "moreInfoUrl": null, - "dri": "xpkoala" - }, - { - "task": "Check ongoing events", - "startedOn": "2024-02-09", - "frequency": "Daily", - "description": "Check event issues and complete steps.", - "moreInfoUrl": "https://fleetdm.com/handbook/engineering#book-an-event", - "dri": "spokanemac" - } - ], - "handbook/sales/sales.rituals.yml": [ - { - "task": "Close leads contacted ≥7 days ago", - "startedOn": "2024-07-05", - "frequency": "Daily", - "description": "Close all of your leads in the 'Attempted to contact' stage and which have been there for 7 or more days. If follow-up is appropriate, and won't be bothersome, it can be done after closing the lead. (A new lead can always be opened for the contact later.)", - "moreInfoUrl": "", - "dri": "Every AE" - }, - { - "task": "Prioritize for next sprint", - "startedOn": "2023-09-04", - "frequency": "Triweekly", - "description": "Using your departmental kanban board, prioritize and finalize next sprint's goals for your team by draging the appropriate issues to the top of the 'Not yet' column.", - "moreInfoUrl": "https://fleetdm.com/handbook/company/why-this-way#why-make-work-visible", - "dri": "alexmitchelliii", - "autoIssue": { - "labels": [ - "#g-sales" - ], - "repo": "confidential" - } - }, - { - "task": "g-sales standup", - "startedOn": "2023-09-04", - "frequency": "Daily", - "description": "Review progress on priorities for Sprint. Discuss previous day accomplishments, goals for today and any blockers.", - "moreInfoUrl": "https://fleetdm.com/handbook/company/why-this-way#why-make-work-visible", - "dri": "alexmitchelliii" - }, - { - "task": "Opportunity pipeline review", - "startedOn": "2023-09-04", - "frequency": "Weekly", - "description": "Review status of sales opportunities and discuss next steps.", - "moreInfoUrl": "https://fleetdm.com/handbook/customers#review-rep-activity", - "dri": "alexmitchelliii", - "autoIssue": { - "labels": [ - "#g-sales" - ], - "repo": "confidential" - } - }, - { - "task": "Review rep activity", - "startedOn": "2023-09-18", - "frequency": "Monthly", - "description": "https://fleetdm.com/handbook/customers#review-rep-activity", - "moreInfoUrl": "https://fleetdm.com/handbook/customers#review-rep-activity", - "dri": "alexmitchelliii" - } - ], - "handbook/product-design/product-design.rituals.yml": [ - { - "task": "Design sprint review", - "startedOn": "2024-03-07", - "frequency": "Triweekly", - "description": "Clear out the drafting board of all issues that are not estimated but leave the items we want to take in the next sprint on the drafting board. Record the number of dropped stories for KPIs (all user stories that did not meet the 3 week drafting timeline).", - "moreInfoUrl": null, - "dri": "noahtalerman" - }, - { - "task": "🎁 Feature fest", - "startedOn": "2024-03-07", - "frequency": "Triweekly", - "description": "We make a decision regarding which customer and community feature requests can be committed to in the next six weeks.", - "moreInfoUrl": "https://fleetdm.com/handbook/company/product-groups#feature-fest", - "dri": "noahtalerman" - }, - { - "task": "Design sprint kickoff", - "startedOn": "2024-03-07", - "frequency": "Triweekly", - "description": "Add stories prioritized during Feature fest to Drafting board, assign stories to product designers, and align on priorities.", - "moreInfoUrl": null, - "dri": "noahtalerman" - }, - { - "task": "Sprint kickoff review", - "startedOn": "2024-03-07", - "frequency": "Triweekly", - "description": "Identify stories that did not make it into this sprint and remove them from the board. Notify relevant requesters/stakeholders. Ensure bugs have been effectively prioritized across teams. Recommend highlights for next release notes. Record the number of drops for KPI reporting. Consider product group staffing. Are we scheduling what we prioritized? Did we finish what we scheduled in the sprint? (Look at org chart.)", - "moreInfoUrl": null, - "dri": "noahtalerman" - }, - { - "task": "🦢🗣 Design review", - "startedOn": "2024-03-07", - "frequency": "Daily", - "description": "On Mondays, contributors present wireframes in 'Feedback' mode and anyone can give feedback. 'Final review' mode during all other days and only Head of Product Design + CTO + Product Designers give feedback.", - "moreInfoUrl": "https://fleetdm.com/handbook/company/product-groups#design-reviews", - "dri": "noahtalerman" - }, - { - "task": "🦢🔄 Product design sync", - "startedOn": "2023-07-11", - "frequency": "Weekly", - "description": "Weekly time to chat about product design work (design reviews, conventions & best practices, using Figma, etc.)", - "moreInfoUrl": "https://docs.google.com/document/d/1GDEcXuTUjHI2CD9Jqega_GyF9DL6-PBmcyJpj55Lmos/edit", - "dri": "noahtalerman" - }, - { - "task": "🦢🗣 Product office hours", - "startedOn": "2023-07-11", - "frequency": "Weekly", - "description": "Head of Product Design + any other contributors who would like to attend. 30 minutes reserved to talk about any product.", - "moreInfoUrl": "https://docs.google.com/document/d/1Znyp2a9qcM9JdYHrzLudvcPwEdhnCg7RiKi22s8yGWw/edit", - "dri": "noahtalerman" - }, - { - "task": "Maintenance", - "startedOn": "2024-03-01", - "frequency": "Weekly", - "description": "Head of Product Design checks the latest versions of relevant platforms, updates the maintenance tracker, and notifies the #g-mdm and #g-endpoint-ops Slack channel.", - "moreInfoUrl": null, - "dri": "noahtalerman" - }, - { - "task": "Product confirm and celebrate", - "startedOn": "2024-02-27", - "frequency": "Weekly", - "description": "Review user stories we shipped but haven't closed/ Confirm all the loose ends are tied up: docs, internal and external comms, guides, pricing page, transparency page, user permissions.", - "moreInfoUrl": null, - "dri": "noahtalerman" - }, - { - "task": "Pre-sprint prioritization", - "startedOn": "2024-02-27", - "frequency": "Triweekly", - "description": "Discuss what stories weren't completed in the previous sprint. Record the number of stories in KPIs. Align on priorities for upcoming sprint.", - "dri": "noahtalerman" - } - ] - }, - "testimonials": [ - { - "quote": "Yes Sir. Great tools for the everyday open-source geeks 💯", - "quoteAuthorName": "Alvaro Gutierrez", - "quoteAuthorProfileImageFilename": "testimonial-authour-alvaro-gutierrez-100x100@2x.png", - "quoteLinkUrl": "https://www.linkedin.com/in/aantoniogutierrez/", - "quoteAuthorJobTitle": "Technology Evangelist", - "productCategories": [ - "Endpoint operations" - ] - }, - { - "quote": "Fleet / osquery are some of my favorite open source detection tooling.", - "quoteAuthorName": "Joe Pistone", - "quoteAuthorProfileImageFilename": "testimonial-author-joe-pistone-100x100@2x.png", - "quoteLinkUrl": "https://www.linkedin.com/in/josephpistone/", - "quoteAuthorJobTitle": "Manager, Security Operations", - "productCategories": [ - "Endpoint operations" - ] - }, - { - "quote": "I had to answer some really complex questions for a compliance audit, and I was able to do it in about 15 minutes by munging some data together via a few queries into a csv. It took me longer to remember how to use `xsv` than to actually put together the report. If you aren't using osquery in your environment, you should be.", - "quoteAuthorName": "Charles Zaffery", - "quoteAuthorProfileImageFilename": "testimonial-author-charles-zaffery-48x48@2x.png", - "quoteLinkUrl": "https://www.linkedin.com/in/charleszaffery/", - "quoteAuthorJobTitle": "Principle Computer Janitor", - "productCategories": [ - "Vulnerability management" - ] - }, - { - "quote": "The visibility down into the assets covered by the agent is phenomenal. Fleet has become the central source for a lot of things.", - "quoteAuthorName": "Andre Shields", - "quoteAuthorProfileImageFilename": "testimonial-author-andre-shields-48x48@2x.png", - "quoteLinkUrl": "https://www.linkedin.com/in/andre-shields/", - "quoteAuthorJobTitle": "Staff Cybersecurity Engineer, Vulnerability Management", - "youtubeVideoUrl": "https://www.youtube.com/watch?v=siXy9aanOu4", - "productCategories": [ - "Endpoint operations", - "Vulnerability management" - ], - "videoIdForEmbed": "siXy9aanOu4" - }, - { - "quote": "I love the steady and consistent delivery of features that help teams work how they want to work, not how your product dictates they work.", - "quoteImageFilename": "social-proof-logo-atlassian-192x32@2x.png", - "quoteLinkUrl": "https://www.linkedin.com/in/danielgrzelak/", - "quoteAuthorName": "Dan Grzelak", - "quoteAuthorProfileImageFilename": "testimonial-author-daniel-grzelak-48x48@2x.png", - "quoteAuthorJobTitle": "Security Chief of Staff", - "productCategories": [ - "Endpoint operations", - "Vulnerability management", - "Device management" - ], - "imageHeight": 32 - }, - { - "quote": "We can build it exactly the way we want it. Which is just not possible on other platforms.", - "quoteAuthorName": "Austin Anderson", - "quoteAuthorProfileImageFilename": "testimonial-author-austin-anderson-48x48@2x.png", - "quoteAuthorJobTitle": "Cybersecurity team senior manager", - "quoteLinkUrl": "https://www.linkedin.com/in/austin-anderson-73172185/", - "youtubeVideoUrl": "https://www.youtube.com/watch?v=G5Ry_vQPaYc", - "productCategories": [ - "Endpoint operations", - "Vulnerability management" - ], - "videoIdForEmbed": "G5Ry_vQPaYc" - }, - { - "quote": "Exciting. This is a team that listens to feedback.", - "quoteImageFilename": "social-proof-logo-uber-71x32@2x.png", - "quoteLinkUrl": "https://www.linkedin.com/in/eriknicolasgomez/", - "quoteAuthorName": "Erik Gomez", - "quoteAuthorProfileImageFilename": "testimonial-author-erik-gomez-48x48@2x.png", - "quoteAuthorJobTitle": "Staff Client Platform Engineer", - "productCategories": [ - "Endpoint operations", - "Device management" - ], - "imageHeight": 32 - }, - { - "quote": "Context is king for device data, and Fleet provides a way to surface that information to our other teams and partners.", - "quoteAuthorName": "Nick Fohs", - "quoteAuthorProfileImageFilename": "testimonial-author-nick-fohs-24x24@2x.png", - "quoteLinkUrl": "https://www.linkedin.com/in/nickfohs/", - "quoteAuthorJobTitle": "Systems and infrastructure manager", - "youtubeVideoUrl": "https://www.youtube.com/watch?v=fs5ULAR4e4A", - "productCategories": [ - "Endpoint operations", - "Device management", - "Vulnerability management" - ], - "videoIdForEmbed": "fs5ULAR4e4A" - }, - { - "quote": "Keeping up with the latest issues in endpoint security is a never-ending task, because engineers have to regularly ensure every laptop and server is still sufficiently patched and securely configured. The problem is, software vendors release new versions all the time, and no matter how much you lock it down, end users find ways to change things.", - "quoteImageFilename": "social-proof-logo-lyft-47x32@2x.png", - "quoteLinkUrl": "https://www.linkedin.com/in/nwaisman/", - "quoteAuthorName": "Nico Waisman", - "quoteAuthorProfileImageFilename": "testimonial-author-nico-waisman-48x48@2x.png", - "quoteAuthorJobTitle": "CISO of Lyft", - "productCategories": [ - "Endpoint operations", - "Vulnerability management" - ], - "imageHeight": 32 - }, - { - "quote": "Having the freedom to take full advantage of the product is one of the reasons why I always support open-source products with a commercially-backed company, like Fleet.", - "quoteImageFilename": "social-proof-logo-lyft-47x32@2x.png", - "quoteLinkUrl": "https://www.linkedin.com/posts/nwaisman_movingtofleet-activity-7156319785981509632-bk_W", - "quoteAuthorName": "Nico Waisman", - "quoteAuthorProfileImageFilename": "testimonial-author-nico-waisman-48x48@2x.png", - "quoteAuthorJobTitle": "CISO of Lyft", - "productCategories": [ - "Device management" - ], - "imageHeight": 32 - }, - { - "quote": "Fleet has been highly effective for our needs. We appreciate your team for always being so open to hearing our feedback.", - "quoteAuthorName": "Kenny Botelho", - "quoteAuthorProfileImageFilename": "testimonial-author-kenny-botelho-48x48@2x.png", - "quoteAuthorJobTitle": "Client Platform IT Engineer / Leader", - "quoteLinkUrl": "https://www.linkedin.com/in/kennybotelho/", - "productCategories": [ - "Endpoint operations", - "Device management" - ] - }, - { - "quote": "Mad props to how easy making a deploy pkg of the agent was. I wish everyone made stuff that easy.", - "quoteImageFilename": "social-proof-logo-stripe-67x32@2x.png", - "quoteAuthorName": "Wes Whetstone", - "quoteAuthorProfileImageFilename": "testimonial-author-wes-whetstone-48x48@2x.png", - "quoteLinkUrl": "https://www.linkedin.com/in/jckwhet/", - "quoteAuthorJobTitle": "Staff CPE at Stripe", - "productCategories": [ - "Endpoint operations", - "Device management" - ], - "imageHeight": 32 - }, - { - "quote": "Fleet’s come a long way - to now being the top open-source osquery manager.", - "quoteImageFilename": "social-proof-logo-atlassian-192x32@2x.png", - "quoteLinkUrl": "https://www.linkedin.com/in/bshak/", - "quoteAuthorName": "Brendan Shaklovitz", - "quoteAuthorProfileImageFilename": "testimonial-author-brendan-shaklovitz-48x48@2x.png", - "quoteAuthorJobTitle": "Senior SRE", - "productCategories": [ - "Endpoint operations" - ], - "imageHeight": 32 - }, - { - "quote": "It’s great to see the new release of Fleet containing some really cool new features that make osquery much more usable in practical environments. I’m really impressed with the work that Zach Wasserman and the crew are doing at Fleet.", - "quoteImageFilename": "social-proof-logo-osquery-124x32@2x.png", - "quoteLinkUrl": "https://www.linkedin.com/in/marpaia/", - "quoteAuthorName": "Mike Arpaia", - "quoteAuthorProfileImageFilename": "testimonial-author-mike-arpaia-48x48@2x.png", - "quoteAuthorJobTitle": "Creator of osquery", - "productCategories": [ - "Endpoint operations" - ], - "imageHeight": 32 - }, - { - "quote": "Osquery is one of the best tools out there and Fleet makes it even better. Highly recommend it if you want to monitor, detect and investigate threats on a scale and also for infra/sys admin. I have used it on 15k servers and it’s really scalable.", - "quoteImageFilename": "social-proof-logo-salesforce-48x32@2x.png", - "quoteLinkUrl": "https://www.linkedin.com/in/anelshaer/", - "quoteAuthorName": "Ahmed Elshaer", - "quoteAuthorProfileImageFilename": "testimonial-author-ahmed-elshaer-48x48@2x.png", - "quoteAuthorJobTitle": "DFIR, Blue Teaming, SecOps", - "productCategories": [ - "Endpoint operations" - ], - "imageHeight": 32 - }, - { - "quote": "With the power of osquery, you need a scalable & resilient platform to manage your workloads. Fleet is the \"just right\" open-source, enterprise grade solution.", - "quoteImageFilename": "social-proof-logo-comcast-91x32@2x.png", - "quoteLinkUrl": "https://www.linkedin.com/in/abubakar-yousafzai-b7213659/", - "quoteAuthorName": "Abubakar Yousafzai", - "quoteAuthorProfileImageFilename": "testimonial-author-abubakar-yousafzai-48x48@2x.png", - "quoteAuthorJobTitle": "Security Software Development & Engineering", - "productCategories": [ - "Endpoint operations" - ], - "imageHeight": 32 - }, - { - "quote": "One of the best teams out there to go work for and help shape security platforms.", - "quoteImageFilename": "social-proof-logo-deloitte-130x32@2x.png", - "quoteLinkUrl": "https://www.linkedin.com/in/neondhruv/", - "quoteAuthorName": "Dhruv Majumdar", - "quoteAuthorProfileImageFilename": "testimonial-author-dhruv-majumdar-48x48@2x.png", - "quoteAuthorJobTitle": "Director Of Cyber Risk & Advisory", - "productCategories": [ - "Vulnerability management", - "Endpoint operations" - ], - "imageHeight": 32 - }, - { - "quote": "Fleet has such a huge amount of use cases. My goal was to get telemetry on endpoints, but then our IR team, our TBM team, and multiple other folks in security started heavily utilizing the system in ways I didn’t expect. It spread so naturally, even our corporate and infrastructure teams want to run it.", - "quoteAuthorName": "Charles Zaffery", - "quoteLinkUrl": "https://www.linkedin.com/in/charleszaffery/", - "quoteAuthorJobTitle": "Principle computer janitor", - "quoteAuthorProfileImageFilename": "testimonial-author-charles-zaffery-48x48@2x.png", - "youtubeVideoUrl": "https://www.youtube.com/watch?v=nRbZJflWqCo", - "productCategories": [ - "Endpoint operations" - ], - "videoIdForEmbed": "nRbZJflWqCo" - }, - { - "quote": "I don't want one bad actor to brick my fleet, I want them to make a pull request first.", - "quoteAuthorName": "Matt Carr", - "quoteAuthorJobTitle": "CPE manager", - "quoteAuthorProfileImageFilename": "testimonial-author-matt-carr-48x48@2x.png", - "quoteLinkUrl": "https://www.linkedin.com/in/mathewcarr/", - "productCategories": [ - "Device management" - ] - }, - { - "quote": "I wanted an easy way to control osquery configurations, and I wanted to stream data as fast as possible into Snowflake. No other solution jumped out to solve those things except for Fleet.", - "quoteAuthorName": "Tom Larkin", - "quoteAuthorJobTitle": "IT Engineering Manager", - "quoteAuthorProfileImageFilename": "testimonial-author-tom-larkin-48x48@2x.png", - "quoteLinkUrl": "https://www.linkedin.com/in/thlarkin/", - "youtubeVideoUrl": "https://www.youtube.com/watch?v=nkjg_hNe86Q", - "productCategories": [ - "Endpoint operations" - ], - "videoIdForEmbed": "nkjg_hNe86Q" - }, - { - "quote": "Something I really appreciate about working with you guys is that it doesn't feel like I'm talking to a vendor. It actually feels like I'm talking to my team, and I really appreciate it.", - "quoteImageFilename": "social-proof-logo-deloitte-130x32@2x.png", - "quoteLinkUrl": "https://www.linkedin.com/in/cmajumdar/", - "quoteAuthorName": "Chandra Majumdar", - "quoteAuthorProfileImageFilename": "testimonial-author-chandra-majumdar-48x48@2x.png", - "quoteAuthorJobTitle": "Partner - Cyber and Strategic Risk", - "productCategories": [ - "Vulnerability management", - "Endpoint operations" - ], - "imageHeight": 32 - }, - { - "quote": "This is not just production osquery, but actually a way bigger opportunity than even something like Airwatch or Jamf.", - "quoteImageFilename": "logo-flock-safety-907x132@2x.png", - "quoteLinkUrl": "https://www.linkedin.com/in/mrerictan/", - "quoteAuthorName": "Eric Tan", - "quoteAuthorProfileImageFilename": "testimonial-author-eric-tan-99x99@2x.png", - "quoteAuthorJobTitle": "CIO & Chief Security Officer at Flock Safety", - "productCategories": [ - "Device management", - "Endpoint operations" - ], - "imageHeight": 132 - } - ], - "openPositions": [ - { - "jobTitle": "🚀 Software Engineer", - "url": "/handbook/company/open-positions/software-engineer" - }, - { - "jobTitle": "🐋 Account Executive", - "url": "/handbook/company/open-positions/account-executive" - } - ], - "compiledPagePartialsAppPath": "views/partials/built-from-markdown" } } From ad99cbd4997b475642b95c2f68587511d735f04d Mon Sep 17 00:00:00 2001 From: Roberto Dip Date: Thu, 3 Oct 2024 14:49:27 -0300 Subject: [PATCH 057/385] FMA: missing pieces (#22593) # 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 paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes - [ ] If database migrations are included, checked table schema to confirm autoupdate - For database migrations: - [ ] Checked schema for all modified table for columns that will auto-update timestamps during migration. - [ ] Confirmed that updating the timestamps is acceptable, and will not cause unwanted side effects. - [ ] Ensured the correct collation is explicitly set for character columns (`COLLATE utf8mb4_unicode_ci`). - [ ] 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)). --- ee/server/service/maintained_apps.go | 30 +- ee/server/service/software_installers.go | 4 +- frontend/interfaces/package_type.ts | 15 +- .../PackageAdvancedOptions.tsx | 5 + frontend/services/entities/software.ts | 2 + server/datastore/mysql/maintained_apps.go | 4 +- server/fleet/service.go | 2 +- server/mdm/maintainedapps/ingest.go | 40 +- server/mdm/maintainedapps/scripts.go | 507 ++++++++++++++++++ server/mdm/maintainedapps/scripts_test.go | 72 +++ .../scripts/1password_install.golden.sh | 10 + .../scripts/1password_uninstall.golden.sh | 56 ++ .../adobe-acrobat-reader_install.golden.sh | 13 + .../adobe-acrobat-reader_uninstall.golden.sh | 126 +++++ .../scripts/box-drive_install.golden.sh | 8 + .../scripts/box-drive_uninstall.golden.sh | 125 +++++ .../scripts/brave-browser_install.golden.sh | 13 + .../scripts/brave-browser_uninstall.golden.sh | 35 ++ .../scripts/cloudflare-warp_install.golden.sh | 8 + .../cloudflare-warp_uninstall.golden.sh | 122 +++++ .../testdata/scripts/docker_install.golden.sh | 21 + .../scripts/docker_uninstall.golden.sh | 152 ++++++ .../testdata/scripts/figma_install.golden.sh | 10 + .../scripts/figma_uninstall.golden.sh | 35 ++ .../scripts/firefox_install.golden.sh | 13 + .../scripts/firefox_uninstall.golden.sh | 85 +++ .../scripts/google-chrome_install.golden.sh | 13 + .../scripts/google-chrome_uninstall.golden.sh | 102 ++++ .../scripts/microsoft-edge_install.golden.sh | 32 ++ .../microsoft-edge_uninstall.golden.sh | 91 ++++ .../scripts/microsoft-excel_install.golden.sh | 32 ++ .../microsoft-excel_uninstall.golden.sh | 121 +++++ .../scripts/microsoft-teams_install.golden.sh | 32 ++ .../microsoft-teams_uninstall.golden.sh | 141 +++++ .../scripts/microsoft-word_install.golden.sh | 32 ++ .../microsoft-word_uninstall.golden.sh | 120 +++++ .../testdata/scripts/notion_install.golden.sh | 13 + .../scripts/notion_uninstall.golden.sh | 38 ++ .../scripts/postman_install.golden.sh | 10 + .../scripts/postman_uninstall.golden.sh | 38 ++ .../testdata/scripts/slack_install.golden.sh | 13 + .../scripts/slack_uninstall.golden.sh | 82 +++ .../scripts/teamviewer_install.golden.sh | 8 + .../scripts/teamviewer_uninstall.golden.sh | 133 +++++ .../visual-studio-code_install.golden.sh | 10 + .../visual-studio-code_uninstall.golden.sh | 124 +++++ .../scripts/whatsapp_install.golden.sh | 10 + .../scripts/whatsapp_uninstall.golden.sh | 35 ++ .../testdata/scripts/zoom_install.golden.sh | 8 + .../testdata/scripts/zoom_uninstall.golden.sh | 146 +++++ server/service/integration_enterprise_test.go | 6 +- server/service/maintained_apps.go | 14 +- 52 files changed, 2892 insertions(+), 25 deletions(-) create mode 100644 server/mdm/maintainedapps/scripts.go create mode 100644 server/mdm/maintainedapps/scripts_test.go create mode 100644 server/mdm/maintainedapps/testdata/scripts/1password_install.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/1password_uninstall.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/adobe-acrobat-reader_install.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/adobe-acrobat-reader_uninstall.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/box-drive_install.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/box-drive_uninstall.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/brave-browser_install.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/brave-browser_uninstall.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/cloudflare-warp_install.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/cloudflare-warp_uninstall.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/docker_install.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/docker_uninstall.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/figma_install.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/figma_uninstall.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/firefox_install.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/firefox_uninstall.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/google-chrome_install.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/google-chrome_uninstall.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/microsoft-edge_install.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/microsoft-edge_uninstall.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/microsoft-excel_install.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/microsoft-excel_uninstall.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/microsoft-teams_install.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/microsoft-teams_uninstall.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/microsoft-word_install.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/microsoft-word_uninstall.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/notion_install.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/notion_uninstall.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/postman_install.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/postman_uninstall.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/slack_install.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/slack_uninstall.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/teamviewer_install.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/teamviewer_uninstall.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/visual-studio-code_install.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/visual-studio-code_uninstall.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/whatsapp_install.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/whatsapp_uninstall.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/zoom_install.golden.sh create mode 100644 server/mdm/maintainedapps/testdata/scripts/zoom_uninstall.golden.sh diff --git a/ee/server/service/maintained_apps.go b/ee/server/service/maintained_apps.go index 95e5e46ee395..1c5b00347d93 100644 --- a/ee/server/service/maintained_apps.go +++ b/ee/server/service/maintained_apps.go @@ -5,9 +5,13 @@ import ( "context" "crypto/sha256" "encoding/hex" + "net/url" "os" + "path/filepath" + "strings" "time" + "github.com/fleetdm/fleet/v4/pkg/file" "github.com/fleetdm/fleet/v4/pkg/fleethttp" "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" "github.com/fleetdm/fleet/v4/server/contexts/viewer" @@ -15,7 +19,13 @@ import ( "github.com/fleetdm/fleet/v4/server/mdm/maintainedapps" ) -func (svc *Service) AddFleetMaintainedApp(ctx context.Context, teamID *uint, appID uint, installScript, preInstallQuery, postInstallScript string, selfService bool) error { +func (svc *Service) AddFleetMaintainedApp( + ctx context.Context, + teamID *uint, + appID uint, + installScript, preInstallQuery, postInstallScript, uninstallScript string, + selfService bool, +) error { if err := svc.authz.Authorize(ctx, &fleet.SoftwareInstaller{TeamID: teamID}, fleet.ActionWrite); err != nil { return err } @@ -59,6 +69,21 @@ func (svc *Service) AddFleetMaintainedApp(ctx context.Context, teamID *uint, app filename = app.Name } + installScript = file.Dos2UnixNewlines(installScript) + if installScript == "" { + installScript = app.InstallScript + } + + uninstallScript = file.Dos2UnixNewlines(uninstallScript) + if uninstallScript == "" { + uninstallScript = app.UninstallScript + } + + installerURL, err := url.Parse(app.InstallerURL) + if err != nil { + return err + } + installerReader := bytes.NewReader(installerBytes) payload := &fleet.UploadSoftwareInstallerPayload{ InstallerFile: installerReader, @@ -68,6 +93,8 @@ func (svc *Service) AddFleetMaintainedApp(ctx context.Context, teamID *uint, app Version: app.Version, Filename: filename, Platform: string(app.Platform), + Source: "apps", + Extension: strings.TrimPrefix(filepath.Ext(installerURL.Path), "."), BundleIdentifier: app.BundleIdentifier, StorageID: app.SHA256, FleetLibraryAppID: &app.ID, @@ -75,6 +102,7 @@ func (svc *Service) AddFleetMaintainedApp(ctx context.Context, teamID *uint, app PostInstallScript: postInstallScript, SelfService: selfService, InstallScript: installScript, + UninstallScript: uninstallScript, } // Create record in software installers table diff --git a/ee/server/service/software_installers.go b/ee/server/service/software_installers.go index a2dea22138bb..f3d443af5206 100644 --- a/ee/server/service/software_installers.go +++ b/ee/server/service/software_installers.go @@ -104,6 +104,8 @@ func preProcessUninstallScript(payload *fleet.UploadSoftwareInstallerPayload) { // Replace $PACKAGE_ID in the uninstall script with the package ID(s). var packageID string switch payload.Extension { + case "dmg", "zip": + return case "pkg": var sb strings.Builder _, _ = sb.WriteString("(\n") @@ -1515,7 +1517,7 @@ func packageExtensionToPlatform(ext string) string { switch ext { case ".msi", ".exe": requiredPlatform = "windows" - case ".pkg": + case ".pkg", ".dmg", ".zip": requiredPlatform = "darwin" case ".deb", ".rpm": requiredPlatform = "linux" diff --git a/frontend/interfaces/package_type.ts b/frontend/interfaces/package_type.ts index b8b83b93fe9d..e9a467585f4f 100644 --- a/frontend/interfaces/package_type.ts +++ b/frontend/interfaces/package_type.ts @@ -1,4 +1,5 @@ -const unixPackageTypes = ["pkg", "deb", "rpm"] as const; +const fleetMaintainedPackageTypes = ["dmg", "zip"] as const; +const unixPackageTypes = ["pkg", "deb", "rpm", "dmg", "zip"] as const; const windowsPackageTypes = ["msi", "exe"] as const; export const packageTypes = [ ...unixPackageTypes, @@ -7,7 +8,11 @@ export const packageTypes = [ export type WindowsPackageType = typeof windowsPackageTypes[number]; export type UnixPackageType = typeof unixPackageTypes[number]; -export type PackageType = WindowsPackageType | UnixPackageType; +export type FleetMaintainedPackageType = typeof fleetMaintainedPackageTypes[number]; +export type PackageType = + | WindowsPackageType + | UnixPackageType + | FleetMaintainedPackageType; export const isWindowsPackageType = (s: any): s is WindowsPackageType => { return windowsPackageTypes.includes(s); @@ -17,6 +22,12 @@ export const isUnixPackageType = (s: any): s is UnixPackageType => { return unixPackageTypes.includes(s); }; +export const isFleetMaintainedPackageType = ( + s: any +): s is FleetMaintainedPackageType => { + return fleetMaintainedPackageTypes.includes(s); +}; + export const isPackageType = (s: any): s is PackageType => { return packageTypes.includes(s); }; diff --git a/frontend/pages/SoftwarePage/components/PackageAdvancedOptions/PackageAdvancedOptions.tsx b/frontend/pages/SoftwarePage/components/PackageAdvancedOptions/PackageAdvancedOptions.tsx index 8966852a2726..e4ed568c5695 100644 --- a/frontend/pages/SoftwarePage/components/PackageAdvancedOptions/PackageAdvancedOptions.tsx +++ b/frontend/pages/SoftwarePage/components/PackageAdvancedOptions/PackageAdvancedOptions.tsx @@ -6,6 +6,7 @@ import { LEARN_MORE_ABOUT_BASE_LINK } from "utilities/constants"; import { isPackageType, isWindowsPackageType, + isFleetMaintainedPackageType, PackageType, } from "interfaces/package_type"; @@ -46,6 +47,10 @@ const getPostInstallHelpText = (pkgType: PackageType) => { }; const getUninstallHelpText = (pkgType: PackageType) => { + if (isFleetMaintainedPackageType(pkgType)) { + return "Currently, shell scripts are supported"; + } + return ( <> $PACKAGE_ID will be populated with the {PKG_TYPE_TO_ID_TEXT[pkgType]} from diff --git a/frontend/services/entities/software.ts b/frontend/services/entities/software.ts index bc5ab98db40d..1557e169654c 100644 --- a/frontend/services/entities/software.ts +++ b/frontend/services/entities/software.ts @@ -136,6 +136,7 @@ interface IAddFleetMaintainedAppPostBody { pre_install_query?: string; install_script?: string; post_install_script?: string; + uninstall_script?: string; self_service?: boolean; } @@ -376,6 +377,7 @@ export default { pre_install_query: formData.preInstallQuery, install_script: formData.installScript, post_install_script: formData.postInstallScript, + uninstall_script: formData.uninstallScript, self_service: formData.selfService, }; diff --git a/server/datastore/mysql/maintained_apps.go b/server/datastore/mysql/maintained_apps.go index 6be77a458362..06ebd4703d40 100644 --- a/server/datastore/mysql/maintained_apps.go +++ b/server/datastore/mysql/maintained_apps.go @@ -122,9 +122,9 @@ WHERE NOT EXISTS ( WHERE st.bundle_identifier = fla.bundle_identifier AND ( - (si.platform = fla.platform AND si.team_id = ?) + (si.platform = fla.platform AND si.global_or_team_id = ?) OR - (va.platform = fla.platform AND vat.team_id = ?) + (va.platform = fla.platform AND vat.global_or_team_id = ?) ) )` diff --git a/server/fleet/service.go b/server/fleet/service.go index bd3ba0bbce37..b555fec06ecf 100644 --- a/server/fleet/service.go +++ b/server/fleet/service.go @@ -1124,7 +1124,7 @@ type Service interface { // Fleet-maintained apps // AddFleetMaintainedApp adds a Fleet-maintained app to the given team. - AddFleetMaintainedApp(ctx context.Context, teamID *uint, appID uint, installScript, preInstallQuery, postInstallScript string, selfService bool) error + AddFleetMaintainedApp(ctx context.Context, teamID *uint, appID uint, installScript, preInstallQuery, postInstallScript, uninstallScript string, selfService bool) error // ListFleetMaintainedApps lists Fleet-maintained apps available to a specific team ListFleetMaintainedApps(ctx context.Context, teamID uint, opts ListOptions) ([]MaintainedApp, *PaginationMetadata, error) // GetFleetMaintainedApp returns a Fleet-maintained app by ID diff --git a/server/mdm/maintainedapps/ingest.go b/server/mdm/maintainedapps/ingest.go index c699c22b8583..16b3cffa83b6 100644 --- a/server/mdm/maintainedapps/ingest.go +++ b/server/mdm/maintainedapps/ingest.go @@ -137,8 +137,11 @@ func (i ingester) ingestOne(ctx context.Context, app maintainedApp, client *http return ctxerr.Wrapf(ctx, err, "parse URL for cask %s", app.Identifier) } - installScript := installScriptForApp(app, &cask) - uninstallScript := uninstallScriptForApp(app, &cask) + installScript, err := installScriptForApp(app, &cask) + if err != nil { + return ctxerr.Wrapf(ctx, err, "create install script for cask %s", app.Identifier) + } + uninstallScript := uninstallScriptForApp(&cask) _, err = i.ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ Name: cask.Name[0], @@ -155,16 +158,6 @@ func (i ingester) ingestOne(ctx context.Context, app maintainedApp, client *http return ctxerr.Wrap(ctx, err, "upsert maintained app") } -func installScriptForApp(app maintainedApp, cask *brewCask) string { - // TODO: implement install script based on cask and app installer format - return "install" -} - -func uninstallScriptForApp(app maintainedApp, cask *brewCask) string { - // TODO: implement uninstall script based on cask and app installer format - return "uninstall" -} - type brewCask struct { Token string `json:"token"` FullToken string `json:"full_token"` @@ -195,9 +188,28 @@ type brewArtifact struct { Binary []optjson.StringOr[*brewBinaryTarget] `json:"binary"` } +// The choiceChanges file is a property list containing an array of dictionaries. Each dictionary has the following three keys: +// +// Key Description +// choiceIdentifier Identifier for the choice to be modified (string) +// choiceAttribute One of the attribute names described below (string) +// attributeSetting A setting that depends on the choiceAttribute, described below (number or string) +// +// The choiceAttribute and attributeSetting values are as follows: +// +// choiceAttribute attributeSetting Description +// selected (number) 1 to select the choice, 0 to deselect it +// enabled (number) 1 to enable the choice, 0 to disable it +// visible (number) 1 to show the choice, 0 to hide it +// customLocation (string) path at which to install the choice (see below) +type brewPkgConfig struct { + ChoiceIdentifier string `json:"choiceIdentifier" plist:"choiceIdentifier"` + ChoiceAttribute string `json:"choiceAttribute" plist:"choiceAttribute"` + AttributeSetting int `json:"attributeSetting" plist:"attributeSetting"` +} + type brewPkgChoices struct { - // At the moment we don't care about the "choices" field on the pkg. - Choices []any `json:"choices"` + Choices []brewPkgConfig `json:"choices"` } type brewBinaryTarget struct { diff --git a/server/mdm/maintainedapps/scripts.go b/server/mdm/maintainedapps/scripts.go new file mode 100644 index 000000000000..1a3179b28531 --- /dev/null +++ b/server/mdm/maintainedapps/scripts.go @@ -0,0 +1,507 @@ +package maintainedapps + +import ( + "fmt" + "slices" + "sort" + "strings" + + "github.com/fleetdm/fleet/v4/pkg/optjson" + "github.com/groob/plist" +) + +func installScriptForApp(app maintainedApp, cask *brewCask) (string, error) { + sb := newScriptBuilder() + + sb.AddVariable("TMPDIR", `$(dirname "$(realpath $INSTALLER_PATH)")`) + sb.AddVariable("APPDIR", `"/Applications/"`) + + formats := strings.Split(app.InstallerFormat, ":") + sb.Extract(formats[0]) + + for _, artifact := range cask.Artifacts { + switch { + case len(artifact.App) > 0: + sb.Write("# copy to the applications folder") + for _, appPath := range artifact.App { + sb.Copy(appPath, "$APPDIR") + } + + case len(artifact.Pkg) > 0: + sb.Write("# install pkg files") + switch len(artifact.Pkg) { + case 1: + if err := sb.InstallPkg(artifact.Pkg[0].String); err != nil { + return "", fmt.Errorf("building statement to install pkg: %w", err) + } + case 2: + if err := sb.InstallPkg(artifact.Pkg[0].String, artifact.Pkg[1].Other.Choices); err != nil { + return "", fmt.Errorf("building statement to install pkg with choices: %w", err) + } + default: + return "", fmt.Errorf("application %s has unknown directive format for pkg", app.Identifier) + } + + case len(artifact.Binary) > 0: + if len(artifact.Binary) == 2 { + source := artifact.Binary[0].String + target := artifact.Binary[1].Other.Target + + if !strings.Contains(target, "$HOMEBREW_PREFIX") && + !strings.Contains(source, "$HOMEBREW_PREFIX") { + sb.Symlink(source, target) + } + } + } + } + + return sb.String(), nil +} + +func uninstallScriptForApp(cask *brewCask) string { + sb := newScriptBuilder() + + for _, artifact := range cask.Artifacts { + switch { + case len(artifact.App) > 0: + sb.AddVariable("APPDIR", `"/Applications/"`) + for _, appPath := range artifact.App { + sb.RemoveFile(fmt.Sprintf(`"$APPDIR/%s"`, appPath)) + } + case len(artifact.Binary) > 0: + if len(artifact.Binary) == 2 { + target := artifact.Binary[1].Other.Target + if !strings.Contains(target, "$HOMEBREW_PREFIX") { + sb.RemoveFile(fmt.Sprintf(`'%s'`, target)) + } + } + case len(artifact.Uninstall) > 0: + sortUninstall(artifact.Uninstall) + for _, u := range artifact.Uninstall { + processUninstallArtifact(u, sb) + } + case len(artifact.Zap) > 0: + sortUninstall(artifact.Zap) + for _, z := range artifact.Zap { + processUninstallArtifact(z, sb) + } + } + } + + return sb.String() +} + +// priority of uninstall directives is defined by homebrew here: +// https://github.com/Homebrew/brew/blob/e1ff668957dd8a66304c0290dfa66083e6c7444e/Library/Homebrew/cask/artifact/abstract_uninstall.rb#L18-L30 +const ( + PriorityEarlyScript = iota + PriorityLaunchctl + PriorityQuit + PrioritySignal + PriorityLoginItem + PriorityKext + PriorityScript + PriorityPkgutil + PriorityDelete + PriorityTrash + PriorityRmdir +) + +// uninstallArtifactOrder returns an integer representing the priority of the +// artifact based on the uninstall directives it contains. Lower number means +// higher priority +func uninstallArtifactOrder(artifact *brewUninstall) int { + switch { + case len(artifact.LaunchCtl.String)+len(artifact.LaunchCtl.Other) > 0: + return PriorityLaunchctl + case len(artifact.Quit.String)+len(artifact.Quit.Other) > 0: + return PriorityQuit + case len(artifact.Signal.String)+len(artifact.Signal.Other) > 0: + return PrioritySignal + case len(artifact.LoginItem.String)+len(artifact.LoginItem.Other) > 0: + return PriorityLoginItem + case len(artifact.Kext.String)+len(artifact.Kext.Other) > 0: + return PriorityKext + case len(artifact.Script.String)+len(artifact.Script.Other) > 0: + return PriorityScript + case len(artifact.PkgUtil.String)+len(artifact.PkgUtil.Other) > 0: + return PriorityPkgutil + case len(artifact.Delete.String)+len(artifact.Delete.Other) > 0: + return PriorityDelete + case len(artifact.Trash.String)+len(artifact.Trash.Other) > 0: + return PriorityTrash + case len(artifact.RmDir.String)+len(artifact.RmDir.Other) > 0: + return PriorityRmdir + default: + return 999 + } +} + +func sortUninstall(artifacts []*brewUninstall) { + slices.SortFunc(artifacts, func(a, b *brewUninstall) int { + return uninstallArtifactOrder(a) - uninstallArtifactOrder(b) + }) +} + +func processUninstallArtifact(u *brewUninstall, sb *scriptBuilder) { + process := func(target optjson.StringOr[[]string], f func(path string)) { + if target.IsOther { + for _, path := range target.Other { + f(path) + } + } else if len(target.String) > 0 { + f(target.String) + } + } + + addUserVar := func() { + sb.AddVariable("LOGGED_IN_USER", `$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }')`) + } + + process(u.LaunchCtl, func(lc string) { + sb.AddFunction("remove_launchctl_service", removeLaunchctlServiceFunc) + sb.Writef("remove_launchctl_service '%s'", lc) + }) + + process(u.Quit, func(appName string) { + sb.AddFunction("quit_application", quitApplicationFunc) + sb.Writef("quit_application '%s'", appName) + }) + + // per the spec, signals can't have a different format. In the homebrew + // source code an error is raised when the format is different. + if u.Signal.IsOther && len(u.Signal.Other) == 2 { + addUserVar() + sb.AddFunction("send_signal", sendSignalFunc) + sb.Writef(`send_signal '%s' '%s' "$LOGGED_IN_USER"`, u.Signal.Other[0], u.Signal.Other[1]) + } + + if u.Script.IsOther { + addUserVar() + for _, path := range u.Script.Other { + sb.Writef(fmt.Sprintf(`sudo -u "$LOGGED_IN_USER" '%s'`, path)) + } + } else if len(u.Script.String) > 0 { + addUserVar() + sb.Writef(fmt.Sprintf(`sudo -u "$LOGGED_IN_USER" '%s'`, u.Script.String)) + } + + process(u.PkgUtil, func(pkgID string) { + sb.Writef("sudo pkgutil --forget '%s'", pkgID) + }) + + process(u.Delete, func(path string) { + sb.RemoveFile(fmt.Sprintf("'%s'", path)) + }) + + process(u.RmDir, func(dir string) { + sb.Writef("sudo rmdir '%s'", dir) + }) + + process(u.Trash, func(path string) { + addUserVar() + sb.AddFunction("trash", trashFunc) + sb.Writef("trash $LOGGED_IN_USER '%s'", path) + }) +} + +type scriptBuilder struct { + statements []string + variables map[string]string + functions map[string]string +} + +func newScriptBuilder() *scriptBuilder { + return &scriptBuilder{ + statements: []string{}, + variables: map[string]string{}, + functions: map[string]string{}, + } +} + +// AddVariable adds a variable definition to the script +func (s *scriptBuilder) AddVariable(name, definition string) { + s.variables[name] = definition +} + +// AddFunction adds a shell function to the script. +func (s *scriptBuilder) AddFunction(name, definition string) { + s.functions[name] = definition +} + +// Write appends a raw shell command or statement to the script. +func (s *scriptBuilder) Write(in string) { + s.statements = append(s.statements, in) +} + +// Writef formats a string according to the specified format and arguments, +// then appends it to the script. +func (s *scriptBuilder) Writef(format string, args ...any) { + s.statements = append(s.statements, fmt.Sprintf(format, args...)) +} + +// Extract writes shell commands to extract the contents of an installer based +// on the given format. +// +// Supported formats are "dmg" and "zip". It adds the necessary extraction +// commands to the script. +func (s *scriptBuilder) Extract(format string) { + + switch format { + case "dmg": + s.Write("# extract contents") + s.Write(`MOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX) +hdiutil attach -plist -nobrowse -readonly -mountpoint "$MOUNT_POINT" "$INSTALLER_PATH" +sudo cp -R "$MOUNT_POINT"/* "$TMPDIR" +hdiutil detach "$MOUNT_POINT"`) + + case "zip": + s.Write("# extract contents") + s.Write(`unzip "$INSTALLER_PATH" -d "$TMPDIR"`) + } +} + +// Copy writes a command to copy a file from the temporary directory to a +// destination. +func (s *scriptBuilder) Copy(file, dest string) { + s.Writef(`sudo cp -R "$TMPDIR/%s" "%s"`, file, dest) +} + +// RemoveFile writes a command to remove a file or directory with sudo +// privileges. +func (s *scriptBuilder) RemoveFile(file string) { + s.Writef(`sudo rm -rf %s`, file) +} + +// InstallPkg writes a command to install a package using the macOS `installer` utility. +// 'pkg' is the package file to install. Optionally, 'choices' can be provided to specify +// installation options. +// +// If no choices are provided, a simple install command is written. +// +// Returns an error if generating the XML for choices fails. +func (s *scriptBuilder) InstallPkg(pkg string, choices ...[]brewPkgConfig) error { + if len(choices) == 0 { + s.Writef(`sudo installer -pkg "$TMPDIR/%s" -target /`, pkg) + return nil + } + + choiceXML, err := plist.MarshalIndent(choices, " ") + if err != nil { + return err + } + + s.Writef(` +CHOICE_XML=$(mktemp /tmp/choice_xml) + +cat << EOF > "$CHOICE_XML" +%s +EOF + +sudo installer -pkg "$temp_dir"/%s -target / -applyChoiceChangesXML "$CHOICE_XML" +`, choiceXML, pkg) + + return nil +} + +// Symlink writes a command to create a symbolic link from 'source' to 'target'. +func (s *scriptBuilder) Symlink(source, target string) { + s.Writef(`/bin/ln -h -f -s -- "%s" "%s"`, source, target) +} + +// String generates the final script as a string. +// +// It includes the shebang, any variables, functions, and statements in the +// correct order. +func (s *scriptBuilder) String() string { + var script strings.Builder + script.WriteString("#!/bin/sh\n\n") + + if len(s.variables) > 0 { + // write variables, order them alphabetically to produce deterministic + // scripts. + script.WriteString("# variables\n") + keys := make([]string, 0, len(s.variables)) + for name := range s.variables { + keys = append(keys, name) + } + sort.Strings(keys) + for _, name := range keys { + script.WriteString(fmt.Sprintf("%s=%s\n", name, s.variables[name])) + } + } + + if len(s.functions) > 0 { + // write functions, order them alphabetically to produce deterministic + // scripts. + script.WriteString("# functions\n") + keys := make([]string, 0, len(s.functions)) + for name := range s.functions { + keys = append(keys, name) + } + sort.Strings(keys) + for _, name := range keys { + script.WriteString("\n") + script.WriteString(s.functions[name]) + script.WriteString("\n") + } + } + + // write any statements + if len(s.statements) > 0 { + script.WriteString("\n") + script.WriteString(strings.Join(s.statements, "\n")) + script.WriteString("\n") + } + + return script.String() +} + +// removeLaunchctlServiceFunc removes a launchctl service, it's a direct port +// of the homebrew implementation +// https://github.com/Homebrew/brew/blob/e1ff668957dd8a66304c0290dfa66083e6c7444e/Library/Homebrew/cask/artifact/abstract_uninstall.rb#L92 +const removeLaunchctlServiceFunc = `remove_launchctl_service() { + local service="$1" + local booleans=("true" "false") + local plist_status + local paths + local sudo + + echo "Removing launchctl service ${service}" + + for sudo in "${booleans[@]}"; do + plist_status=$(launchctl list "${service}" 2>/dev/null) + + if [[ $plist_status == \{* ]]; then + if [[ $sudo == "true" ]]; then + sudo launchctl remove "${service}" + else + launchctl remove "${service}" + fi + sleep 1 + fi + + paths=( + "/Library/LaunchAgents/${service}.plist" + "/Library/LaunchDaemons/${service}.plist" + ) + + # if not using sudo, prepend the home directory to the paths + if [[ $sudo == "false" ]]; then + for i in "${!paths[@]}"; do + paths[i]="${HOME}${paths[i]}" + done + fi + + for path in "${paths[@]}"; do + if [[ -e "$path" ]]; then + if [[ $sudo == "true" ]]; then + sudo rm -f -- "$path" + else + rm -f -- "$path" + fi + fi + done + done +}` + +// quitApplicationFunc quits a running application. It's a direct port of the +// homebrew implementation +// https://github.com/Homebrew/brew/blob/e1ff668957dd8a66304c0290dfa66083e6c7444e/Library/Homebrew/cask/artifact/abstract_uninstall.rb#L192 +const quitApplicationFunc = `quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} +` + +const trashFunc = `trash() { + local logged_in_user="$1" + local target_file="$2" + local timestamp="$(date +%Y-%m-%d-%s)" + + # replace ~ with /Users/$logged_in_user + if [[ "$target_file" == ~* ]]; then + target_file="/Users/$logged_in_user${target_file:1}" + fi + + local trash="/Users/$logged_in_user/.Trash" + local file_name="$(basename "${target_file}")" + + if [[ -e "$target_file" ]]; then + echo "removing $target_file." + mv -f "$target_file" "$trash/${file_name}_${timestamp}" + else + echo "$target_file doesn't exist." + fi +}` + +const sendSignalFunc = `send_signal() { + local signal="$1" + local bundle_id="$2" + local logged_in_user="$3" + local logged_in_uid pids + + if [ -z "$signal" ] || [ -z "$bundle_id" ] || [ -z "$logged_in_user" ]; then + echo "Usage: uninstall_signal " + return 1 + fi + + logged_in_uid=$(id -u "$logged_in_user") + if [ -z "$logged_in_uid" ]; then + echo "Could not find UID for user '$logged_in_user'." + return 1 + fi + + echo "Signalling '$signal' to application ID '$bundle_id' for user '$logged_in_user'" + + pids=$(/bin/launchctl asuser "$logged_in_uid" sudo -iu "$logged_in_user" /bin/launchctl list | awk -v bundle_id="$bundle_id" ' + $3 ~ bundle_id { print $1 }') + + if [ -z "$pids" ]; then + echo "No processes found for bundle ID '$bundle_id'." + return 0 + fi + + echo "Unix PIDs are $pids for processes with bundle identifier $bundle_id" + for pid in $pids; do + if kill -s "$signal" "$pid" 2>/dev/null; then + echo "Successfully signaled PID $pid with signal $signal." + else + echo "Failed to kill PID $pid with signal $signal. Check permissions." + fi + done + + sleep 3 +}` diff --git a/server/mdm/maintainedapps/scripts_test.go b/server/mdm/maintainedapps/scripts_test.go new file mode 100644 index 000000000000..6c785a67f6b1 --- /dev/null +++ b/server/mdm/maintainedapps/scripts_test.go @@ -0,0 +1,72 @@ +package maintainedapps + +import ( + "encoding/json" + "flag" + "io" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +var ( + update = flag.Bool("update", false, "update the golden files of this test") +) + +func TestMain(m *testing.M) { + flag.Parse() + os.Exit(m.Run()) +} + +func TestScriptGeneration(t *testing.T) { + appsJSON, err := os.ReadFile("apps.json") + require.NoError(t, err) + + var apps []maintainedApp + err = json.Unmarshal(appsJSON, &apps) + require.NoError(t, err) + + for _, app := range apps { + caskJSON, err := os.ReadFile(filepath.Join("testdata", app.Identifier+".json")) + require.NoError(t, err) + + var cask brewCask + err = json.Unmarshal(caskJSON, &cask) + require.NoError(t, err) + + t.Run(app.Identifier, func(t *testing.T) { + installScript, err := installScriptForApp(app, &cask) + require.NoError(t, err) + assertGoldenMatches(t, app.Identifier+"_install", installScript, *update) + assertGoldenMatches(t, app.Identifier+"_uninstall", uninstallScriptForApp(&cask), *update) + }) + } + +} + +func assertGoldenMatches(t *testing.T, goldenFile string, actual string, update bool) { + t.Helper() + goldenPath := filepath.Join("testdata", "scripts", goldenFile+".golden.sh") + + var f *os.File + var err error + if update { + f, err = os.OpenFile(goldenPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + } else { + f, err = os.OpenFile(goldenPath, os.O_RDONLY, 0644) + } + require.NoError(t, err) + defer f.Close() + + if update { + _, err := f.WriteString(actual) + require.NoError(t, err) + return + } + + content, err := io.ReadAll(f) + require.NoError(t, err) + require.Equal(t, string(content), actual) +} diff --git a/server/mdm/maintainedapps/testdata/scripts/1password_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/1password_install.golden.sh new file mode 100644 index 000000000000..892fe48d9e3d --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/1password_install.golden.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") + +# extract contents +unzip "$INSTALLER_PATH" -d "$TMPDIR" +# copy to the applications folder +sudo cp -R "$TMPDIR/1Password.app" "$APPDIR" diff --git a/server/mdm/maintainedapps/testdata/scripts/1password_uninstall.golden.sh b/server/mdm/maintainedapps/testdata/scripts/1password_uninstall.golden.sh new file mode 100644 index 000000000000..b12d324a6087 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/1password_uninstall.golden.sh @@ -0,0 +1,56 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }') +# functions + +trash() { + local logged_in_user="$1" + local target_file="$2" + local timestamp="$(date +%Y-%m-%d-%s)" + + # replace ~ with /Users/$logged_in_user + if [[ "$target_file" == ~* ]]; then + target_file="/Users/$logged_in_user${target_file:1}" + fi + + local trash="/Users/$logged_in_user/.Trash" + local file_name="$(basename "${target_file}")" + + if [[ -e "$target_file" ]]; then + echo "removing $target_file." + mv -f "$target_file" "$trash/${file_name}_${timestamp}" + else + echo "$target_file doesn't exist." + fi +} + +sudo rm -rf "$APPDIR/1Password.app" +trash $LOGGED_IN_USER '~/Library/Application Scripts/2BUA8C4S2C.com.1password*' +trash $LOGGED_IN_USER '~/Library/Application Scripts/2BUA8C4S2C.com.agilebits' +trash $LOGGED_IN_USER '~/Library/Application Scripts/com.1password.1password-launcher' +trash $LOGGED_IN_USER '~/Library/Application Scripts/com.1password.browser-support' +trash $LOGGED_IN_USER '~/Library/Application Support/1Password' +trash $LOGGED_IN_USER '~/Library/Application Support/Arc/User Data/NativeMessagingHosts/com.1password.1password.json' +trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.1password.1password.sfl*' +trash $LOGGED_IN_USER '~/Library/Application Support/CrashReporter/1Password*' +trash $LOGGED_IN_USER '~/Library/Application Support/Google/Chrome Beta/NativeMessagingHosts/com.1password.1password.json' +trash $LOGGED_IN_USER '~/Library/Application Support/Google/Chrome Canary/NativeMessagingHosts/com.1password.1password.json' +trash $LOGGED_IN_USER '~/Library/Application Support/Google/Chrome Dev/NativeMessagingHosts/com.1password.1password.json' +trash $LOGGED_IN_USER '~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.1password.1password.json' +trash $LOGGED_IN_USER '~/Library/Application Support/Microsoft Edge Beta/NativeMessagingHosts/com.1password.1password.json' +trash $LOGGED_IN_USER '~/Library/Application Support/Microsoft Edge Canary/NativeMessagingHosts/com.1password.1password.json' +trash $LOGGED_IN_USER '~/Library/Application Support/Microsoft Edge Dev/NativeMessagingHosts/com.1password.1password.json' +trash $LOGGED_IN_USER '~/Library/Application Support/Microsoft Edge/NativeMessagingHosts/com.1password.1password.json' +trash $LOGGED_IN_USER '~/Library/Application Support/Mozilla/NativeMessagingHosts/com.1password.1password.json' +trash $LOGGED_IN_USER '~/Library/Application Support/Vivaldi/NativeMessagingHosts/com.1password.1password.json' +trash $LOGGED_IN_USER '~/Library/Containers/2BUA8C4S2C.com.1password.browser-helper' +trash $LOGGED_IN_USER '~/Library/Containers/com.1password.1password*' +trash $LOGGED_IN_USER '~/Library/Containers/com.1password.browser-support' +trash $LOGGED_IN_USER '~/Library/Group Containers/2BUA8C4S2C.com.1password' +trash $LOGGED_IN_USER '~/Library/Group Containers/2BUA8C4S2C.com.agilebits' +trash $LOGGED_IN_USER '~/Library/Logs/1Password' +trash $LOGGED_IN_USER '~/Library/Preferences/com.1password.1password.plist' +trash $LOGGED_IN_USER '~/Library/Preferences/group.com.1password.plist' +trash $LOGGED_IN_USER '~/Library/Saved Application State/com.1password.1password.savedState' diff --git a/server/mdm/maintainedapps/testdata/scripts/adobe-acrobat-reader_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/adobe-acrobat-reader_install.golden.sh new file mode 100644 index 000000000000..1c975a9950aa --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/adobe-acrobat-reader_install.golden.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") + +# extract contents +MOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX) +hdiutil attach -plist -nobrowse -readonly -mountpoint "$MOUNT_POINT" "$INSTALLER_PATH" +sudo cp -R "$MOUNT_POINT"/* "$TMPDIR" +hdiutil detach "$MOUNT_POINT" +# install pkg files +sudo installer -pkg "$TMPDIR/AcroRdrDC_2400221005_MUI.pkg" -target / diff --git a/server/mdm/maintainedapps/testdata/scripts/adobe-acrobat-reader_uninstall.golden.sh b/server/mdm/maintainedapps/testdata/scripts/adobe-acrobat-reader_uninstall.golden.sh new file mode 100644 index 000000000000..7a6e702ffe3f --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/adobe-acrobat-reader_uninstall.golden.sh @@ -0,0 +1,126 @@ +#!/bin/sh + +# variables +LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }') +# functions + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} + + +remove_launchctl_service() { + local service="$1" + local booleans=("true" "false") + local plist_status + local paths + local sudo + + echo "Removing launchctl service ${service}" + + for sudo in "${booleans[@]}"; do + plist_status=$(launchctl list "${service}" 2>/dev/null) + + if [[ $plist_status == \{* ]]; then + if [[ $sudo == "true" ]]; then + sudo launchctl remove "${service}" + else + launchctl remove "${service}" + fi + sleep 1 + fi + + paths=( + "/Library/LaunchAgents/${service}.plist" + "/Library/LaunchDaemons/${service}.plist" + ) + + # if not using sudo, prepend the home directory to the paths + if [[ $sudo == "false" ]]; then + for i in "${!paths[@]}"; do + paths[i]="${HOME}${paths[i]}" + done + fi + + for path in "${paths[@]}"; do + if [[ -e "$path" ]]; then + if [[ $sudo == "true" ]]; then + sudo rm -f -- "$path" + else + rm -f -- "$path" + fi + fi + done + done +} + +trash() { + local logged_in_user="$1" + local target_file="$2" + local timestamp="$(date +%Y-%m-%d-%s)" + + # replace ~ with /Users/$logged_in_user + if [[ "$target_file" == ~* ]]; then + target_file="/Users/$logged_in_user${target_file:1}" + fi + + local trash="/Users/$logged_in_user/.Trash" + local file_name="$(basename "${target_file}")" + + if [[ -e "$target_file" ]]; then + echo "removing $target_file." + mv -f "$target_file" "$trash/${file_name}_${timestamp}" + else + echo "$target_file doesn't exist." + fi +} + +remove_launchctl_service 'com.adobe.ARMDC.Communicator' +remove_launchctl_service 'com.adobe.ARMDC.SMJobBlessHelper' +remove_launchctl_service 'com.adobe.ARMDCHelper.cc24aef4a1b90ed56a725c38014c95072f92651fb65e1bf9c8e43c37a23d420d' +quit_application 'com.adobe.AdobeRdrCEF' +quit_application 'com.adobe.AdobeRdrCEFHelper' +quit_application 'com.adobe.Reader' +sudo pkgutil --forget 'com.adobe.acrobat.DC.reader.*' +sudo pkgutil --forget 'com.adobe.armdc.app.pkg' +sudo pkgutil --forget 'com.adobe.RdrServicesUpdater' +sudo rm -rf '/Applications/Adobe Acrobat Reader.app' +sudo rm -rf '/Library/Preferences/com.adobe.reader.DC.WebResource.plist' +trash $LOGGED_IN_USER '~/Library/Caches/com.adobe.Reader' +trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.adobe.Reader.binarycookies' +trash $LOGGED_IN_USER '~/Library/Preferences/com.adobe.AdobeRdrCEFHelper.plist' +trash $LOGGED_IN_USER '~/Library/Preferences/com.adobe.crashreporter.plist' +trash $LOGGED_IN_USER '~/Library/Preferences/com.adobe.Reader.plist' diff --git a/server/mdm/maintainedapps/testdata/scripts/box-drive_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/box-drive_install.golden.sh new file mode 100644 index 000000000000..1cd05d3dd6e3 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/box-drive_install.golden.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") + +# install pkg files +sudo installer -pkg "$TMPDIR/BoxDrive-2.40.345.pkg" -target / diff --git a/server/mdm/maintainedapps/testdata/scripts/box-drive_uninstall.golden.sh b/server/mdm/maintainedapps/testdata/scripts/box-drive_uninstall.golden.sh new file mode 100644 index 000000000000..11cd1c552deb --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/box-drive_uninstall.golden.sh @@ -0,0 +1,125 @@ +#!/bin/sh + +# variables +LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }') +# functions + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} + + +remove_launchctl_service() { + local service="$1" + local booleans=("true" "false") + local plist_status + local paths + local sudo + + echo "Removing launchctl service ${service}" + + for sudo in "${booleans[@]}"; do + plist_status=$(launchctl list "${service}" 2>/dev/null) + + if [[ $plist_status == \{* ]]; then + if [[ $sudo == "true" ]]; then + sudo launchctl remove "${service}" + else + launchctl remove "${service}" + fi + sleep 1 + fi + + paths=( + "/Library/LaunchAgents/${service}.plist" + "/Library/LaunchDaemons/${service}.plist" + ) + + # if not using sudo, prepend the home directory to the paths + if [[ $sudo == "false" ]]; then + for i in "${!paths[@]}"; do + paths[i]="${HOME}${paths[i]}" + done + fi + + for path in "${paths[@]}"; do + if [[ -e "$path" ]]; then + if [[ $sudo == "true" ]]; then + sudo rm -f -- "$path" + else + rm -f -- "$path" + fi + fi + done + done +} + +trash() { + local logged_in_user="$1" + local target_file="$2" + local timestamp="$(date +%Y-%m-%d-%s)" + + # replace ~ with /Users/$logged_in_user + if [[ "$target_file" == ~* ]]; then + target_file="/Users/$logged_in_user${target_file:1}" + fi + + local trash="/Users/$logged_in_user/.Trash" + local file_name="$(basename "${target_file}")" + + if [[ -e "$target_file" ]]; then + echo "removing $target_file." + mv -f "$target_file" "$trash/${file_name}_${timestamp}" + else + echo "$target_file doesn't exist." + fi +} + +remove_launchctl_service 'com.box.desktop.helper' +quit_application 'com.box.Box-Local-Com-Server' +quit_application 'com.box.desktop' +quit_application 'com.box.desktop.findersyncext' +quit_application 'com.box.desktop.helper' +quit_application 'com.box.desktop.ui' +sudo -u "$LOGGED_IN_USER" '/Library/Application Support/Box/uninstall_box_drive' +sudo pkgutil --forget 'com.box.desktop.installer.*' +trash $LOGGED_IN_USER '~/.Box_*' +trash $LOGGED_IN_USER '~/Library/Application Support/Box/Box' +trash $LOGGED_IN_USER '~/Library/Application Support/FileProvider/com.box.desktop.boxfileprovider' +trash $LOGGED_IN_USER '~/Library/Containers/com.box.desktop.findersyncext' +trash $LOGGED_IN_USER '~/Library/Logs/Box/Box' +trash $LOGGED_IN_USER '~/Library/Preferences/com.box.desktop.plist' +trash $LOGGED_IN_USER '~/Library/Preferences/com.box.desktop.ui.plist' diff --git a/server/mdm/maintainedapps/testdata/scripts/brave-browser_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/brave-browser_install.golden.sh new file mode 100644 index 000000000000..4dae8ce08582 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/brave-browser_install.golden.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") + +# extract contents +MOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX) +hdiutil attach -plist -nobrowse -readonly -mountpoint "$MOUNT_POINT" "$INSTALLER_PATH" +sudo cp -R "$MOUNT_POINT"/* "$TMPDIR" +hdiutil detach "$MOUNT_POINT" +# copy to the applications folder +sudo cp -R "$TMPDIR/Brave Browser.app" "$APPDIR" diff --git a/server/mdm/maintainedapps/testdata/scripts/brave-browser_uninstall.golden.sh b/server/mdm/maintainedapps/testdata/scripts/brave-browser_uninstall.golden.sh new file mode 100644 index 000000000000..c31741a1b6d6 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/brave-browser_uninstall.golden.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }') +# functions + +trash() { + local logged_in_user="$1" + local target_file="$2" + local timestamp="$(date +%Y-%m-%d-%s)" + + # replace ~ with /Users/$logged_in_user + if [[ "$target_file" == ~* ]]; then + target_file="/Users/$logged_in_user${target_file:1}" + fi + + local trash="/Users/$logged_in_user/.Trash" + local file_name="$(basename "${target_file}")" + + if [[ -e "$target_file" ]]; then + echo "removing $target_file." + mv -f "$target_file" "$trash/${file_name}_${timestamp}" + else + echo "$target_file doesn't exist." + fi +} + +sudo rm -rf "$APPDIR/Brave Browser.app" +trash $LOGGED_IN_USER '~/Library/Application Support/BraveSoftware' +trash $LOGGED_IN_USER '~/Library/Caches/BraveSoftware' +trash $LOGGED_IN_USER '~/Library/Caches/com.brave.Browser' +trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.brave.Browser' +trash $LOGGED_IN_USER '~/Library/Preferences/com.brave.Browser.plist' +trash $LOGGED_IN_USER '~/Library/Saved Application State/com.brave.Browser.savedState' diff --git a/server/mdm/maintainedapps/testdata/scripts/cloudflare-warp_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/cloudflare-warp_install.golden.sh new file mode 100644 index 000000000000..88210167e9a0 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/cloudflare-warp_install.golden.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") + +# install pkg files +sudo installer -pkg "$TMPDIR/Cloudflare_WARP_2024.6.474.0.pkg" -target / diff --git a/server/mdm/maintainedapps/testdata/scripts/cloudflare-warp_uninstall.golden.sh b/server/mdm/maintainedapps/testdata/scripts/cloudflare-warp_uninstall.golden.sh new file mode 100644 index 000000000000..32a20bdb719b --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/cloudflare-warp_uninstall.golden.sh @@ -0,0 +1,122 @@ +#!/bin/sh + +# variables +LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }') +# functions + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} + + +remove_launchctl_service() { + local service="$1" + local booleans=("true" "false") + local plist_status + local paths + local sudo + + echo "Removing launchctl service ${service}" + + for sudo in "${booleans[@]}"; do + plist_status=$(launchctl list "${service}" 2>/dev/null) + + if [[ $plist_status == \{* ]]; then + if [[ $sudo == "true" ]]; then + sudo launchctl remove "${service}" + else + launchctl remove "${service}" + fi + sleep 1 + fi + + paths=( + "/Library/LaunchAgents/${service}.plist" + "/Library/LaunchDaemons/${service}.plist" + ) + + # if not using sudo, prepend the home directory to the paths + if [[ $sudo == "false" ]]; then + for i in "${!paths[@]}"; do + paths[i]="${HOME}${paths[i]}" + done + fi + + for path in "${paths[@]}"; do + if [[ -e "$path" ]]; then + if [[ $sudo == "true" ]]; then + sudo rm -f -- "$path" + else + rm -f -- "$path" + fi + fi + done + done +} + +trash() { + local logged_in_user="$1" + local target_file="$2" + local timestamp="$(date +%Y-%m-%d-%s)" + + # replace ~ with /Users/$logged_in_user + if [[ "$target_file" == ~* ]]; then + target_file="/Users/$logged_in_user${target_file:1}" + fi + + local trash="/Users/$logged_in_user/.Trash" + local file_name="$(basename "${target_file}")" + + if [[ -e "$target_file" ]]; then + echo "removing $target_file." + mv -f "$target_file" "$trash/${file_name}_${timestamp}" + else + echo "$target_file doesn't exist." + fi +} + +remove_launchctl_service 'com.cloudflare.1dot1dot1dot1.macos.loginlauncherapp' +remove_launchctl_service 'com.cloudflare.1dot1dot1dot1.macos.warp.daemon' +quit_application 'com.cloudflare.1dot1dot1dot1.macos' +sudo pkgutil --forget 'com.cloudflare.1dot1dot1dot1.macos' +trash $LOGGED_IN_USER '~/Library/Application Scripts/com.cloudflare.1dot1dot1dot1.macos.loginlauncherapp' +trash $LOGGED_IN_USER '~/Library/Application Support/com.cloudflare.1dot1dot1dot1.macos' +trash $LOGGED_IN_USER '~/Library/Caches/com.cloudflare.1dot1dot1dot1.macos' +trash $LOGGED_IN_USER '~/Library/Caches/com.plausiblelabs.crashreporter.data/com.cloudflare.1dot1dot1dot1.macos' +trash $LOGGED_IN_USER '~/Library/Containers/com.cloudflare.1dot1dot1dot1.macos.loginlauncherapp' +trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.cloudflare.1dot1dot1dot1.macos' +trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.cloudflare.1dot1dot1dot1.macos.binarycookies' +trash $LOGGED_IN_USER '~/Library/Preferences/com.cloudflare.1dot1dot1dot1.macos.plist' diff --git a/server/mdm/maintainedapps/testdata/scripts/docker_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/docker_install.golden.sh new file mode 100644 index 000000000000..4d6dd3f76e06 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/docker_install.golden.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") + +# extract contents +MOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX) +hdiutil attach -plist -nobrowse -readonly -mountpoint "$MOUNT_POINT" "$INSTALLER_PATH" +sudo cp -R "$MOUNT_POINT"/* "$TMPDIR" +hdiutil detach "$MOUNT_POINT" +# copy to the applications folder +sudo cp -R "$TMPDIR/Docker.app" "$APPDIR" +/bin/ln -h -f -s -- "$APPDIR/Docker.app/Contents/Resources/bin/docker" "/usr/local/bin/docker" +/bin/ln -h -f -s -- "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-desktop" "/usr/local/bin/docker-credential-desktop" +/bin/ln -h -f -s -- "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-ecr-login" "/usr/local/bin/docker-credential-ecr-login" +/bin/ln -h -f -s -- "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-osxkeychain" "/usr/local/bin/docker-credential-osxkeychain" +/bin/ln -h -f -s -- "$APPDIR/Docker.app/Contents/Resources/bin/docker-index" "/usr/local/bin/docker-index" +/bin/ln -h -f -s -- "$APPDIR/Docker.app/Contents/Resources/bin/kubectl" "/usr/local/bin/kubectl.docker" +/bin/ln -h -f -s -- "$APPDIR/Docker.app/Contents/Resources/cli-plugins/docker-compose" "/usr/local/cli-plugins/docker-compose" +/bin/ln -h -f -s -- "$APPDIR/Docker.app/Contents/Resources/bin/hub-tool" "/usr/local/bin/hub-tool" diff --git a/server/mdm/maintainedapps/testdata/scripts/docker_uninstall.golden.sh b/server/mdm/maintainedapps/testdata/scripts/docker_uninstall.golden.sh new file mode 100644 index 000000000000..02d6d5d91052 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/docker_uninstall.golden.sh @@ -0,0 +1,152 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }') +# functions + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} + + +remove_launchctl_service() { + local service="$1" + local booleans=("true" "false") + local plist_status + local paths + local sudo + + echo "Removing launchctl service ${service}" + + for sudo in "${booleans[@]}"; do + plist_status=$(launchctl list "${service}" 2>/dev/null) + + if [[ $plist_status == \{* ]]; then + if [[ $sudo == "true" ]]; then + sudo launchctl remove "${service}" + else + launchctl remove "${service}" + fi + sleep 1 + fi + + paths=( + "/Library/LaunchAgents/${service}.plist" + "/Library/LaunchDaemons/${service}.plist" + ) + + # if not using sudo, prepend the home directory to the paths + if [[ $sudo == "false" ]]; then + for i in "${!paths[@]}"; do + paths[i]="${HOME}${paths[i]}" + done + fi + + for path in "${paths[@]}"; do + if [[ -e "$path" ]]; then + if [[ $sudo == "true" ]]; then + sudo rm -f -- "$path" + else + rm -f -- "$path" + fi + fi + done + done +} + +trash() { + local logged_in_user="$1" + local target_file="$2" + local timestamp="$(date +%Y-%m-%d-%s)" + + # replace ~ with /Users/$logged_in_user + if [[ "$target_file" == ~* ]]; then + target_file="/Users/$logged_in_user${target_file:1}" + fi + + local trash="/Users/$logged_in_user/.Trash" + local file_name="$(basename "${target_file}")" + + if [[ -e "$target_file" ]]; then + echo "removing $target_file." + mv -f "$target_file" "$trash/${file_name}_${timestamp}" + else + echo "$target_file doesn't exist." + fi +} + +remove_launchctl_service 'com.docker.helper' +remove_launchctl_service 'com.docker.socket' +remove_launchctl_service 'com.docker.vmnetd' +quit_application 'com.docker.docker' +sudo rm -rf '/Library/PrivilegedHelperTools/com.docker.socket' +sudo rm -rf '/Library/PrivilegedHelperTools/com.docker.vmnetd' +sudo rmdir '~/.docker/bin' +sudo rm -rf "$APPDIR/Docker.app" +sudo rm -rf '/usr/local/bin/docker' +sudo rm -rf '/usr/local/bin/docker-credential-desktop' +sudo rm -rf '/usr/local/bin/docker-credential-ecr-login' +sudo rm -rf '/usr/local/bin/docker-credential-osxkeychain' +sudo rm -rf '/usr/local/bin/docker-index' +sudo rm -rf '/usr/local/bin/kubectl.docker' +sudo rm -rf '/usr/local/cli-plugins/docker-compose' +sudo rm -rf '/usr/local/bin/hub-tool' +sudo rmdir '~/Library/Caches/com.plausiblelabs.crashreporter.data' +sudo rmdir '~/Library/Caches/KSCrashReports' +trash $LOGGED_IN_USER '/usr/local/bin/docker-compose.backup' +trash $LOGGED_IN_USER '/usr/local/bin/docker.backup' +trash $LOGGED_IN_USER '~/.docker' +trash $LOGGED_IN_USER '~/Library/Application Scripts/com.docker.helper' +trash $LOGGED_IN_USER '~/Library/Application Scripts/group.com.docker' +trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.docker.helper.sfl*' +trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.electron.dockerdesktop.sfl*' +trash $LOGGED_IN_USER '~/Library/Application Support/com.bugsnag.Bugsnag/com.docker.docker' +trash $LOGGED_IN_USER '~/Library/Application Support/Docker Desktop' +trash $LOGGED_IN_USER '~/Library/Caches/com.docker.docker' +trash $LOGGED_IN_USER '~/Library/Caches/com.plausiblelabs.crashreporter.data/com.docker.docker' +trash $LOGGED_IN_USER '~/Library/Caches/KSCrashReports/Docker' +trash $LOGGED_IN_USER '~/Library/Containers/com.docker.docker' +trash $LOGGED_IN_USER '~/Library/Containers/com.docker.helper' +trash $LOGGED_IN_USER '~/Library/Group Containers/group.com.docker' +trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.docker.docker' +trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.docker.docker.binarycookies' +trash $LOGGED_IN_USER '~/Library/Logs/Docker Desktop' +trash $LOGGED_IN_USER '~/Library/Preferences/com.docker.docker.plist' +trash $LOGGED_IN_USER '~/Library/Preferences/com.electron.docker-frontend.plist' +trash $LOGGED_IN_USER '~/Library/Preferences/com.electron.dockerdesktop.plist' +trash $LOGGED_IN_USER '~/Library/Saved Application State/com.electron.docker-frontend.savedState' +trash $LOGGED_IN_USER '~/Library/Saved Application State/com.electron.dockerdesktop.savedState' diff --git a/server/mdm/maintainedapps/testdata/scripts/figma_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/figma_install.golden.sh new file mode 100644 index 000000000000..0afbf1dbc286 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/figma_install.golden.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") + +# extract contents +unzip "$INSTALLER_PATH" -d "$TMPDIR" +# copy to the applications folder +sudo cp -R "$TMPDIR/Figma.app" "$APPDIR" diff --git a/server/mdm/maintainedapps/testdata/scripts/figma_uninstall.golden.sh b/server/mdm/maintainedapps/testdata/scripts/figma_uninstall.golden.sh new file mode 100644 index 000000000000..551651b8736c --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/figma_uninstall.golden.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }') +# functions + +trash() { + local logged_in_user="$1" + local target_file="$2" + local timestamp="$(date +%Y-%m-%d-%s)" + + # replace ~ with /Users/$logged_in_user + if [[ "$target_file" == ~* ]]; then + target_file="/Users/$logged_in_user${target_file:1}" + fi + + local trash="/Users/$logged_in_user/.Trash" + local file_name="$(basename "${target_file}")" + + if [[ -e "$target_file" ]]; then + echo "removing $target_file." + mv -f "$target_file" "$trash/${file_name}_${timestamp}" + else + echo "$target_file doesn't exist." + fi +} + +sudo rm -rf "$APPDIR/Figma.app" +trash $LOGGED_IN_USER '~/Library/Application Support/Figma' +trash $LOGGED_IN_USER '~/Library/Application Support/figma-desktop' +trash $LOGGED_IN_USER '~/Library/Caches/com.figma.agent' +trash $LOGGED_IN_USER '~/Library/Caches/com.figma.Desktop' +trash $LOGGED_IN_USER '~/Library/Preferences/com.figma.Desktop.plist' +trash $LOGGED_IN_USER '~/Library/Saved Application State/com.figma.Desktop.savedState' diff --git a/server/mdm/maintainedapps/testdata/scripts/firefox_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/firefox_install.golden.sh new file mode 100644 index 000000000000..5de47f50aa98 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/firefox_install.golden.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") + +# extract contents +MOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX) +hdiutil attach -plist -nobrowse -readonly -mountpoint "$MOUNT_POINT" "$INSTALLER_PATH" +sudo cp -R "$MOUNT_POINT"/* "$TMPDIR" +hdiutil detach "$MOUNT_POINT" +# copy to the applications folder +sudo cp -R "$TMPDIR/Firefox.app" "$APPDIR" diff --git a/server/mdm/maintainedapps/testdata/scripts/firefox_uninstall.golden.sh b/server/mdm/maintainedapps/testdata/scripts/firefox_uninstall.golden.sh new file mode 100644 index 000000000000..b082bae00e79 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/firefox_uninstall.golden.sh @@ -0,0 +1,85 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }') +# functions + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} + + +trash() { + local logged_in_user="$1" + local target_file="$2" + local timestamp="$(date +%Y-%m-%d-%s)" + + # replace ~ with /Users/$logged_in_user + if [[ "$target_file" == ~* ]]; then + target_file="/Users/$logged_in_user${target_file:1}" + fi + + local trash="/Users/$logged_in_user/.Trash" + local file_name="$(basename "${target_file}")" + + if [[ -e "$target_file" ]]; then + echo "removing $target_file." + mv -f "$target_file" "$trash/${file_name}_${timestamp}" + else + echo "$target_file doesn't exist." + fi +} + +quit_application 'org.mozilla.firefox' +sudo rm -rf "$APPDIR/Firefox.app" +sudo rm -rf 'firefox' +sudo rmdir '~/Library/Application Support/Mozilla' +sudo rmdir '~/Library/Caches/Mozilla' +sudo rmdir '~/Library/Caches/Mozilla/updates' +sudo rmdir '~/Library/Caches/Mozilla/updates/Applications' +trash $LOGGED_IN_USER '/Library/Logs/DiagnosticReports/firefox_*' +trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/org.mozilla.firefox.sfl*' +trash $LOGGED_IN_USER '~/Library/Application Support/CrashReporter/firefox_*' +trash $LOGGED_IN_USER '~/Library/Application Support/Firefox' +trash $LOGGED_IN_USER '~/Library/Caches/Firefox' +trash $LOGGED_IN_USER '~/Library/Caches/Mozilla/updates/Applications/Firefox' +trash $LOGGED_IN_USER '~/Library/Caches/org.mozilla.crashreporter' +trash $LOGGED_IN_USER '~/Library/Caches/org.mozilla.firefox' +trash $LOGGED_IN_USER '~/Library/Preferences/org.mozilla.crashreporter.plist' +trash $LOGGED_IN_USER '~/Library/Preferences/org.mozilla.firefox.plist' +trash $LOGGED_IN_USER '~/Library/Saved Application State/org.mozilla.firefox.savedState' +trash $LOGGED_IN_USER '~/Library/WebKit/org.mozilla.firefox' diff --git a/server/mdm/maintainedapps/testdata/scripts/google-chrome_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/google-chrome_install.golden.sh new file mode 100644 index 000000000000..8ce14c6b9136 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/google-chrome_install.golden.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") + +# extract contents +MOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX) +hdiutil attach -plist -nobrowse -readonly -mountpoint "$MOUNT_POINT" "$INSTALLER_PATH" +sudo cp -R "$MOUNT_POINT"/* "$TMPDIR" +hdiutil detach "$MOUNT_POINT" +# copy to the applications folder +sudo cp -R "$TMPDIR/Google Chrome.app" "$APPDIR" diff --git a/server/mdm/maintainedapps/testdata/scripts/google-chrome_uninstall.golden.sh b/server/mdm/maintainedapps/testdata/scripts/google-chrome_uninstall.golden.sh new file mode 100644 index 000000000000..0d657a9b8daa --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/google-chrome_uninstall.golden.sh @@ -0,0 +1,102 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }') +# functions + +remove_launchctl_service() { + local service="$1" + local booleans=("true" "false") + local plist_status + local paths + local sudo + + echo "Removing launchctl service ${service}" + + for sudo in "${booleans[@]}"; do + plist_status=$(launchctl list "${service}" 2>/dev/null) + + if [[ $plist_status == \{* ]]; then + if [[ $sudo == "true" ]]; then + sudo launchctl remove "${service}" + else + launchctl remove "${service}" + fi + sleep 1 + fi + + paths=( + "/Library/LaunchAgents/${service}.plist" + "/Library/LaunchDaemons/${service}.plist" + ) + + # if not using sudo, prepend the home directory to the paths + if [[ $sudo == "false" ]]; then + for i in "${!paths[@]}"; do + paths[i]="${HOME}${paths[i]}" + done + fi + + for path in "${paths[@]}"; do + if [[ -e "$path" ]]; then + if [[ $sudo == "true" ]]; then + sudo rm -f -- "$path" + else + rm -f -- "$path" + fi + fi + done + done +} + +trash() { + local logged_in_user="$1" + local target_file="$2" + local timestamp="$(date +%Y-%m-%d-%s)" + + # replace ~ with /Users/$logged_in_user + if [[ "$target_file" == ~* ]]; then + target_file="/Users/$logged_in_user${target_file:1}" + fi + + local trash="/Users/$logged_in_user/.Trash" + local file_name="$(basename "${target_file}")" + + if [[ -e "$target_file" ]]; then + echo "removing $target_file." + mv -f "$target_file" "$trash/${file_name}_${timestamp}" + else + echo "$target_file doesn't exist." + fi +} + +sudo rm -rf "$APPDIR/Google Chrome.app" +remove_launchctl_service 'com.google.keystone.agent' +remove_launchctl_service 'com.google.keystone.daemon' +sudo rmdir '/Library/Google' +sudo rmdir '~/Library/Application Support/Google' +sudo rmdir '~/Library/Caches/Google' +sudo rmdir '~/Library/Google' +trash $LOGGED_IN_USER '/Library/Caches/com.google.SoftwareUpdate.*' +trash $LOGGED_IN_USER '/Library/Google/Google Chrome Brand.plist' +trash $LOGGED_IN_USER '/Library/Google/GoogleSoftwareUpdate' +trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.google.chrome.app.*.sfl*' +trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.google.chrome.sfl*' +trash $LOGGED_IN_USER '~/Library/Application Support/Google/Chrome' +trash $LOGGED_IN_USER '~/Library/Caches/com.google.Chrome' +trash $LOGGED_IN_USER '~/Library/Caches/com.google.Chrome.helper.*' +trash $LOGGED_IN_USER '~/Library/Caches/com.google.Keystone' +trash $LOGGED_IN_USER '~/Library/Caches/com.google.Keystone.Agent' +trash $LOGGED_IN_USER '~/Library/Caches/com.google.SoftwareUpdate' +trash $LOGGED_IN_USER '~/Library/Caches/Google/Chrome' +trash $LOGGED_IN_USER '~/Library/Google/Google Chrome Brand.plist' +trash $LOGGED_IN_USER '~/Library/Google/GoogleSoftwareUpdate' +trash $LOGGED_IN_USER '~/Library/LaunchAgents/com.google.keystone.agent.plist' +trash $LOGGED_IN_USER '~/Library/LaunchAgents/com.google.keystone.xpcservice.plist' +trash $LOGGED_IN_USER '~/Library/Logs/GoogleSoftwareUpdateAgent.log' +trash $LOGGED_IN_USER '~/Library/Preferences/com.google.Chrome.plist' +trash $LOGGED_IN_USER '~/Library/Preferences/com.google.Keystone.Agent.plist' +trash $LOGGED_IN_USER '~/Library/Saved Application State/com.google.Chrome.app.*.savedState' +trash $LOGGED_IN_USER '~/Library/Saved Application State/com.google.Chrome.savedState' +trash $LOGGED_IN_USER '~/Library/WebKit/com.google.Chrome' diff --git a/server/mdm/maintainedapps/testdata/scripts/microsoft-edge_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/microsoft-edge_install.golden.sh new file mode 100644 index 000000000000..fa6887a8aa4b --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/microsoft-edge_install.golden.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") + +# install pkg files + +CHOICE_XML=$(mktemp /tmp/choice_xml) + +cat << EOF > "$CHOICE_XML" + + + + + + + attributeSetting + 0 + choiceAttribute + selected + choiceIdentifier + com.microsoft.package.Microsoft_AutoUpdate.app + + + + + +EOF + +sudo installer -pkg "$temp_dir"/MicrosoftEdge-128.0.2739.67.pkg -target / -applyChoiceChangesXML "$CHOICE_XML" + diff --git a/server/mdm/maintainedapps/testdata/scripts/microsoft-edge_uninstall.golden.sh b/server/mdm/maintainedapps/testdata/scripts/microsoft-edge_uninstall.golden.sh new file mode 100644 index 000000000000..31592353cc9e --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/microsoft-edge_uninstall.golden.sh @@ -0,0 +1,91 @@ +#!/bin/sh + +# variables +LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }') +# functions + +remove_launchctl_service() { + local service="$1" + local booleans=("true" "false") + local plist_status + local paths + local sudo + + echo "Removing launchctl service ${service}" + + for sudo in "${booleans[@]}"; do + plist_status=$(launchctl list "${service}" 2>/dev/null) + + if [[ $plist_status == \{* ]]; then + if [[ $sudo == "true" ]]; then + sudo launchctl remove "${service}" + else + launchctl remove "${service}" + fi + sleep 1 + fi + + paths=( + "/Library/LaunchAgents/${service}.plist" + "/Library/LaunchDaemons/${service}.plist" + ) + + # if not using sudo, prepend the home directory to the paths + if [[ $sudo == "false" ]]; then + for i in "${!paths[@]}"; do + paths[i]="${HOME}${paths[i]}" + done + fi + + for path in "${paths[@]}"; do + if [[ -e "$path" ]]; then + if [[ $sudo == "true" ]]; then + sudo rm -f -- "$path" + else + rm -f -- "$path" + fi + fi + done + done +} + +trash() { + local logged_in_user="$1" + local target_file="$2" + local timestamp="$(date +%Y-%m-%d-%s)" + + # replace ~ with /Users/$logged_in_user + if [[ "$target_file" == ~* ]]; then + target_file="/Users/$logged_in_user${target_file:1}" + fi + + local trash="/Users/$logged_in_user/.Trash" + local file_name="$(basename "${target_file}")" + + if [[ -e "$target_file" ]]; then + echo "removing $target_file." + mv -f "$target_file" "$trash/${file_name}_${timestamp}" + else + echo "$target_file doesn't exist." + fi +} + +remove_launchctl_service 'com.microsoft.EdgeUpdater.update-internal.109.0.1518.89.system' +remove_launchctl_service 'com.microsoft.EdgeUpdater.update.system' +remove_launchctl_service 'com.microsoft.EdgeUpdater.wake.system' +sudo pkgutil --forget 'com.microsoft.edgemac' +sudo rm -rf '/Library/Application Support/Microsoft/EdgeUpdater' +sudo rmdir '/Library/Application Support/Microsoft' +trash $LOGGED_IN_USER '~/Library/Application Scripts/com.microsoft.edgemac.wdgExtension' +trash $LOGGED_IN_USER '~/Library/Application Support/Microsoft Edge' +trash $LOGGED_IN_USER '~/Library/Application Support/Microsoft/EdgeUpdater' +trash $LOGGED_IN_USER '~/Library/Caches/com.microsoft.edgemac' +trash $LOGGED_IN_USER '~/Library/Caches/com.microsoft.EdgeUpdater' +trash $LOGGED_IN_USER '~/Library/Caches/Microsoft Edge' +trash $LOGGED_IN_USER '~/Library/Containers/com.microsoft.edgemac.wdgExtension' +trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.microsoft.edge*' +trash $LOGGED_IN_USER '~/Library/LaunchAgents/com.microsoft.EdgeUpdater.*.plist' +trash $LOGGED_IN_USER '~/Library/Microsoft/EdgeUpdater' +trash $LOGGED_IN_USER '~/Library/Preferences/com.microsoft.edgemac.plist' +trash $LOGGED_IN_USER '~/Library/Saved Application State/com.microsoft.edgemac.*' +trash $LOGGED_IN_USER '~/Library/WebKit/com.microsoft.edgemac' diff --git a/server/mdm/maintainedapps/testdata/scripts/microsoft-excel_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/microsoft-excel_install.golden.sh new file mode 100644 index 000000000000..cc88f6bc2e70 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/microsoft-excel_install.golden.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") + +# install pkg files + +CHOICE_XML=$(mktemp /tmp/choice_xml) + +cat << EOF > "$CHOICE_XML" + + + + + + + attributeSetting + 0 + choiceAttribute + selected + choiceIdentifier + com.microsoft.autoupdate + + + + + +EOF + +sudo installer -pkg "$temp_dir"/Microsoft_Excel_16.88.24081116_Installer.pkg -target / -applyChoiceChangesXML "$CHOICE_XML" + diff --git a/server/mdm/maintainedapps/testdata/scripts/microsoft-excel_uninstall.golden.sh b/server/mdm/maintainedapps/testdata/scripts/microsoft-excel_uninstall.golden.sh new file mode 100644 index 000000000000..170c57073258 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/microsoft-excel_uninstall.golden.sh @@ -0,0 +1,121 @@ +#!/bin/sh + +# variables +LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }') +# functions + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} + + +remove_launchctl_service() { + local service="$1" + local booleans=("true" "false") + local plist_status + local paths + local sudo + + echo "Removing launchctl service ${service}" + + for sudo in "${booleans[@]}"; do + plist_status=$(launchctl list "${service}" 2>/dev/null) + + if [[ $plist_status == \{* ]]; then + if [[ $sudo == "true" ]]; then + sudo launchctl remove "${service}" + else + launchctl remove "${service}" + fi + sleep 1 + fi + + paths=( + "/Library/LaunchAgents/${service}.plist" + "/Library/LaunchDaemons/${service}.plist" + ) + + # if not using sudo, prepend the home directory to the paths + if [[ $sudo == "false" ]]; then + for i in "${!paths[@]}"; do + paths[i]="${HOME}${paths[i]}" + done + fi + + for path in "${paths[@]}"; do + if [[ -e "$path" ]]; then + if [[ $sudo == "true" ]]; then + sudo rm -f -- "$path" + else + rm -f -- "$path" + fi + fi + done + done +} + +trash() { + local logged_in_user="$1" + local target_file="$2" + local timestamp="$(date +%Y-%m-%d-%s)" + + # replace ~ with /Users/$logged_in_user + if [[ "$target_file" == ~* ]]; then + target_file="/Users/$logged_in_user${target_file:1}" + fi + + local trash="/Users/$logged_in_user/.Trash" + local file_name="$(basename "${target_file}")" + + if [[ -e "$target_file" ]]; then + echo "removing $target_file." + mv -f "$target_file" "$trash/${file_name}_${timestamp}" + else + echo "$target_file doesn't exist." + fi +} + +remove_launchctl_service 'com.microsoft.office.licensingV2.helper' +quit_application 'com.microsoft.autoupdate2' +sudo pkgutil --forget 'com.microsoft.package.Microsoft_Excel.app' +sudo pkgutil --forget 'com.microsoft.pkg.licensing' +trash $LOGGED_IN_USER '~/Library/Application Scripts/com.microsoft.Excel' +trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.microsoft.excel.sfl*' +trash $LOGGED_IN_USER '~/Library/Caches/com.microsoft.Excel' +trash $LOGGED_IN_USER '~/Library/Containers/com.microsoft.Excel' +trash $LOGGED_IN_USER '~/Library/Preferences/com.microsoft.Excel.plist' +trash $LOGGED_IN_USER '~/Library/Saved Application State/com.microsoft.Excel.savedState' +trash $LOGGED_IN_USER '~/Library/Webkit/com.microsoft.Excel' diff --git a/server/mdm/maintainedapps/testdata/scripts/microsoft-teams_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/microsoft-teams_install.golden.sh new file mode 100644 index 000000000000..58222137e17f --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/microsoft-teams_install.golden.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") + +# install pkg files + +CHOICE_XML=$(mktemp /tmp/choice_xml) + +cat << EOF > "$CHOICE_XML" + + + + + + + attributeSetting + 0 + choiceAttribute + selected + choiceIdentifier + com.microsoft.autoupdate + + + + + +EOF + +sudo installer -pkg "$temp_dir"/MicrosoftTeams.pkg -target / -applyChoiceChangesXML "$CHOICE_XML" + diff --git a/server/mdm/maintainedapps/testdata/scripts/microsoft-teams_uninstall.golden.sh b/server/mdm/maintainedapps/testdata/scripts/microsoft-teams_uninstall.golden.sh new file mode 100644 index 000000000000..c3193ccc3d45 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/microsoft-teams_uninstall.golden.sh @@ -0,0 +1,141 @@ +#!/bin/sh + +# variables +LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }') +# functions + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} + + +remove_launchctl_service() { + local service="$1" + local booleans=("true" "false") + local plist_status + local paths + local sudo + + echo "Removing launchctl service ${service}" + + for sudo in "${booleans[@]}"; do + plist_status=$(launchctl list "${service}" 2>/dev/null) + + if [[ $plist_status == \{* ]]; then + if [[ $sudo == "true" ]]; then + sudo launchctl remove "${service}" + else + launchctl remove "${service}" + fi + sleep 1 + fi + + paths=( + "/Library/LaunchAgents/${service}.plist" + "/Library/LaunchDaemons/${service}.plist" + ) + + # if not using sudo, prepend the home directory to the paths + if [[ $sudo == "false" ]]; then + for i in "${!paths[@]}"; do + paths[i]="${HOME}${paths[i]}" + done + fi + + for path in "${paths[@]}"; do + if [[ -e "$path" ]]; then + if [[ $sudo == "true" ]]; then + sudo rm -f -- "$path" + else + rm -f -- "$path" + fi + fi + done + done +} + +trash() { + local logged_in_user="$1" + local target_file="$2" + local timestamp="$(date +%Y-%m-%d-%s)" + + # replace ~ with /Users/$logged_in_user + if [[ "$target_file" == ~* ]]; then + target_file="/Users/$logged_in_user${target_file:1}" + fi + + local trash="/Users/$logged_in_user/.Trash" + local file_name="$(basename "${target_file}")" + + if [[ -e "$target_file" ]]; then + echo "removing $target_file." + mv -f "$target_file" "$trash/${file_name}_${timestamp}" + else + echo "$target_file doesn't exist." + fi +} + +remove_launchctl_service 'com.microsoft.teams.TeamsUpdaterDaemon' +quit_application 'com.microsoft.autoupdate2' +sudo pkgutil --forget 'com.microsoft.MSTeamsAudioDevice' +sudo pkgutil --forget 'com.microsoft.package.Microsoft_AutoUpdate.app' +sudo pkgutil --forget 'com.microsoft.teams2' +sudo rm -rf '/Applications/Microsoft Teams.app' +sudo rm -rf '/Library/Application Support/Microsoft/TeamsUpdaterDaemon' +sudo rm -rf '/Library/Logs/Microsoft/MSTeams' +sudo rm -rf '/Library/Logs/Microsoft/Teams' +sudo rm -rf '/Library/Preferences/com.microsoft.teams.plist' +sudo rmdir '~/Library/Application Support/Microsoft' +trash $LOGGED_IN_USER '~/Library/Application Scripts/com.microsoft.teams2' +trash $LOGGED_IN_USER '~/Library/Application Scripts/com.microsoft.teams2.launcher' +trash $LOGGED_IN_USER '~/Library/Application Scripts/com.microsoft.teams2.notificationcenter' +trash $LOGGED_IN_USER '~/Library/Application Support/com.microsoft.teams' +trash $LOGGED_IN_USER '~/Library/Application Support/Microsoft/Teams' +trash $LOGGED_IN_USER '~/Library/Application Support/Teams' +trash $LOGGED_IN_USER '~/Library/Caches/com.microsoft.teams' +trash $LOGGED_IN_USER '~/Library/Containers/com.microsoft.teams2' +trash $LOGGED_IN_USER '~/Library/Containers/com.microsoft.teams2.launcher' +trash $LOGGED_IN_USER '~/Library/Containers/com.microsoft.teams2.notificationcenter' +trash $LOGGED_IN_USER '~/Library/Cookies/com.microsoft.teams.binarycookies' +trash $LOGGED_IN_USER '~/Library/Group Containers/*.com.microsoft.teams' +trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.microsoft.teams' +trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.microsoft.teams.binarycookies' +trash $LOGGED_IN_USER '~/Library/Logs/Microsoft Teams Helper (Renderer)' +trash $LOGGED_IN_USER '~/Library/Logs/Microsoft Teams' +trash $LOGGED_IN_USER '~/Library/Preferences/com.microsoft.teams.plist' +trash $LOGGED_IN_USER '~/Library/Saved Application State/com.microsoft.teams.savedState' +trash $LOGGED_IN_USER '~/Library/Saved Application State/com.microsoft.teams2.savedState' +trash $LOGGED_IN_USER '~/Library/WebKit/com.microsoft.teams' diff --git a/server/mdm/maintainedapps/testdata/scripts/microsoft-word_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/microsoft-word_install.golden.sh new file mode 100644 index 000000000000..5686532766d7 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/microsoft-word_install.golden.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") + +# install pkg files + +CHOICE_XML=$(mktemp /tmp/choice_xml) + +cat << EOF > "$CHOICE_XML" + + + + + + + attributeSetting + 0 + choiceAttribute + selected + choiceIdentifier + com.microsoft.autoupdate + + + + + +EOF + +sudo installer -pkg "$temp_dir"/Microsoft_Word_16.88.24081116_Installer.pkg -target / -applyChoiceChangesXML "$CHOICE_XML" + diff --git a/server/mdm/maintainedapps/testdata/scripts/microsoft-word_uninstall.golden.sh b/server/mdm/maintainedapps/testdata/scripts/microsoft-word_uninstall.golden.sh new file mode 100644 index 000000000000..312f00d1eab4 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/microsoft-word_uninstall.golden.sh @@ -0,0 +1,120 @@ +#!/bin/sh + +# variables +LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }') +# functions + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} + + +remove_launchctl_service() { + local service="$1" + local booleans=("true" "false") + local plist_status + local paths + local sudo + + echo "Removing launchctl service ${service}" + + for sudo in "${booleans[@]}"; do + plist_status=$(launchctl list "${service}" 2>/dev/null) + + if [[ $plist_status == \{* ]]; then + if [[ $sudo == "true" ]]; then + sudo launchctl remove "${service}" + else + launchctl remove "${service}" + fi + sleep 1 + fi + + paths=( + "/Library/LaunchAgents/${service}.plist" + "/Library/LaunchDaemons/${service}.plist" + ) + + # if not using sudo, prepend the home directory to the paths + if [[ $sudo == "false" ]]; then + for i in "${!paths[@]}"; do + paths[i]="${HOME}${paths[i]}" + done + fi + + for path in "${paths[@]}"; do + if [[ -e "$path" ]]; then + if [[ $sudo == "true" ]]; then + sudo rm -f -- "$path" + else + rm -f -- "$path" + fi + fi + done + done +} + +trash() { + local logged_in_user="$1" + local target_file="$2" + local timestamp="$(date +%Y-%m-%d-%s)" + + # replace ~ with /Users/$logged_in_user + if [[ "$target_file" == ~* ]]; then + target_file="/Users/$logged_in_user${target_file:1}" + fi + + local trash="/Users/$logged_in_user/.Trash" + local file_name="$(basename "${target_file}")" + + if [[ -e "$target_file" ]]; then + echo "removing $target_file." + mv -f "$target_file" "$trash/${file_name}_${timestamp}" + else + echo "$target_file doesn't exist." + fi +} + +remove_launchctl_service 'com.microsoft.office.licensingV2.helper' +quit_application 'com.microsoft.autoupdate2' +sudo pkgutil --forget 'com.microsoft.package.Microsoft_Word.app' +sudo pkgutil --forget 'com.microsoft.pkg.licensing' +trash $LOGGED_IN_USER '~/Library/Application Scripts/com.microsoft.Word' +trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.microsoft.word.sfl*' +trash $LOGGED_IN_USER '~/Library/Application Support/CrashReporter/Microsoft Word_*.plist' +trash $LOGGED_IN_USER '~/Library/Containers/com.microsoft.Word' +trash $LOGGED_IN_USER '~/Library/Preferences/com.microsoft.Word.plist' +trash $LOGGED_IN_USER '~/Library/Saved Application State/com.microsoft.Word.savedState' diff --git a/server/mdm/maintainedapps/testdata/scripts/notion_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/notion_install.golden.sh new file mode 100644 index 000000000000..99a374eb95e2 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/notion_install.golden.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") + +# extract contents +MOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX) +hdiutil attach -plist -nobrowse -readonly -mountpoint "$MOUNT_POINT" "$INSTALLER_PATH" +sudo cp -R "$MOUNT_POINT"/* "$TMPDIR" +hdiutil detach "$MOUNT_POINT" +# copy to the applications folder +sudo cp -R "$TMPDIR/Notion.app" "$APPDIR" diff --git a/server/mdm/maintainedapps/testdata/scripts/notion_uninstall.golden.sh b/server/mdm/maintainedapps/testdata/scripts/notion_uninstall.golden.sh new file mode 100644 index 000000000000..abbe904ee983 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/notion_uninstall.golden.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }') +# functions + +trash() { + local logged_in_user="$1" + local target_file="$2" + local timestamp="$(date +%Y-%m-%d-%s)" + + # replace ~ with /Users/$logged_in_user + if [[ "$target_file" == ~* ]]; then + target_file="/Users/$logged_in_user${target_file:1}" + fi + + local trash="/Users/$logged_in_user/.Trash" + local file_name="$(basename "${target_file}")" + + if [[ -e "$target_file" ]]; then + echo "removing $target_file." + mv -f "$target_file" "$trash/${file_name}_${timestamp}" + else + echo "$target_file doesn't exist." + fi +} + +sudo rm -rf "$APPDIR/Notion.app" +trash $LOGGED_IN_USER '~/Library/Application Support/Caches/notion-updater' +trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/notion.id.sfl*' +trash $LOGGED_IN_USER '~/Library/Application Support/Notion' +trash $LOGGED_IN_USER '~/Library/Caches/notion.id*' +trash $LOGGED_IN_USER '~/Library/Logs/Notion' +trash $LOGGED_IN_USER '~/Library/Preferences/ByHost/notion.id.*' +trash $LOGGED_IN_USER '~/Library/Preferences/notion.id.*' +trash $LOGGED_IN_USER '~/Library/Saved Application State/notion.id.savedState' +trash $LOGGED_IN_USER '~/Library/WebKit/notion.id' diff --git a/server/mdm/maintainedapps/testdata/scripts/postman_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/postman_install.golden.sh new file mode 100644 index 000000000000..1b411372b58a --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/postman_install.golden.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") + +# extract contents +unzip "$INSTALLER_PATH" -d "$TMPDIR" +# copy to the applications folder +sudo cp -R "$TMPDIR/Postman.app" "$APPDIR" diff --git a/server/mdm/maintainedapps/testdata/scripts/postman_uninstall.golden.sh b/server/mdm/maintainedapps/testdata/scripts/postman_uninstall.golden.sh new file mode 100644 index 000000000000..89e7892499c6 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/postman_uninstall.golden.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }') +# functions + +trash() { + local logged_in_user="$1" + local target_file="$2" + local timestamp="$(date +%Y-%m-%d-%s)" + + # replace ~ with /Users/$logged_in_user + if [[ "$target_file" == ~* ]]; then + target_file="/Users/$logged_in_user${target_file:1}" + fi + + local trash="/Users/$logged_in_user/.Trash" + local file_name="$(basename "${target_file}")" + + if [[ -e "$target_file" ]]; then + echo "removing $target_file." + mv -f "$target_file" "$trash/${file_name}_${timestamp}" + else + echo "$target_file doesn't exist." + fi +} + +sudo rm -rf "$APPDIR/Postman.app" +trash $LOGGED_IN_USER '~/Library/Application Support/com.postmanlabs.mac.ShipIt' +trash $LOGGED_IN_USER '~/Library/Application Support/Postman' +trash $LOGGED_IN_USER '~/Library/Caches/com.postmanlabs.mac' +trash $LOGGED_IN_USER '~/Library/Caches/com.postmanlabs.mac.ShipIt' +trash $LOGGED_IN_USER '~/Library/Caches/Postman' +trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.postmanlabs.mac' +trash $LOGGED_IN_USER '~/Library/Preferences/ByHost/com.postmanlabs.mac.ShipIt.*.plist' +trash $LOGGED_IN_USER '~/Library/Preferences/com.postmanlabs.mac.plist' +trash $LOGGED_IN_USER '~/Library/Saved Application State/com.postmanlabs.mac.savedState' diff --git a/server/mdm/maintainedapps/testdata/scripts/slack_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/slack_install.golden.sh new file mode 100644 index 000000000000..6ba8f9ecd2e0 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/slack_install.golden.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") + +# extract contents +MOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX) +hdiutil attach -plist -nobrowse -readonly -mountpoint "$MOUNT_POINT" "$INSTALLER_PATH" +sudo cp -R "$MOUNT_POINT"/* "$TMPDIR" +hdiutil detach "$MOUNT_POINT" +# copy to the applications folder +sudo cp -R "$TMPDIR/Slack.app" "$APPDIR" diff --git a/server/mdm/maintainedapps/testdata/scripts/slack_uninstall.golden.sh b/server/mdm/maintainedapps/testdata/scripts/slack_uninstall.golden.sh new file mode 100644 index 000000000000..702aeb3a7ce5 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/slack_uninstall.golden.sh @@ -0,0 +1,82 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }') +# functions + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} + + +trash() { + local logged_in_user="$1" + local target_file="$2" + local timestamp="$(date +%Y-%m-%d-%s)" + + # replace ~ with /Users/$logged_in_user + if [[ "$target_file" == ~* ]]; then + target_file="/Users/$logged_in_user${target_file:1}" + fi + + local trash="/Users/$logged_in_user/.Trash" + local file_name="$(basename "${target_file}")" + + if [[ -e "$target_file" ]]; then + echo "removing $target_file." + mv -f "$target_file" "$trash/${file_name}_${timestamp}" + else + echo "$target_file doesn't exist." + fi +} + +quit_application 'com.tinyspeck.slackmacgap' +sudo rm -rf "$APPDIR/Slack.app" +trash $LOGGED_IN_USER '~/Library/Application Scripts/com.tinyspeck.slackmacgap' +trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.tinyspeck.slackmacgap.sfl*' +trash $LOGGED_IN_USER '~/Library/Application Support/Slack' +trash $LOGGED_IN_USER '~/Library/Caches/com.tinyspeck.slackmacgap*' +trash $LOGGED_IN_USER '~/Library/Containers/com.tinyspeck.slackmacgap*' +trash $LOGGED_IN_USER '~/Library/Cookies/com.tinyspeck.slackmacgap.binarycookies' +trash $LOGGED_IN_USER '~/Library/Group Containers/*.com.tinyspeck.slackmacgap' +trash $LOGGED_IN_USER '~/Library/Group Containers/*.slack' +trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.tinyspeck.slackmacgap*' +trash $LOGGED_IN_USER '~/Library/Logs/Slack' +trash $LOGGED_IN_USER '~/Library/Preferences/ByHost/com.tinyspeck.slackmacgap.ShipIt.*.plist' +trash $LOGGED_IN_USER '~/Library/Preferences/com.tinyspeck.slackmacgap*' +trash $LOGGED_IN_USER '~/Library/Saved Application State/com.tinyspeck.slackmacgap.savedState' +trash $LOGGED_IN_USER '~/Library/WebKit/com.tinyspeck.slackmacgap' diff --git a/server/mdm/maintainedapps/testdata/scripts/teamviewer_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/teamviewer_install.golden.sh new file mode 100644 index 000000000000..cc6a0b4e32eb --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/teamviewer_install.golden.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") + +# install pkg files +sudo installer -pkg "$TMPDIR/TeamViewer.pkg" -target / diff --git a/server/mdm/maintainedapps/testdata/scripts/teamviewer_uninstall.golden.sh b/server/mdm/maintainedapps/testdata/scripts/teamviewer_uninstall.golden.sh new file mode 100644 index 000000000000..6a584a8d9f86 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/teamviewer_uninstall.golden.sh @@ -0,0 +1,133 @@ +#!/bin/sh + +# variables +LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }') +# functions + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} + + +remove_launchctl_service() { + local service="$1" + local booleans=("true" "false") + local plist_status + local paths + local sudo + + echo "Removing launchctl service ${service}" + + for sudo in "${booleans[@]}"; do + plist_status=$(launchctl list "${service}" 2>/dev/null) + + if [[ $plist_status == \{* ]]; then + if [[ $sudo == "true" ]]; then + sudo launchctl remove "${service}" + else + launchctl remove "${service}" + fi + sleep 1 + fi + + paths=( + "/Library/LaunchAgents/${service}.plist" + "/Library/LaunchDaemons/${service}.plist" + ) + + # if not using sudo, prepend the home directory to the paths + if [[ $sudo == "false" ]]; then + for i in "${!paths[@]}"; do + paths[i]="${HOME}${paths[i]}" + done + fi + + for path in "${paths[@]}"; do + if [[ -e "$path" ]]; then + if [[ $sudo == "true" ]]; then + sudo rm -f -- "$path" + else + rm -f -- "$path" + fi + fi + done + done +} + +trash() { + local logged_in_user="$1" + local target_file="$2" + local timestamp="$(date +%Y-%m-%d-%s)" + + # replace ~ with /Users/$logged_in_user + if [[ "$target_file" == ~* ]]; then + target_file="/Users/$logged_in_user${target_file:1}" + fi + + local trash="/Users/$logged_in_user/.Trash" + local file_name="$(basename "${target_file}")" + + if [[ -e "$target_file" ]]; then + echo "removing $target_file." + mv -f "$target_file" "$trash/${file_name}_${timestamp}" + else + echo "$target_file doesn't exist." + fi +} + +remove_launchctl_service 'com.teamviewer.desktop' +remove_launchctl_service 'com.teamviewer.Helper' +remove_launchctl_service 'com.teamviewer.service' +remove_launchctl_service 'com.teamviewer.teamviewer' +remove_launchctl_service 'com.teamviewer.teamviewer_desktop' +remove_launchctl_service 'com.teamviewer.teamviewer_service' +remove_launchctl_service 'com.teamviewer.UninstallerHelper' +remove_launchctl_service 'com.teamviewer.UninstallerWatcher' +quit_application 'com.teamviewer.TeamViewer' +quit_application 'com.teamviewer.TeamViewerUninstaller' +sudo pkgutil --forget 'com.teamviewer.AuthorizationPlugin' +sudo pkgutil --forget 'com.teamviewer.remoteaudiodriver' +sudo pkgutil --forget 'com.teamviewer.teamviewer.*' +sudo pkgutil --forget 'TeamViewerUninstaller' +sudo rm -rf '/Applications/TeamViewer.app' +sudo rm -rf '/Library/Preferences/com.teamviewer*' +trash $LOGGED_IN_USER '~/Library/Application Support/TeamViewer' +trash $LOGGED_IN_USER '~/Library/Caches/com.teamviewer.TeamViewer' +trash $LOGGED_IN_USER '~/Library/Caches/TeamViewer' +trash $LOGGED_IN_USER '~/Library/Cookies/com.teamviewer.TeamViewer.binarycookies' +trash $LOGGED_IN_USER '~/Library/Logs/TeamViewer' +trash $LOGGED_IN_USER '~/Library/Preferences/com.teamviewer*' +trash $LOGGED_IN_USER '~/Library/Saved Application State/com.teamviewer.TeamViewer.savedState' diff --git a/server/mdm/maintainedapps/testdata/scripts/visual-studio-code_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/visual-studio-code_install.golden.sh new file mode 100644 index 000000000000..ba1b1e7158a3 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/visual-studio-code_install.golden.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") + +# extract contents +unzip "$INSTALLER_PATH" -d "$TMPDIR" +# copy to the applications folder +sudo cp -R "$TMPDIR/Visual Studio Code.app" "$APPDIR" diff --git a/server/mdm/maintainedapps/testdata/scripts/visual-studio-code_uninstall.golden.sh b/server/mdm/maintainedapps/testdata/scripts/visual-studio-code_uninstall.golden.sh new file mode 100644 index 000000000000..952e5141f306 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/visual-studio-code_uninstall.golden.sh @@ -0,0 +1,124 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }') +# functions + +quit_application() { + local bundle_id="$1" + local timeout_duration=10 + + # check if the application is running + if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then + return + fi + + local console_user + console_user=$(stat -f "%Su" /dev/console) + if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then + echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'." + return + fi + + echo "Quitting application '$bundle_id'..." + + # try to quit the application within the timeout period + local quit_success=false + SECONDS=0 + while (( SECONDS < timeout_duration )); do + if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then + if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then + echo "Application '$bundle_id' quit successfully." + quit_success=true + break + fi + fi + sleep 1 + done + + if [[ "$quit_success" = false ]]; then + echo "Application '$bundle_id' did not quit." + fi +} + + +remove_launchctl_service() { + local service="$1" + local booleans=("true" "false") + local plist_status + local paths + local sudo + + echo "Removing launchctl service ${service}" + + for sudo in "${booleans[@]}"; do + plist_status=$(launchctl list "${service}" 2>/dev/null) + + if [[ $plist_status == \{* ]]; then + if [[ $sudo == "true" ]]; then + sudo launchctl remove "${service}" + else + launchctl remove "${service}" + fi + sleep 1 + fi + + paths=( + "/Library/LaunchAgents/${service}.plist" + "/Library/LaunchDaemons/${service}.plist" + ) + + # if not using sudo, prepend the home directory to the paths + if [[ $sudo == "false" ]]; then + for i in "${!paths[@]}"; do + paths[i]="${HOME}${paths[i]}" + done + fi + + for path in "${paths[@]}"; do + if [[ -e "$path" ]]; then + if [[ $sudo == "true" ]]; then + sudo rm -f -- "$path" + else + rm -f -- "$path" + fi + fi + done + done +} + +trash() { + local logged_in_user="$1" + local target_file="$2" + local timestamp="$(date +%Y-%m-%d-%s)" + + # replace ~ with /Users/$logged_in_user + if [[ "$target_file" == ~* ]]; then + target_file="/Users/$logged_in_user${target_file:1}" + fi + + local trash="/Users/$logged_in_user/.Trash" + local file_name="$(basename "${target_file}")" + + if [[ -e "$target_file" ]]; then + echo "removing $target_file." + mv -f "$target_file" "$trash/${file_name}_${timestamp}" + else + echo "$target_file doesn't exist." + fi +} + +remove_launchctl_service 'com.microsoft.VSCode.ShipIt' +quit_application 'com.microsoft.VSCode' +sudo rm -rf "$APPDIR/Visual Studio Code.app" +trash $LOGGED_IN_USER '~/.vscode' +trash $LOGGED_IN_USER '~/Library/Application Support/Code' +trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.microsoft.vscode.sfl*' +trash $LOGGED_IN_USER '~/Library/Caches/com.microsoft.VSCode' +trash $LOGGED_IN_USER '~/Library/Caches/com.microsoft.VSCode.ShipIt' +trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.microsoft.VSCode' +trash $LOGGED_IN_USER '~/Library/Preferences/ByHost/com.microsoft.VSCode.ShipIt.*.plist' +trash $LOGGED_IN_USER '~/Library/Preferences/com.microsoft.VSCode.helper.plist' +trash $LOGGED_IN_USER '~/Library/Preferences/com.microsoft.VSCode.plist' +trash $LOGGED_IN_USER '~/Library/Saved Application State/com.microsoft.VSCode.savedState' diff --git a/server/mdm/maintainedapps/testdata/scripts/whatsapp_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/whatsapp_install.golden.sh new file mode 100644 index 000000000000..6a7bfccdf474 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/whatsapp_install.golden.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") + +# extract contents +unzip "$INSTALLER_PATH" -d "$TMPDIR" +# copy to the applications folder +sudo cp -R "$TMPDIR/WhatsApp.app" "$APPDIR" diff --git a/server/mdm/maintainedapps/testdata/scripts/whatsapp_uninstall.golden.sh b/server/mdm/maintainedapps/testdata/scripts/whatsapp_uninstall.golden.sh new file mode 100644 index 000000000000..427218681d9c --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/whatsapp_uninstall.golden.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }') +# functions + +trash() { + local logged_in_user="$1" + local target_file="$2" + local timestamp="$(date +%Y-%m-%d-%s)" + + # replace ~ with /Users/$logged_in_user + if [[ "$target_file" == ~* ]]; then + target_file="/Users/$logged_in_user${target_file:1}" + fi + + local trash="/Users/$logged_in_user/.Trash" + local file_name="$(basename "${target_file}")" + + if [[ -e "$target_file" ]]; then + echo "removing $target_file." + mv -f "$target_file" "$trash/${file_name}_${timestamp}" + else + echo "$target_file doesn't exist." + fi +} + +sudo rm -rf "$APPDIR/WhatsApp.app" +trash $LOGGED_IN_USER '~/Library/Application Scripts/net.whatsapp.WhatsApp*' +trash $LOGGED_IN_USER '~/Library/Caches/net.whatsapp.WhatsApp' +trash $LOGGED_IN_USER '~/Library/Containers/net.whatsapp.WhatsApp*' +trash $LOGGED_IN_USER '~/Library/Group Containers/group.com.facebook.family' +trash $LOGGED_IN_USER '~/Library/Group Containers/group.net.whatsapp*' +trash $LOGGED_IN_USER '~/Library/Saved Application State/net.whatsapp.WhatsApp.savedState' diff --git a/server/mdm/maintainedapps/testdata/scripts/zoom_install.golden.sh b/server/mdm/maintainedapps/testdata/scripts/zoom_install.golden.sh new file mode 100644 index 000000000000..da33a9173972 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/zoom_install.golden.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +# variables +APPDIR="/Applications/" +TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)") + +# install pkg files +sudo installer -pkg "$TMPDIR/zoomusInstallerFull.pkg" -target / diff --git a/server/mdm/maintainedapps/testdata/scripts/zoom_uninstall.golden.sh b/server/mdm/maintainedapps/testdata/scripts/zoom_uninstall.golden.sh new file mode 100644 index 000000000000..b0a5a94fb743 --- /dev/null +++ b/server/mdm/maintainedapps/testdata/scripts/zoom_uninstall.golden.sh @@ -0,0 +1,146 @@ +#!/bin/sh + +# variables +LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }') +# functions + +remove_launchctl_service() { + local service="$1" + local booleans=("true" "false") + local plist_status + local paths + local sudo + + echo "Removing launchctl service ${service}" + + for sudo in "${booleans[@]}"; do + plist_status=$(launchctl list "${service}" 2>/dev/null) + + if [[ $plist_status == \{* ]]; then + if [[ $sudo == "true" ]]; then + sudo launchctl remove "${service}" + else + launchctl remove "${service}" + fi + sleep 1 + fi + + paths=( + "/Library/LaunchAgents/${service}.plist" + "/Library/LaunchDaemons/${service}.plist" + ) + + # if not using sudo, prepend the home directory to the paths + if [[ $sudo == "false" ]]; then + for i in "${!paths[@]}"; do + paths[i]="${HOME}${paths[i]}" + done + fi + + for path in "${paths[@]}"; do + if [[ -e "$path" ]]; then + if [[ $sudo == "true" ]]; then + sudo rm -f -- "$path" + else + rm -f -- "$path" + fi + fi + done + done +} + +send_signal() { + local signal="$1" + local bundle_id="$2" + local logged_in_user="$3" + local logged_in_uid pids + + if [ -z "$signal" ] || [ -z "$bundle_id" ] || [ -z "$logged_in_user" ]; then + echo "Usage: uninstall_signal " + return 1 + fi + + logged_in_uid=$(id -u "$logged_in_user") + if [ -z "$logged_in_uid" ]; then + echo "Could not find UID for user '$logged_in_user'." + return 1 + fi + + echo "Signalling '$signal' to application ID '$bundle_id' for user '$logged_in_user'" + + pids=$(/bin/launchctl asuser "$logged_in_uid" sudo -iu "$logged_in_user" /bin/launchctl list | awk -v bundle_id="$bundle_id" ' + $3 ~ bundle_id { print $1 }') + + if [ -z "$pids" ]; then + echo "No processes found for bundle ID '$bundle_id'." + return 0 + fi + + echo "Unix PIDs are $pids for processes with bundle identifier $bundle_id" + for pid in $pids; do + if kill -s "$signal" "$pid" 2>/dev/null; then + echo "Successfully signaled PID $pid with signal $signal." + else + echo "Failed to kill PID $pid with signal $signal. Check permissions." + fi + done + + sleep 3 +} + +trash() { + local logged_in_user="$1" + local target_file="$2" + local timestamp="$(date +%Y-%m-%d-%s)" + + # replace ~ with /Users/$logged_in_user + if [[ "$target_file" == ~* ]]; then + target_file="/Users/$logged_in_user${target_file:1}" + fi + + local trash="/Users/$logged_in_user/.Trash" + local file_name="$(basename "${target_file}")" + + if [[ -e "$target_file" ]]; then + echo "removing $target_file." + mv -f "$target_file" "$trash/${file_name}_${timestamp}" + else + echo "$target_file doesn't exist." + fi +} + +remove_launchctl_service 'us.zoom.ZoomDaemon' +send_signal 'KILL' 'us.zoom.xos' "$LOGGED_IN_USER" +sudo pkgutil --forget 'us.zoom.pkg.videomeeting' +sudo rm -rf '/Applications/zoom.us.app' +sudo rm -rf '/Library/Internet Plug-Ins/ZoomUsPlugIn.plugin' +sudo rm -rf '/Library/Logs/DiagnosticReports/zoom.us*' +sudo rm -rf '/Library/PrivilegedHelperTools/us.zoom.ZoomDaemon' +trash $LOGGED_IN_USER '~/.zoomus' +trash $LOGGED_IN_USER '~/Desktop/Zoom' +trash $LOGGED_IN_USER '~/Documents/Zoom' +trash $LOGGED_IN_USER '~/Library/Application Scripts/*.ZoomClient3rd' +trash $LOGGED_IN_USER '~/Library/Application Support/CloudDocs/session/containers/iCloud.us.zoom.videomeetings' +trash $LOGGED_IN_USER '~/Library/Application Support/CloudDocs/session/containers/iCloud.us.zoom.videomeetings.plist' +trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/us.zoom*.sfl*' +trash $LOGGED_IN_USER '~/Library/Application Support/CrashReporter/zoom.us*' +trash $LOGGED_IN_USER '~/Library/Application Support/zoom.us' +trash $LOGGED_IN_USER '~/Library/Caches/us.zoom.xos' +trash $LOGGED_IN_USER '~/Library/Cookies/us.zoom.xos.binarycookies' +trash $LOGGED_IN_USER '~/Library/Group Containers/*.ZoomClient3rd' +trash $LOGGED_IN_USER '~/Library/HTTPStorages/us.zoom.xos' +trash $LOGGED_IN_USER '~/Library/HTTPStorages/us.zoom.xos.binarycookies' +trash $LOGGED_IN_USER '~/Library/Internet Plug-Ins/ZoomUsPlugIn.plugin' +trash $LOGGED_IN_USER '~/Library/Logs/zoom.us' +trash $LOGGED_IN_USER '~/Library/Logs/zoominstall.log' +trash $LOGGED_IN_USER '~/Library/Logs/ZoomPhone' +trash $LOGGED_IN_USER '~/Library/Preferences/us.zoom.airhost.plist' +trash $LOGGED_IN_USER '~/Library/Preferences/us.zoom.caphost.plist' +trash $LOGGED_IN_USER '~/Library/Preferences/us.zoom.Transcode.plist' +trash $LOGGED_IN_USER '~/Library/Preferences/us.zoom.xos.Hotkey.plist' +trash $LOGGED_IN_USER '~/Library/Preferences/us.zoom.xos.plist' +trash $LOGGED_IN_USER '~/Library/Preferences/us.zoom.ZoomAutoUpdater.plist' +trash $LOGGED_IN_USER '~/Library/Preferences/us.zoom.ZoomClips.plist' +trash $LOGGED_IN_USER '~/Library/Preferences/ZoomChat.plist' +trash $LOGGED_IN_USER '~/Library/Saved Application State/us.zoom.xos.savedState' +trash $LOGGED_IN_USER '~/Library/WebKit/us.zoom.xos' diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go index 857299c8c9c9..87f8b3b2f407 100644 --- a/server/service/integration_enterprise_test.go +++ b/server/service/integration_enterprise_test.go @@ -14576,8 +14576,10 @@ func (s *integrationEnterpriseTestSuite) TestMaintainedApps() { s.DoJSON(http.MethodGet, fmt.Sprintf("/api/latest/fleet/software/fleet_maintained_apps/%d", listMAResp.FleetMaintainedApps[0].ID), getFleetMaintainedAppRequest{}, http.StatusOK, &getMAResp) // TODO this will change when actual install scripts are created. actualApp := listMAResp.FleetMaintainedApps[0] - actualApp.InstallScript = "install" - actualApp.UninstallScript = "uninstall" + require.NotEmpty(t, getMAResp.FleetMaintainedApp.InstallScript) + require.NotEmpty(t, getMAResp.FleetMaintainedApp.UninstallScript) + getMAResp.FleetMaintainedApp.InstallScript = "" + getMAResp.FleetMaintainedApp.UninstallScript = "" require.Equal(t, actualApp, *getMAResp.FleetMaintainedApp) // Add an ingested app to the team diff --git a/server/service/maintained_apps.go b/server/service/maintained_apps.go index e4d0309318e4..1dd11dda772c 100644 --- a/server/service/maintained_apps.go +++ b/server/service/maintained_apps.go @@ -15,6 +15,7 @@ type addFleetMaintainedAppRequest struct { PreInstallQuery string `json:"pre_install_query"` PostInstallScript string `json:"post_install_script"` SelfService bool `json:"self_service"` + UninstallScript string `json:"uninstall_script"` } type addFleetMaintainedAppResponse struct { @@ -27,7 +28,16 @@ func addFleetMaintainedAppEndpoint(ctx context.Context, request interface{}, svc req := request.(*addFleetMaintainedAppRequest) ctx, cancel := context.WithTimeout(ctx, maintainedapps.InstallerTimeout) defer cancel() - err := svc.AddFleetMaintainedApp(ctx, req.TeamID, req.AppID, req.InstallScript, req.PreInstallQuery, req.PostInstallScript, req.SelfService) + err := svc.AddFleetMaintainedApp( + ctx, + req.TeamID, + req.AppID, + req.InstallScript, + req.PreInstallQuery, + req.PostInstallScript, + req.UninstallScript, + req.SelfService, + ) if err != nil { if errors.Is(err, context.DeadlineExceeded) { err = fleet.NewGatewayTimeoutError("Couldn't upload. Request timeout. Please make sure your server and load balancer timeout is long enough.", err) @@ -38,7 +48,7 @@ func addFleetMaintainedAppEndpoint(ctx context.Context, request interface{}, svc return &addFleetMaintainedAppResponse{}, nil } -func (svc *Service) AddFleetMaintainedApp(ctx context.Context, teamID *uint, appID uint, installScript, preInstallQuery, postInstallScript string, selfService bool) error { +func (svc *Service) AddFleetMaintainedApp(ctx context.Context, teamID *uint, appID uint, installScript, preInstallQuery, postInstallScript, uninstallScript string, selfService bool) error { // skipauth: No authorization check needed due to implementation returning // only license error. svc.authz.SkipAuthorization(ctx) From 875d5dd2184e770ba6d9ab9cbff94a0624279a9a Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 3 Oct 2024 12:50:06 -0500 Subject: [PATCH 058/385] Website: update /better page (#22621) Closes: #22611 Changes: - Updated the content of the /better page to match the latest wireframes. --- .../images/better-hero-image-468x380@2x.png | Bin 0 -> 108170 bytes website/assets/styles/pages/transparency.less | 80 +++++++++--------- website/views/pages/transparency.ejs | 26 ++++-- 3 files changed, 57 insertions(+), 49 deletions(-) create mode 100644 website/assets/images/better-hero-image-468x380@2x.png diff --git a/website/assets/images/better-hero-image-468x380@2x.png b/website/assets/images/better-hero-image-468x380@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b90b0079829233ecb347f036ee2857f58ec78970 GIT binary patch literal 108170 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%0A~+{cPHimmF{QhI;mSbzRcYJg8kNFAhiD^(_ab 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�?V-odmb8H;c-XINk5 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

=E={)yd(n0q`CM%LV41-M&hgyLknJS!!C9s) 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%0DJTIhRa>j1z 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

-

What can Fleet access on my device?

-

End users deserve to know what their employer can see and do on their laptops. Here’s what Fleet can manage on your device:

- +

Why is Fleet on my computer?

+

Fleet makes it a little easier for your IT team to keep your computer healthy without interfering with your work.

-
+
+
+

How does it affect me?

+

Your IT team has to maintain your computer and keep it compliant with with a bunch of security requirements. But they also realize you have a job to do. So they want to be as un-intrusive as possible.

+

That’s why they chose Fleet. It’s compatible with everything, including Linux, so it doesn’t limit what operating system you use. And it’s open source, meaning if you want to, you can take it apart and see what it’s doing to your computer.

+
+
+ Information about a users device in Fleet. +
+
+
+ +
+
+

What personal information can Fleet see?

+

Here’s what Fleet can manage on your computer:

@@ -111,9 +124,6 @@

-
-
-
From 3571db8ef8a29530379fea512e8165a95587de5c Mon Sep 17 00:00:00 2001 From: RachelElysia <71795832+RachelElysia@users.noreply.github.com> Date: Thu, 3 Oct 2024 10:53:11 -0700 Subject: [PATCH 059/385] Fleet UI: Remove extra word (#22622) --- .../cards/MdmSettings/WindowsMdmPage/WindowsMdmPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/WindowsMdmPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/WindowsMdmPage.tsx index fe26cb9a7558..e9f76b8e30fb 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/WindowsMdmPage.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/WindowsMdmPage.tsx @@ -98,7 +98,7 @@ const WindowsMdmOffContent = ({ router }: IWindowsMdmOffContentProps) => {

MDM will no longer be turned on for Windows hosts that enroll to Fleet.

-

Hosts with MDM already turned on MDM will not have MDM removed.

+

Hosts with MDM already turned on will not have MDM removed.

); From 3813a49db819709d1653edd8fc5e66fce091b2eb Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 3 Oct 2024 13:14:50 -0500 Subject: [PATCH 060/385] Website: Small /better page update (#22625) Changes: - Updated a heading on the /better page to be centered on smaller screens --- website/views/pages/transparency.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/views/pages/transparency.ejs b/website/views/pages/transparency.ejs index 1b181ed74cd0..44f9b2854e03 100644 --- a/website/views/pages/transparency.ejs +++ b/website/views/pages/transparency.ejs @@ -19,7 +19,7 @@
-

What personal information can Fleet see?

+

What personal information can Fleet see?

Here’s what Fleet can manage on your computer:

From d1dde4f190539e7f5cefbc667bf02de0b70837c1 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 3 Oct 2024 13:49:42 -0500 Subject: [PATCH 061/385] Website: update save-questionnaire-progress (#22586) Closes: https://github.com/fleetdm/confidential/issues/8237 Closes: https://github.com/fleetdm/confidential/issues/8236 Changes: - Updated `save-questionnaire-progress` to update CRM records whenever a user submits a step of the get started questionnaire. (This previously only happened when a user's psychological stage changed) - Changed the name of the 'cross-platform-mdm' step to 'message-about-cross-platform-mdm' --- .../save-questionnaire-progress.js | 45 ++++++++++--------- website/assets/js/pages/start.page.js | 10 ++--- website/views/pages/start.ejs | 4 +- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/website/api/controllers/save-questionnaire-progress.js b/website/api/controllers/save-questionnaire-progress.js index 62faf3792994..2571eb1fd1b1 100644 --- a/website/api/controllers/save-questionnaire-progress.js +++ b/website/api/controllers/save-questionnaire-progress.js @@ -21,7 +21,7 @@ module.exports = { 'what-does-your-team-manage-eo-it', 'what-does-your-team-manage-vm', 'what-do-you-manage-mdm', - 'cross-platform-mdm', + 'message-about-cross-platform-mdm', 'is-it-any-good', 'what-did-you-think', 'deploy-fleet-in-your-environment', @@ -57,7 +57,6 @@ module.exports = { } else {// other wise clone it from the user record. questionnaireProgress = _.clone(userRecord.getStartedQuestionnaireAnswers); } - // Tease out what liur buying situation will now be (or is and was, if it's not changing) let primaryBuyingSituation = formData.primaryBuyingSituation === undefined ? this.req.me.primaryBuyingSituation : formData.primaryBuyingSituation; @@ -212,11 +211,23 @@ module.exports = { questionnaireProgressAsAFormattedString = JSON.stringify(getStartedProgress) .replace(/[\{|\}|"]/g, '')// Remove the curly braces and quotation marks wrapping JSON objects .replace(/,/g, '\n')// Replace commas with newlines. - .replace(/:\w+:/g, ':\t');// Replace the key from the formData with a color and tab, (e.g., what-are-you-using-fleet-for:primaryBuyingSituation:eo-security, » what-are-you-using-fleet-for: eo-security) + .replace(/:\w+:/g, ':\t')// Replace the key from the formData with a colon and tab, (e.g., what-are-you-using-fleet-for:primaryBuyingSituation:eo-security, » what-are-you-using-fleet-for: eo-security) + .replace(/(true)/g, 'step completed');// Replace any "true" answers with "step completed". } catch(err){ sails.log.warn(`When converting a user's (email: ${this.req.me.emailAddress}) getStartedQuestionnaireAnswers to a formatted string to send to the CRM, and error occurred`, err); } - // Only update CRM records if the user's psychological stage changes. + // Create a dictionary of values to send to the CRM for this user. + let contactInformation = { + emailAddress: this.req.me.emailAddress, + firstName: this.req.me.firstName, + lastName: this.req.me.lastName, + primaryBuyingSituation: primaryBuyingSituation === 'eo-security' ? 'Endpoint operations - Security' : primaryBuyingSituation === 'eo-it' ? 'Endpoint operations - IT' : primaryBuyingSituation === 'mdm' ? 'Device management (MDM)' : primaryBuyingSituation === 'vm' ? 'Vulnerability management' : undefined, + organization: this.req.me.organization, + psychologicalStage, + getStartedResponses: questionnaireProgressAsAFormattedString, + contactSource: 'Website - Sign up', + }; + // If the user's psychologicalStage changes, add a psychologicalStageChangeReason to the contactInformation dictionary that we'll update the CRM record with. if(psychologicalStage !== userRecord.psychologicalStage) { let psychologicalStageChangeReason = 'Website - Organic start flow'; // Default psystageChangeReason to "Website - Organic start flow" if(this.req.session.adAttributionString && this.req.session.visitedSiteFromAdAt) { @@ -226,25 +237,17 @@ module.exports = { psychologicalStageChangeReason = this.req.session.adAttributionString; } } - // Update the psychologicalStageLastChangedAt timestamp if the user's psychological stage + contactInformation.psychologicalStageChangeReason = psychologicalStageChangeReason; + // Update the psychologicalStageLastChangedAt timestamp if the user's psychological stage has changed (otherwise this is set to the current value) psychologicalStageLastChangedAt = Date.now(); - sails.helpers.salesforce.updateOrCreateContactAndAccount.with({ - emailAddress: this.req.me.emailAddress, - firstName: this.req.me.firstName, - lastName: this.req.me.lastName, - primaryBuyingSituation: primaryBuyingSituation === 'eo-security' ? 'Endpoint operations - Security' : primaryBuyingSituation === 'eo-it' ? 'Endpoint operations - IT' : primaryBuyingSituation === 'mdm' ? 'Device management (MDM)' : primaryBuyingSituation === 'vm' ? 'Vulnerability management' : undefined, - organization: this.req.me.organization, - psychologicalStage, - psychologicalStageChangeReason, - getStartedResponses: questionnaireProgressAsAFormattedString, - contactSource: 'Website - Sign up', - }).exec((err)=>{ - if(err){ - sails.log.warn(`Background task failed: When a user (email: ${this.req.me.emailAddress} submitted a step of the get started questionnaire, a Contact and Account record could not be created/updated in the CRM.`, err); - } - return; - }); }//fi + // Update the CRM record for this user. + sails.helpers.salesforce.updateOrCreateContactAndAccount.with(contactInformation).exec((err)=>{ + if(err){ + sails.log.warn(`Background task failed: When a user (email: ${this.req.me.emailAddress} submitted a step of the get started questionnaire, a Contact and Account record could not be created/updated in the CRM.`, err); + } + return; + }); // Update the user's database model. await User.updateOne({id: userRecord.id}) .set({ diff --git a/website/assets/js/pages/start.page.js b/website/assets/js/pages/start.page.js index 3b95e7218bc3..50b3dbdc21ab 100644 --- a/website/assets/js/pages/start.page.js +++ b/website/assets/js/pages/start.page.js @@ -18,7 +18,7 @@ parasails.registerPage('start', { 'what-does-your-team-manage-eo-it': {}, 'what-does-your-team-manage-vm': {}, 'what-do-you-manage-mdm': {}, - 'cross-platform-mdm': {stepCompleted: true}, + 'message-about-cross-platform-mdm': {stepCompleted: true}, 'is-it-any-good': {stepCompleted: true}, 'what-did-you-think': {}, 'deploy-fleet-in-your-environment': {stepCompleted: true}, @@ -198,10 +198,10 @@ parasails.registerPage('start', { } else if(primaryBuyingSituation === 'vm') { this.currentStep = 'what-does-your-team-manage-vm'; } else if(primaryBuyingSituation === 'mdm') { - this.currentStep = 'cross-platform-mdm'; + this.currentStep = 'message-about-cross-platform-mdm'; } break; - case 'cross-platform-mdm': + case 'message-about-cross-platform-mdm': this.currentStep = 'what-do-you-manage-mdm'; break; case 'lets-talk-to-your-team': @@ -300,9 +300,9 @@ parasails.registerPage('start', { nextStepInForm = 'is-it-any-good'; break; case 'what-do-you-manage-mdm': - nextStepInForm = 'cross-platform-mdm'; + nextStepInForm = 'message-about-cross-platform-mdm'; break; - case 'cross-platform-mdm': + case 'message-about-cross-platform-mdm': nextStepInForm = 'is-it-any-good'; break; case 'is-it-any-good': diff --git a/website/views/pages/start.ejs b/website/views/pages/start.ejs index d012d01748af..8326f87bf116 100644 --- a/website/views/pages/start.ejs +++ b/website/views/pages/start.ejs @@ -399,8 +399,8 @@ <%// ╔═╗╦═╗╔═╗╔═╗╔═╗ ╔═╗╦ ╔═╗╔╦╗╔═╗╔═╗╦═╗╔╦╗ ╔╦╗╔╦╗╔╦╗ // ║ ╠╦╝║ ║╚═╗╚═╗ ╠═╝║ ╠═╣ ║ ╠╣ ║ ║╠╦╝║║║ ║║║ ║║║║║ // ╚═╝╩╚═╚═╝╚═╝╚═╝ ╩ ╩═╝╩ ╩ ╩ ╚ ╚═╝╩╚═╩ ╩ ╩ ╩═╩╝╩ ╩%> -
- +
+
🚀 From 47c1f29a2ca5297d58e057576eede0b4445d423e Mon Sep 17 00:00:00 2001 From: Sam Pfluger <108141731+Sampfluger88@users.noreply.github.com> Date: Thu, 3 Oct 2024 14:26:27 -0500 Subject: [PATCH 062/385] Update leadership.md (#22626) --- handbook/company/leadership.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/handbook/company/leadership.md b/handbook/company/leadership.md index 90c9d0b2a70c..2e83a841b9c9 100644 --- a/handbook/company/leadership.md +++ b/handbook/company/leadership.md @@ -414,9 +414,9 @@ Although it's sad to see someone go, Fleet understands that not everything is me 4. **CEO**: The CEO will make an announcement during the "🌈 Weekly Update" post on Friday in the `#general` channel on Slack. -## Changing someone's position +## Request a role change for a Fleetie -From time to time, someone's job title changes. Use the following steps to change someone's position: +From time to time, someone's job title changes. The hiring manager can use the following steps to change someone's position: 1. Create Slack channel: Create a private "#YYYY-change-title-for-xxxxxx" Slack channel (where "xxxxxx" is the Fleetie's name and YYYY is the current year) for discussion and invite the CEO and Head of Digital Experience. 2. At-mention the Head of Digital Experience in the new channel with any context regarding the title change. Share any related documents with the Head of Digital Experience and the CEO. 3. After getting approval from the [Head of People](https://fleetdm.com/handbook/digital-experience#team), the Digital Experience team will take the necessary steps to [change the fleetie's job title](https://fleetdm.com/handbook/digital-experience#change-a-fleeties-job-title). From 90fa30e6c27c8fb7f443999a3e4f8b438e986ab5 Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Thu, 3 Oct 2024 16:03:54 -0400 Subject: [PATCH 063/385] Update Design sprint kickoff ritual (#22615) Co-authored-by: Sam Pfluger <108141731+Sampfluger88@users.noreply.github.com> --- handbook/product-design/product-design.rituals.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handbook/product-design/product-design.rituals.yml b/handbook/product-design/product-design.rituals.yml index fb6af51f8d50..967d7b2a75a8 100644 --- a/handbook/product-design/product-design.rituals.yml +++ b/handbook/product-design/product-design.rituals.yml @@ -16,7 +16,7 @@ task: "Design sprint kickoff" # 2024-03-06 TODO: Link to responsibility or corresponding "how to" info e.g. https://fleetdm.com/handbook/company/product-groups#making-changes startedOn: "2024-03-07" frequency: "Triweekly" - description: "Add stories prioritized during Feature fest to Drafting board, assign stories to product designers, create upcoming reference docs release branch, and align on priorities." + description: "1. Add all stories that are not estimated to the feature fest board. Remove the stories from the drafting board that we're not actively working on and notify stakeholders. 2. Record the number of dropped stories for KPIs (all user stories that did not meet the 3 week drafting timeline). 3. Retro: What went well? What could go better? What to remember for next time?" moreInfoUrl: dri: "noahtalerman" - From edd81bfd33e7f081bc1f4f184b9caacefbfff1a2 Mon Sep 17 00:00:00 2001 From: Alex Mitchell <105945793+alexmitchelliii@users.noreply.github.com> Date: Thu, 3 Oct 2024 16:51:25 -0500 Subject: [PATCH 064/385] Update communications.md (#22601) Updated link from old SOC 2 Type 1 report to latest SOC 2 Type 2 report from July 2024. --- 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 5b36e34f5191..15affb27a466 100644 --- a/handbook/company/communications.md +++ b/handbook/company/communications.md @@ -749,7 +749,7 @@ When attending a conference for Fleet, treat it [like other travel for the compa ## SOC 2 -You can find a copy of Fleet's SOC 2 report in [Google Drive](https://drive.google.com/file/d/1B-Xb4ZVmZk7Fk0IA1eCr8tCVJ-cfipid/view?usp=drivesdk). In its current form, this SOC 2 report is intended to be shared only with parties who have signed a non-disclosure agreement with Fleet. +You can find a copy of Fleet's SOC 2 report in [Google Drive](https://drive.google.com/file/d/1mUwmCUdggONULr6OjDO--QtKfDWLcrI4/view?usp=sharing). In its current form, this SOC 2 report is intended to be shared only with parties who have signed a non-disclosure agreement with Fleet. You can learn more about how Fleet approaches security in the [security handbook](https://fleetdm.com/handbook/security) or in [Fleet's trust report](https://fleetdm.com/trust). From 88331b54f2856f44e9ca3aad36f553440c3f0aea Mon Sep 17 00:00:00 2001 From: Sam Pfluger <108141731+Sampfluger88@users.noreply.github.com> Date: Thu, 3 Oct 2024 16:56:48 -0500 Subject: [PATCH 065/385] Add Cancel a Fleet Premium subscription (#22639) --- handbook/digital-experience/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/handbook/digital-experience/README.md b/handbook/digital-experience/README.md index ab17f0cf5c89..ec21cfdafbf5 100644 --- a/handbook/digital-experience/README.md +++ b/handbook/digital-experience/README.md @@ -780,6 +780,14 @@ Fleet has several brand fronts that need to be updated from time to time. Check > For any support-related questions, forward the submission to [Fleet's support team](https://docs.google.com/document/d/1tE-NpNfw1icmU2MjYuBRib0VWBPVAdmq4NiCrpuI0F0/edit#heading=h.wqalwz1je6rq). +### Cancel a Fleet Premium subscription + +Use the following steps to cancel a Fleet Premium subscription: +1. Log into [Stripe](https://dashboard.stripe.com/dashboard) (login in 1Password) and paste the customer's email they used to sign up in the search bar at the top of the page. +2. Select the subscription related to the email and use the "Actions" drop-down to "Cancel immediately". +3. Reach out to the community member (using the [correct email template](https://docs.google.com/document/d/1D02k0tc5v-sEJ4uahAouuqnvZ6phxA_gP-IqmkBdMTE/edit#heading=h.vw9mkh5e9msx)) and let them know their subscription was canceled. + + ## Rituals - Note: Some rituals (⏰) are especially time-sensitive and require attention multiple times (3+) per day. Set reminders for the following times (CT): From 841a425bb054c400f55c681dd0d239d702aac5de Mon Sep 17 00:00:00 2001 From: jacobshandling <61553566+jacobshandling@users.noreply.github.com> Date: Thu, 3 Oct 2024 15:59:10 -0700 Subject: [PATCH 066/385] =?UTF-8?q?UI=20=E2=80=93=C2=A0Display=20whitespac?= =?UTF-8?q?e=20of=20existing,=20trim=20names=20on=20create/update=20of=20t?= =?UTF-8?q?eam=20and=20query=20names=20(#22524)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## #22212 - Trim whitespace from names on field blur, form submit, and in API calls when: - Creating a team - Updating a team - Creating a query - Updating a query - Refactor `AutoResizeInputField` to remove its internal state-based management of its value, leaving its `value` prop as the single source of truth for the field's value at all times. [Loom demo](https://www.loom.com/share/882f4a803b1540db985c987adbd9f441?sid=67caf100-4711-41a3-971f-bc8f67beeae7) - [x] Changes file added for user-visible changes in `changes/`, - [x] Manual QA for all new/changed functionality --------- Co-authored-by: Jacob Shandling --- changes/22212-trim-names | 1 + .../AutoSizeInputField/AutoSizeInputField.tsx | 30 ++++++------------- .../CreateTeamModal/CreateTeamModal.tsx | 7 +++-- .../RenameTeamModal/RenameTeamModal.tsx | 5 +++- .../EditQueryForm/EditQueryForm.tsx | 7 ++++- .../SaveQueryModal/SaveQueryModal.tsx | 10 +++++-- frontend/services/entities/queries.ts | 9 ++++-- frontend/services/entities/teams.ts | 6 +++- 8 files changed, 44 insertions(+), 31 deletions(-) create mode 100644 changes/22212-trim-names diff --git a/changes/22212-trim-names b/changes/22212-trim-names new file mode 100644 index 000000000000..3f52cb9a06ea --- /dev/null +++ b/changes/22212-trim-names @@ -0,0 +1 @@ +* Update UI to remove leading/trailing whitespace when creating or editing team or query names. \ No newline at end of file diff --git a/frontend/components/forms/fields/AutoSizeInputField/AutoSizeInputField.tsx b/frontend/components/forms/fields/AutoSizeInputField/AutoSizeInputField.tsx index e150c6f448ce..2392e0d2dfd5 100644 --- a/frontend/components/forms/fields/AutoSizeInputField/AutoSizeInputField.tsx +++ b/frontend/components/forms/fields/AutoSizeInputField/AutoSizeInputField.tsx @@ -1,10 +1,4 @@ -import React, { - ChangeEvent, - KeyboardEvent, - useEffect, - useRef, - useState, -} from "react"; +import React, { KeyboardEvent, useEffect, useRef } from "react"; import classnames from "classnames"; interface IAutoSizeInputFieldProps { @@ -38,8 +32,6 @@ const AutoSizeInputField = ({ onChange, onKeyPress, }: IAutoSizeInputFieldProps): JSX.Element => { - const [inputValue, setInputValue] = useState(value); - const inputClasses = classnames(baseClass, inputClassName, "no-hover", { [`${baseClass}--disabled`]: isDisabled, [`${baseClass}--error`]: hasError, @@ -48,22 +40,14 @@ const AutoSizeInputField = ({ const inputElement = useRef(null); - useEffect(() => { - onChange(inputValue); - }, [inputValue]); - useEffect(() => { if (isFocused && inputElement.current) { inputElement.current.focus(); - inputElement.current.selectionStart = inputValue.length; - inputElement.current.selectionEnd = inputValue.length; + inputElement.current.selectionStart = value.length; + inputElement.current.selectionEnd = value.length; } }, [isFocused]); - const onInputChange = (event: ChangeEvent) => { - setInputValue(event.currentTarget.value); - }; - const onInputFocus = () => { isFocused = true; onFocus(); @@ -78,15 +62,19 @@ const AutoSizeInputField = ({ onKeyPress(event); }; + const onInputChange = (event: React.ChangeEvent) => { + onChange(event.target.value); + }; + return (
-