From afc4cc5d23197699172923948fa1ab1e128023c0 Mon Sep 17 00:00:00 2001 From: Gabriel Hernandez Date: Fri, 22 Nov 2024 16:52:03 +0000 Subject: [PATCH 01/92] add UI for new windows mdm page and automatic migration (#24068) relates to #22896 Implements the UI for the windows automatic migration. **new windows mdm page layout with automatic migration checkbox** ![image](https://github.com/user-attachments/assets/2909d6d2-e802-4dec-9c78-0b8f6a4466c0) - [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 - [x] Manual QA for all new/changed functionality --- changes/22896-ui-windows-automatic-migration | 1 + frontend/__mocks__/configMock.ts | 1 + .../forms/fields/Checkbox/_styles.scss | 1 + frontend/interfaces/config.ts | 1 + .../pages/DashboardPage/cards/MDM/MDM.tsx | 6 +- .../WindowsMdmPage/WindowsMdmPage.tsx | 121 +++++++++--------- .../MdmSettings/WindowsMdmPage/__styles.scss | 9 +- frontend/utilities/constants.tsx | 5 +- 8 files changed, 76 insertions(+), 69 deletions(-) create mode 100644 changes/22896-ui-windows-automatic-migration diff --git a/changes/22896-ui-windows-automatic-migration b/changes/22896-ui-windows-automatic-migration new file mode 100644 index 0000000000..ae0234123b --- /dev/null +++ b/changes/22896-ui-windows-automatic-migration @@ -0,0 +1 @@ +- add UI changes for windows mdm page and allow for automatic migration for windows hosts. diff --git a/frontend/__mocks__/configMock.ts b/frontend/__mocks__/configMock.ts index c589f05386..d1f0a644cd 100644 --- a/frontend/__mocks__/configMock.ts +++ b/frontend/__mocks__/configMock.ts @@ -39,6 +39,7 @@ const DEFAULT_CONFIG_MDM_MOCK: IMdmConfig = { deadline_days: null, grace_period_days: null, }, + windows_migration_enabled: false, end_user_authentication: { entity_id: "", issuer_uri: "", diff --git a/frontend/components/forms/fields/Checkbox/_styles.scss b/frontend/components/forms/fields/Checkbox/_styles.scss index 8e8d023422..6d311ead47 100644 --- a/frontend/components/forms/fields/Checkbox/_styles.scss +++ b/frontend/components/forms/fields/Checkbox/_styles.scss @@ -68,6 +68,7 @@ } .checkbox-unchecked-state { stroke: $ui-fleet-black-25; + fill: $ui-fleet-black-25; } } } diff --git a/frontend/interfaces/config.ts b/frontend/interfaces/config.ts index 0de36a5d36..12d61f3c85 100644 --- a/frontend/interfaces/config.ts +++ b/frontend/interfaces/config.ts @@ -57,6 +57,7 @@ export interface IMdmConfig { apple_bm_terms_expired: boolean; apple_bm_enabled_and_configured: boolean; windows_enabled_and_configured: boolean; + windows_migration_enabled: boolean; end_user_authentication: IEndUserAuthentication; macos_updates: IAppleDeviceUpdates; ios_updates: IAppleDeviceUpdates; diff --git a/frontend/pages/DashboardPage/cards/MDM/MDM.tsx b/frontend/pages/DashboardPage/cards/MDM/MDM.tsx index 583460c128..6824ebe5ae 100644 --- a/frontend/pages/DashboardPage/cards/MDM/MDM.tsx +++ b/frontend/pages/DashboardPage/cards/MDM/MDM.tsx @@ -2,11 +2,7 @@ import React, { useMemo, useState } from "react"; import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; import { Row } from "react-table"; -import { - IMdmStatusCardData, - IMdmSolution, - IMdmSummaryMdmSolution, -} from "interfaces/mdm"; +import { IMdmStatusCardData, IMdmSummaryMdmSolution } from "interfaces/mdm"; import TabsWrapper from "components/TabsWrapper"; import TableContainer from "components/TableContainer"; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/WindowsMdmPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/WindowsMdmPage.tsx index 405cf3b4a6..e7b5b814a4 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/WindowsMdmPage.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/WindowsMdmPage.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from "react"; +import React, { useContext, useState } from "react"; import { InjectedRouter } from "react-router"; import { isAxiosError } from "axios"; @@ -11,18 +11,22 @@ import { AppContext } from "context/app"; import MainContent from "components/MainContent/MainContent"; import Button from "components/buttons/Button"; import BackLink from "components/BackLink/BackLink"; +import Slider from "components/forms/fields/Slider"; +import Checkbox from "components/forms/fields/Checkbox"; const baseClass = "windows-mdm-page"; interface ISetWindowsMdmOptions { - enable: boolean; + enableMdm: boolean; + enableAutoMigration: boolean; successMessage: string; errorMessage: string; router: InjectedRouter; } const useSetWindowsMdm = ({ - enable, + enableMdm, + enableAutoMigration, successMessage, errorMessage, router, @@ -35,13 +39,14 @@ const useSetWindowsMdm = ({ try { const updatedConfig = await configAPI.updateMDMConfig( { - windows_enabled_and_configured: enable, + windows_enabled_and_configured: enableMdm, + windows_migration_enabled: enableAutoMigration, }, true ); setConfig(updatedConfig); } catch (e) { - if (enable && isAxiosError(e) && e.response?.status === 422) { + if (enableMdm && isAxiosError(e) && e.response?.status === 422) { flashErrMsg = getErrorReason(e, { nameEquals: "mdm.windows_enabled_and_configured", @@ -62,53 +67,6 @@ const useSetWindowsMdm = ({ return turnOnWindowsMdm; }; -interface IWindowsMdmOnContentProps { - router: InjectedRouter; -} - -const WindowsMdmOnContent = ({ router }: IWindowsMdmOnContentProps) => { - const turnOnWindowsMdm = useSetWindowsMdm({ - enable: true, - successMessage: "Windows MDM turned on (servers excluded).", - errorMessage: "Unable to turn on Windows MDM. Please try again.", - router, - }); - - return ( - <> -

Turn on Windows MDM

-

This will turn MDM on for Windows hosts with fleetd.

-

Hosts connected to another MDM solution won't be migrated.

-

MDM won't be turned on for Windows servers.

- - - ); -}; - -interface IWindowsMdmOffContentProps { - router: InjectedRouter; -} - -const WindowsMdmOffContent = ({ router }: IWindowsMdmOffContentProps) => { - const turnOffWindowsMdm = useSetWindowsMdm({ - enable: false, - successMessage: "Windows MDM turned off.", - errorMessage: "Unable to turn off Windows MDM. Please try again.", - router, - }); - - return ( - <> -

Turn off Windows MDM

-

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

-

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

- - - ); -}; - interface IWindowsMdmPageProps { router: InjectedRouter; } @@ -116,8 +74,37 @@ interface IWindowsMdmPageProps { const WindowsMdmPage = ({ router }: IWindowsMdmPageProps) => { const { config } = useContext(AppContext); - const isWindowsMdmEnabled = - config?.mdm?.windows_enabled_and_configured ?? false; + const [mdmOn, setMdmOn] = useState( + config?.mdm?.windows_enabled_and_configured ?? false + ); + const [autoMigration, setAutoMigration] = useState( + config?.mdm?.windows_migration_enabled ?? false + ); + + const updateWindowsMdm = useSetWindowsMdm({ + enableMdm: mdmOn, + enableAutoMigration: autoMigration, + successMessage: "Windows MDM settings successfully updated.", + errorMessage: "Unable to update Windows MDM. Please try again.", + router, + }); + + const onChangeMdmOn = () => { + setMdmOn(!mdmOn); + mdmOn && setAutoMigration(false); + }; + + const onChangeAutoMigration = () => { + setAutoMigration(!autoMigration); + }; + + const onSaveMdm = () => { + updateWindowsMdm(); + }; + + const descriptionText = mdmOn + ? "Turns on MDM for Windows hosts that enroll to Fleet (excluding servers)." + : "Hosts with MDM already turned on will not have MDM removed."; return ( @@ -127,11 +114,27 @@ const WindowsMdmPage = ({ router }: IWindowsMdmPageProps) => { path={PATHS.ADMIN_INTEGRATIONS_MDM} className={`${baseClass}__back-to-mdm`} /> - {isWindowsMdmEnabled ? ( - - ) : ( - - )} +

Manage Windows MDM

+
+ +

{descriptionText}

+ + Automatically migrate hosts connected to another MDM solution + + + +
); diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/__styles.scss b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/__styles.scss index ecf4b4c49c..64d1279746 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/__styles.scss +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/__styles.scss @@ -5,12 +5,13 @@ } h1 { - margin-bottom: $pad-xxlarge; + margin-bottom: $pad-large; font-size: $x-large; } - p { - font-size: $x-small; - margin: 0 0 $pad-large; + form { + display: flex; + flex-direction: column; + gap: $pad-large; } } diff --git a/frontend/utilities/constants.tsx b/frontend/utilities/constants.tsx index d7e2497655..3c5820df35 100644 --- a/frontend/utilities/constants.tsx +++ b/frontend/utilities/constants.tsx @@ -340,7 +340,10 @@ export const MDM_STATUS_TOOLTIP: Record = { ), "On (manual)": ( - MDM was turned on manually. End users can turn MDM off. + + MDM was turned on manually (macOS), or hosts were automatically migrated + with fleetd (Windows). End users can turn MDM off. + ), Off: undefined, // no tooltip specified Pending: ( From 5abcf2ef3aeaf3959314fcccd6295c5f77007ca8 Mon Sep 17 00:00:00 2001 From: Victor Lyuboslavsky Date: Mon, 25 Nov 2024 10:03:19 -0600 Subject: [PATCH 02/92] Drop duplicate MySQL indexes. (#24107) #24109 Duplicate indexes identified after running pt-duplicate-key-checker https://docs.percona.com/percona-toolkit/pt-duplicate-key-checker.html # Checklist for submitter - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files) for more information. - 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`). --- changes/24109-drop-duplicate-indexes | 1 + .../20241122171434_RemoveDuplicateIndexes.go | 117 ++++++++++++++++++ server/datastore/mysql/schema.sql | 21 ++-- 3 files changed, 126 insertions(+), 13 deletions(-) create mode 100644 changes/24109-drop-duplicate-indexes create mode 100644 server/datastore/mysql/migrations/tables/20241122171434_RemoveDuplicateIndexes.go diff --git a/changes/24109-drop-duplicate-indexes b/changes/24109-drop-duplicate-indexes new file mode 100644 index 0000000000..df813981a4 --- /dev/null +++ b/changes/24109-drop-duplicate-indexes @@ -0,0 +1 @@ +Removed duplicate indexes from the database schema. diff --git a/server/datastore/mysql/migrations/tables/20241122171434_RemoveDuplicateIndexes.go b/server/datastore/mysql/migrations/tables/20241122171434_RemoveDuplicateIndexes.go new file mode 100644 index 0000000000..972fa45f85 --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20241122171434_RemoveDuplicateIndexes.go @@ -0,0 +1,117 @@ +package tables + +import ( + "database/sql" + "fmt" +) + +func init() { + MigrationClient.AddMigration(Up_20241122171434, Down_20241122171434) +} + +func Up_20241122171434(tx *sql.Tx) error { + // Duplicate indexes identified after running pt-duplicate-key-checker + // https://docs.percona.com/percona-toolkit/pt-duplicate-key-checker.html + + // # ######################################################################## + // # fleet.app_config_json + // # ######################################################################## + // + // # Uniqueness of id ignored because PRIMARY is a duplicate constraint + // # id is a duplicate of PRIMARY + // # Key definitions: + // # UNIQUE KEY `id` (`id`) + // # PRIMARY KEY (`id`), + // # Column types: + // # `id` int unsigned not null default '1' + // # To remove this duplicate index, execute: + // ALTER TABLE `fleet`.`app_config_json` DROP INDEX `id`; + // + // # ######################################################################## + // # fleet.host_users + // # ######################################################################## + // + // # idx_uid_username is a duplicate of PRIMARY + // # Key definitions: + // # UNIQUE KEY `idx_uid_username` (`host_id`,`uid`,`username`) + // # PRIMARY KEY (`host_id`,`uid`,`username`), + // # Column types: + // # `host_id` int unsigned not null + // # `uid` int unsigned not null + // # `username` varchar(255) collate utf8mb4_unicode_ci not null + // # To remove this duplicate index, execute: + // ALTER TABLE `fleet`.`host_users` DROP INDEX `idx_uid_username`; + // + // # ######################################################################## + // # fleet.migration_status_tables + // # ######################################################################## + // + // # Uniqueness of id ignored because PRIMARY is a duplicate constraint + // # id is a duplicate of PRIMARY + // # Key definitions: + // # UNIQUE KEY `id` (`id`) + // # PRIMARY KEY (`id`), + // # Column types: + // # `id` bigint unsigned not null auto_increment + // # To remove this duplicate index, execute: + // ALTER TABLE `fleet`.`migration_status_tables` DROP INDEX `id`; + // + // # ######################################################################## + // # fleet.policy_membership + // # ######################################################################## + // + // # idx_policy_membership_policy_id is a left-prefix of PRIMARY + // # Key definitions: + // # KEY `idx_policy_membership_policy_id` (`policy_id`), + // # PRIMARY KEY (`policy_id`,`host_id`), + // # Column types: + // # `policy_id` int unsigned not null + // # `host_id` int unsigned not null + // # To remove this duplicate index, execute: + // ALTER TABLE `fleet`.`policy_membership` DROP INDEX `idx_policy_membership_policy_id`; + // + // # ######################################################################## + // # fleet.software + // # ######################################################################## + // + // # Key software_listing_idx ends with a prefix of the clustered index + // # Key definitions: + // # KEY `software_listing_idx` (`name`,`id`), + // # PRIMARY KEY (`id`), + // # Column types: + // # `name` varchar(255) collate utf8mb4_unicode_ci not null + // # `id` bigint unsigned not null auto_increment + // # To shorten this duplicate clustered index, execute: + // ALTER TABLE `fleet`.`software` DROP INDEX `software_listing_idx`, ADD INDEX `software_listing_idx` (`name`); + // + // # ######################################################################## + // # fleet.software_cve + // # ######################################################################## + // + // # software_cve_software_id is a left-prefix of unq_software_id_cve + // # Key definitions: + // # KEY `software_cve_software_id` (`software_id`) + // # UNIQUE KEY `unq_software_id_cve` (`software_id`,`cve`), + // # Column types: + // # `software_id` bigint unsigned default null + // # `cve` varchar(255) collate utf8mb4_unicode_ci not null + // # To remove this duplicate index, execute: + // ALTER TABLE `fleet`.`software_cve` DROP INDEX `software_cve_software_id`; + + _, err := tx.Exec( + "ALTER TABLE `app_config_json` DROP INDEX `id`;" + + "ALTER TABLE `host_users` DROP INDEX `idx_uid_username`;" + + "ALTER TABLE `migration_status_tables` DROP INDEX `id`;" + + "ALTER TABLE `policy_membership` DROP INDEX `idx_policy_membership_policy_id`;" + + "ALTER TABLE `software` DROP INDEX `software_listing_idx`, ADD INDEX `software_listing_idx` (`name`);" + + "ALTER TABLE `software_cve` DROP INDEX `software_cve_software_id`;", + ) + if err != nil { + return fmt.Errorf("failed to remove duplicate indexes: %w", err) + } + return nil +} + +func Down_20241122171434(tx *sql.Tx) error { + return nil +} diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql index 45718cbd1d..038a0de892 100644 --- a/server/datastore/mysql/schema.sql +++ b/server/datastore/mysql/schema.sql @@ -61,8 +61,7 @@ CREATE TABLE `app_config_json` ( `json_value` json NOT NULL, `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`), - UNIQUE KEY `id` (`id`) + PRIMARY KEY (`id`) ) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; INSERT INTO `app_config_json` VALUES (1,'{\"mdm\": {\"ios_updates\": {\"deadline\": null, \"minimum_version\": null}, \"macos_setup\": {\"script\": null, \"software\": null, \"bootstrap_package\": null, \"macos_setup_assistant\": null, \"enable_end_user_authentication\": false, \"enable_release_device_manually\": false}, \"macos_updates\": {\"deadline\": null, \"minimum_version\": null}, \"ipados_updates\": {\"deadline\": null, \"minimum_version\": null}, \"macos_settings\": {\"custom_settings\": null}, \"macos_migration\": {\"mode\": \"\", \"enable\": false, \"webhook_url\": \"\"}, \"windows_updates\": {\"deadline_days\": null, \"grace_period_days\": null}, \"apple_server_url\": \"\", \"windows_settings\": {\"custom_settings\": null}, \"apple_bm_terms_expired\": false, \"apple_business_manager\": null, \"enable_disk_encryption\": false, \"enabled_and_configured\": false, \"end_user_authentication\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"issuer_uri\": \"\", \"metadata_url\": \"\"}, \"volume_purchasing_program\": null, \"windows_enabled_and_configured\": false, \"apple_bm_enabled_and_configured\": false}, \"scripts\": null, \"features\": {\"enable_host_users\": true, \"enable_software_inventory\": false}, \"org_info\": {\"org_name\": \"\", \"contact_url\": \"\", \"org_logo_url\": \"\", \"org_logo_url_light_background\": \"\"}, \"integrations\": {\"jira\": null, \"zendesk\": null, \"google_calendar\": null, \"ndes_scep_proxy\": null}, \"sso_settings\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"enable_sso\": false, \"issuer_uri\": \"\", \"metadata_url\": \"\", \"idp_image_url\": \"\", \"enable_jit_role_sync\": false, \"enable_sso_idp_login\": false, \"enable_jit_provisioning\": false}, \"agent_options\": {\"config\": {\"options\": {\"logger_plugin\": \"tls\", \"pack_delimiter\": \"/\", \"logger_tls_period\": 10, \"distributed_plugin\": \"tls\", \"disable_distributed\": false, \"logger_tls_endpoint\": \"/api/osquery/log\", \"distributed_interval\": 10, \"distributed_tls_max_attempts\": 3}, \"decorators\": {\"load\": [\"SELECT uuid AS host_uuid FROM system_info;\", \"SELECT hostname AS hostname FROM system_info;\"]}}, \"overrides\": {}}, \"fleet_desktop\": {\"transparency_url\": \"\"}, \"smtp_settings\": {\"port\": 587, \"domain\": \"\", \"server\": \"\", \"password\": \"\", \"user_name\": \"\", \"configured\": false, \"enable_smtp\": false, \"enable_ssl_tls\": true, \"sender_address\": \"\", \"enable_start_tls\": true, \"verify_ssl_certs\": true, \"authentication_type\": \"0\", \"authentication_method\": \"0\"}, \"server_settings\": {\"server_url\": \"\", \"enable_analytics\": false, \"query_report_cap\": 0, \"scripts_disabled\": false, \"deferred_save_host\": false, \"live_query_disabled\": false, \"ai_features_disabled\": false, \"query_reports_disabled\": false}, \"webhook_settings\": {\"interval\": \"0s\", \"activities_webhook\": {\"destination_url\": \"\", \"enable_activities_webhook\": false}, \"host_status_webhook\": {\"days_count\": 0, \"destination_url\": \"\", \"host_percentage\": 0, \"enable_host_status_webhook\": false}, \"vulnerabilities_webhook\": {\"destination_url\": \"\", \"host_batch_size\": 0, \"enable_vulnerabilities_webhook\": false}, \"failing_policies_webhook\": {\"policy_ids\": null, \"destination_url\": \"\", \"host_batch_size\": 0, \"enable_failing_policies_webhook\": false}}, \"host_expiry_settings\": {\"host_expiry_window\": 0, \"host_expiry_enabled\": false}, \"vulnerability_settings\": {\"databases_path\": \"\"}, \"activity_expiry_settings\": {\"activity_expiry_window\": 0, \"activity_expiry_enabled\": false}}','2020-01-01 01:01:01','2020-01-01 01:01:01'); @@ -654,8 +653,7 @@ CREATE TABLE `host_users` ( `removed_at` timestamp NULL DEFAULT NULL, `user_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, `shell` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT '', - PRIMARY KEY (`host_id`,`uid`,`username`), - UNIQUE KEY `idx_uid_username` (`host_id`,`uid`,`username`) + PRIMARY KEY (`host_id`,`uid`,`username`) ) /*!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 */; @@ -1102,11 +1100,10 @@ CREATE TABLE `migration_status_tables` ( `version_id` bigint NOT NULL, `is_applied` tinyint(1) NOT NULL, `tstamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`), - UNIQUE KEY `id` (`id`) -) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=331 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + PRIMARY KEY (`id`) +) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=332 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; -INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20241002104104,1,'2020-01-01 01:01:01'),(314,20241002104105,1,'2020-01-01 01:01:01'),(315,20241002104106,1,'2020-01-01 01:01:01'),(316,20241002210000,1,'2020-01-01 01:01:01'),(317,20241003145349,1,'2020-01-01 01:01:01'),(318,20241004005000,1,'2020-01-01 01:01:01'),(319,20241008083925,1,'2020-01-01 01:01:01'),(320,20241009090010,1,'2020-01-01 01:01:01'),(321,20241017163402,1,'2020-01-01 01:01:01'),(322,20241021224359,1,'2020-01-01 01:01:01'),(323,20241022140321,1,'2020-01-01 01:01:01'),(324,20241025111236,1,'2020-01-01 01:01:01'),(325,20241025112748,1,'2020-01-01 01:01:01'),(326,20241025141855,1,'2020-01-01 01:01:01'),(327,20241110152839,1,'2020-01-01 01:01:01'),(328,20241110152840,1,'2020-01-01 01:01:01'),(329,20241110152841,1,'2020-01-01 01:01:01'),(330,20241116233322,1,'2020-01-01 01:01:01'); +INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20241002104104,1,'2020-01-01 01:01:01'),(314,20241002104105,1,'2020-01-01 01:01:01'),(315,20241002104106,1,'2020-01-01 01:01:01'),(316,20241002210000,1,'2020-01-01 01:01:01'),(317,20241003145349,1,'2020-01-01 01:01:01'),(318,20241004005000,1,'2020-01-01 01:01:01'),(319,20241008083925,1,'2020-01-01 01:01:01'),(320,20241009090010,1,'2020-01-01 01:01:01'),(321,20241017163402,1,'2020-01-01 01:01:01'),(322,20241021224359,1,'2020-01-01 01:01:01'),(323,20241022140321,1,'2020-01-01 01:01:01'),(324,20241025111236,1,'2020-01-01 01:01:01'),(325,20241025112748,1,'2020-01-01 01:01:01'),(326,20241025141855,1,'2020-01-01 01:01:01'),(327,20241110152839,1,'2020-01-01 01:01:01'),(328,20241110152840,1,'2020-01-01 01:01:01'),(329,20241110152841,1,'2020-01-01 01:01:01'),(330,20241116233322,1,'2020-01-01 01:01:01'),(331,20241122171434,1,'2020-01-01 01:01:01'); /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `mobile_device_management_solutions` ( @@ -1487,7 +1484,6 @@ CREATE TABLE `policy_membership` ( `automation_iteration` int DEFAULT NULL, PRIMARY KEY (`policy_id`,`host_id`), KEY `idx_policy_membership_passes` (`passes`), - KEY `idx_policy_membership_policy_id` (`policy_id`), KEY `idx_policy_membership_host_id_passes` (`host_id`,`passes`), CONSTRAINT `policy_membership_ibfk_1` FOREIGN KEY (`policy_id`) REFERENCES `policies` (`id`) ON DELETE CASCADE ) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; @@ -1729,10 +1725,10 @@ CREATE TABLE `software` ( `checksum` binary(16) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `idx_software_checksum` (`checksum`), - KEY `software_listing_idx` (`name`,`id`), KEY `software_source_vendor_idx` (`source`,`vendor_old`), KEY `title_id` (`title_id`), - KEY `idx_sw_name_source_browser` (`name`,`source`,`browser`) + KEY `idx_sw_name_source_browser` (`name`,`source`,`browser`), + KEY `software_listing_idx` (`name`) ) /*!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 */; @@ -1760,8 +1756,7 @@ CREATE TABLE `software_cve` ( `software_id` bigint unsigned DEFAULT NULL, `resolved_in_version` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, PRIMARY KEY (`id`), - UNIQUE KEY `unq_software_id_cve` (`software_id`,`cve`), - KEY `software_cve_software_id` (`software_id`) + UNIQUE KEY `unq_software_id_cve` (`software_id`,`cve`) ) /*!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 */; From 95c51ac4d26e7aba53f77cabcb23edecc6760f0d Mon Sep 17 00:00:00 2001 From: Luke Heath Date: Mon, 25 Nov 2024 11:17:07 -0600 Subject: [PATCH 03/92] Update Engineering handbook (#24127) --- handbook/engineering/README.md | 178 ++++++++++++++------------------- 1 file changed, 76 insertions(+), 102 deletions(-) diff --git a/handbook/engineering/README.md b/handbook/engineering/README.md index 570b3e5017..bde2c51804 100644 --- a/handbook/engineering/README.md +++ b/handbook/engineering/README.md @@ -26,18 +26,6 @@ This handbook page details processes specific to working [with](#contact-us) and The 🚀 Engineering department at Fleet is directly responsible for writing and maintaining the [code](https://github.com/fleetdm/fleet) for Fleet's core product and infrastructure. -### Record engineering KPIs - -We track the success of this process by observing the throughput of issues through the system and identifying where buildups (and therefore bottlenecks) are occurring. -The metrics are: -* Number of bugs opened this week -* Total # bugs open -* Bugs in each state (inbox, acknowledged, reproduced) -* Number of bugs closed this week - -Each week these are tracked and shared in the weekly KPI sheet by Luke Heath. - - ### Write a feature guide We write [guides](https://fleetdm.com/guides) for all new features. Feature guides are published before the feature is released so that our users understand how the feature is intended to work. A guide is a type of article, so the process for writing a guide and article is the same. @@ -46,13 +34,14 @@ We write [guides](https://fleetdm.com/guides) for all new features. Feature guid 2. Make a copy of a guide in the `/articles` directory and replace the content with your article. Make sure to maintain the same heading sizes and update the metadata tags at the bottom. 3. Open a new pull request containing your article into `main` and add the pull request to the milestone this feature will be shipped in. The pull request will automatically be assigned to the appropriate reviewer. + ### Create an engineering-initiated story Engineering-initiated stories are types of user stories created by engineers to make technical changes to Fleet. Technical changes should improve the user experience or contributor experience. For example, optimizing SQL that improves the response time of an API endpoint improves user experience by reducing latency. A script that generates common boilerplate, or automated tests to cover important business logic, improves the quality of life for contributors, making them happier and more productive, resulting in faster delivery of features to our customers. It is important to frame engineering-initiated user stories the same way we frame all user stories. Stay focused on how this technical change will drive value for our users. -To [create an engineering-initiated user story](https://fleetdm.com/handbook/engineering#creating-an-engineering-initiated-story), follow the [user story drafting process](https://fleetdm.com/handbook/company/development-groups#drafting). Once your user story is created using the [new story template](https://github.com/fleetdm/fleet/issues/new?assignees=lukeheath&labels=story,~engineering-initiated&projects=&template=story.md&title=), make sure the `~engineering-initiated` label is added and the engineering output and architecture DRI (@lukeheath) is assigned. +To [create an engineering-initiated user story](https://fleetdm.com/handbook/engineering#creating-an-engineering-initiated-story), follow the [user story drafting process](https://fleetdm.com/handbook/company/development-groups#drafting). Once your user story is created using the [new story template](https://github.com/fleetdm/fleet/issues/new?assignees=lukeheath&labels=story,~engineering-initiated&projects=&template=story.md&title=), make sure the `~engineering-initiated` label is added, the `:product` label is removed, and the engineering output and architecture DRI (@lukeheath) is assigned. What happens next? The engineering output and architecture DRI reviews engineering-initiated stories weekly. @@ -158,7 +147,7 @@ Documentation on completing the release process can be found [here](https://gith ### Deploy a new release to dogfood -After each Fleet release, the new release is deployed to Fleet's "dogfood" (internal) instance. +After each Fleet release, the new release is deployed to Fleet's "dogfood" (internal) instance. To avoid interruptions to sales demos using this instance, deploys should occur outside of the business hours of 7am - 5pm Pacific time Monday - Friday. If a deployment is necessary during business hours, coordinate with the Sales department in the #g-sales Slack channel. How to deploy a new release to dogfood: @@ -262,33 +251,6 @@ Once the user has installed fleetd, verify the device is correctly enrolled by c ChromeOS devices are automatically enrolled in dogfood after the IT admin sets up automatic enrollment. This is done in dogfood by following the steps found in the dialog popup when selecting "Add hosts > ChromeOS" from the dogfood Hosts page. -### Lock a macOS host in dogfood using fleetctl CLI tool - -- Download the lock command XML file from Google Drive [here](https://drive.google.com/file/d/1o6vJ1fHilRtBmyKAj0I5URiKn77qe4gS/view?usp=drive_link). -- Customize any messaging that will appear on the locked device, and modify the pin for unlocking the device by editing the file in text editor. - - Note you will need to safely store the recovery pin for the device, suggest using 1Password or other secure storage method -- Run this command with fleetctl CLI tool: `fleetctl mdm run-command --hosts=hostname --payload=Downloads/command-lock-macos-host.xml` - - Note that `hostname` must be replaced with **Hostname** in Fleet (not the display name) - - Note that the payload path may change based on where the file is stored once downloaded - - Note that if you haven't logged into fleetctl recently, will need to follow authentication steps (see [Logging in with SAML (SSO) authentication](https://fleetdm.com/docs/using-fleet/fleetctl-cli#logging-in-with-saml-sso-authentication) ). -- Device will be locked -- When device needs to be unlocked, enter the security pin (from XML file) in the input field of the device -- The device will then open to the regular login screen, asking for password - - If you do not have the password available, you can choose the option to enter recovery key/disk encryption key (this option might be behind `?` icon). - - Get disk encryption key from Fleet dogfood (using the action menu from the individual host page). - - Enter disk encryption key on laptop. This should prompt you to create a new password. -- You will then be logged into the default device profile, and can complete any needed actions (wipe, recover data). - - -### Review another product group's pull request - -Some code paths require pull request review from multiple product groups to confirm there are no -unintended side effects of the change for another product group. All code paths defined in -[CODEOWNERS](https://github.com/fleetdm/fleet/blob/main/CODEOWNERS) that are assigned to individual -engineers across multiple product groups must be approved by one engineer from each product group -before merging. - - ### Review a community pull request If you're assigned a community pull request for review, it is important to keep things moving for the contributor. The goal is to not go more than one business day without following up with the contributor. @@ -336,29 +298,9 @@ If a community member opens an issue that we can't reproduce leave a comment ask ### Schedule developer on-call workload -Engineering managers are asked to be aware of the [on-call rotation](https://docs.google.com/document/d/1FNQdu23wc1S9Yo6x5k04uxT2RwT77CIMzLLeEI2U7JA/edit#) and schedule a light workload for engineers while they are on-call. While it varies week to week considerably, the on-call responsibilities can sometimes take up a substantial portion of the engineer's time. +Engineering Managers are asked to be aware of the [on-call rotation](https://docs.google.com/document/d/1FNQdu23wc1S9Yo6x5k04uxT2RwT77CIMzLLeEI2U7JA/edit#) and reduce estimate capacity for each sprint accordingly. While it varies week to week considerably, the on-call responsibilities can sometimes take up a substantial portion of the engineer's time. -We aspire to clear sprint work for the on-call engineer, but due to capacity or other constraints, sometimes the on-call engineer is required for sprint work. When this is the case, the EM will work with the on-call engineer to take over support requests or @oncall assignment completely when necessary. - -The remaining time after fulfilling the responsibilities of on-call is free for the engineer to choose their own path. Please choose something relevant to your work or Fleet's goals to focus on. If unsure, speak with your manager. - -Some ideas: - -- Do training/learning relevant to your work. -- Improve the Fleet developer experience. -- Hack on a product idea. Note: Experiments are encouraged, but not all experiments will ship! Check in with the product team before shipping user-visible changes. -- Create a blog post (or other content) for fleetdm.com. -- Try out an experimental refactor. - - -### Edit a DNS record - -We use Cloudflare to manage the DNS records of fleetdm.com and our other domains. To make DNS changes in Cloudflare: -1. Log into your Cloudflare account and select the "Fleet" account. -2. Select the domain you want to change and go to the DNS panel on that domain's dashboard. -3. To add a record, click the "Add record" button, select the record's type, fill in the required values, and click "Save". If you're making changes to an existing record, you only need to click on the record, update the record's values, and save your changes. - -> If you need access to Fleet's Cloudflare account, please ask the [DRI](https://fleetdm.com/handbook/company/why-this-way#why-direct-responsibility) [Luke Heath](https://fleetdm.com/handbook/engineering#team) in Slack for an invitation. +On-call engineers are available during the business hours of 9am - 5pm Pacific. The [on-call support SLA](https://fleetdm.com/handbook/company/product-groups#developer-on-call-responsibilities) requires a 1-hour response time during business hours to any @oncall mention. ### Assume developer on-call alias @@ -368,6 +310,15 @@ The on-call developer is responsible for: - Performing the [on-call responsibilities](https://fleetdm.com/handbook/company/product-groups#developer-on-call-responsibilities). - [Escalating community questions and issues](https://fleetdm.com/handbook/company/product-groups#escalations). - Successfully [transferring the on-call persona to the next developer](https://fleetdm.com/handbook/company/product-groups#changing-of-the-guard). +- Work on an [engineering-initiated story](https://fleetdm.com/handbook/engineering#create-an-engineering-initiated-story). + +Some additional ideas: + +- Do training/learning relevant to your work. +- Improve the Fleet contributor experience. +- Hack on a product idea. Note: Experiments are encouraged, but not all experiments will ship! Check in with the product team before shipping user-visible changes. +- Create a blog post (or other content) for fleetdm.com. +- Try out an experimental refactor. ### Notify stakeholders when a user story is pushed to the next release @@ -393,23 +344,6 @@ For each bug found, please use the [bug report template](https://github.com/flee For unreleased bugs in an active sprint, a new bug is created with the `~unreleased bug` label. The `:release` label and associated product group label is added, and the engineer responsible for the feature is assigned. If QA is unsure who the bug should be assigned to, it is assigned to the EM. Fixing the bug becomes part of the story. -### Accept new Apple developer account terms - -Engineering is responsible for managing third-party accounts required to support engineering infrastructure. We use the official Fleet Apple developer account to notarize installers we generate for Apple devices. Whenever Apple releases new terms of service, we are unable to notarize new packages until the new terms are accepted. - -When this occurs, we will begin receiving the following error message when attempting to notarize packages: "You must first sign the relevant contracts online." To resolve this error, follow the steps below. - -1. Visit the [Apple developer account login page](https://appleid.apple.com/account?appId=632&returnUrl=https%3A%2F%2Fdeveloper.apple.com%2Fcontact%2F). - -2. Log in using the credentials stored in 1Password under "Apple developer account". - -3. Contact the Head of Digital Experience to determine which phone number to use for 2FA. - -4. Complete the 2FA process to log in. - -5. Accept the new terms of service. - - ### Interview a developer 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). Once the position is approved manage this process per candidate in a [hiring pipeline](https://drive.google.com/drive/folders/1dLZaor9dQmAxcxyU6prm-MWNd-C-U8_1?usp=drive_link) @@ -425,28 +359,6 @@ Ensure the interview process follows these steps in order. This process must fol If the candidate passes all of these steps then continue with [hiring a new team member](https://fleetdm.com/handbook/company/leadership#hiring-a-new-team-member). -### Renew MDM certificate signing request (CSR) - -The certificate signing request (CSR) certificate expires every year. It needs to be renewed prior to expiring. This is notified to the team by the MDM calendar event [IMPORTANT: Renew MDM CSR certificate](https://calendar.google.com/calendar/u/0/r/eventedit/MmdqNTY4dG9nbWZycnNxbDBzYjQ5dGplM2FfMjAyNDA5MDlUMTczMDAwWiBjXzMyMjM3NjgyZGRlOThlMzI4MjVhNTY1ZDEyZjk0MDEyNmNjMWI0ZDljYjZjNjgyYzQ2MjcxZGY0N2UzNjM5NDZAZw) - -Steps to renew the certificate: - -1. Visit the [Apple developer account login page](https://developer.apple.com/account). -2. Log in using the credentials stored in 1Password under **Apple developer account**. -3. Verify you are using the **Enterprise** subaccount for Fleet Device Management Inc. -4. Generate a new certificate following the instructions in [MicroMDM](https://github.com/micromdm/micromdm/blob/c7e70b94d0cfc7710e5c92be20d4534d9d5a0640/docs/user-guide/quickstart.md?plain=1#L103-L118). -5. Note: `mdmctl` (a micromdm command for MDM vendors) will generate a `VendorPrivateKey.key` and `VendorCertificateRequest.csr` using an appropriate shared email relay and a passphrase (suggested generation method with pwgen available in brew / apt / yum `pwgen -s 32 -1vcy`) -6. Uploading `VendorCertificateRequest.csr` to Apple you will download a corresponding `mdm.cer` file -7. Convert the downloaded cert to PEM with `openssl x509 -inform DER -outform PEM -in mdm.cer -out server.crt.pem` -8. Update the **Config vars** in [Heroku](https://dashboard.heroku.com/apps/production-fleetdm-website/settings): -* Update `sails_custom__mdmVendorCertPem` with the results from step 7 `server.crt.pem` -* Update `sails_custom__mdmVendorKeyPassphrase` with the passphrase used in step 4 -* Update `sails_custom__mdmVendorKeyPem` with `VendorPrivateKey.key` from step 4 -9. Store updated values in [Confidential 1Password Vault](https://start.1password.com/open/i?a=N3F7LHAKQ5G3JPFPX234EC4ZDQ&v=lcvkjobeheaqdgnz33ontpuhxq&i=byyfn2knejwh42a2cbc5war5sa&h=fleetdevicemanagement.1password.com) -10. Verify by logging into a normal apple account (not billing@...) and Generate a new Push Certificate following our [setup MDM](https://fleetdm.com/docs/using-fleet/mdm-setup) steps and verify the Expiration date is 1 year from today. -11. Adjust calendar event to be between 2-4 weeks before the next expiration. - - ### Perform an incident postmortem Conduct a postmortem meetings for every service or feature outage and every critical bug, whether it's a customer's environment or on fleetdm.com. @@ -471,6 +383,68 @@ Beginning with macOS 16, Fleet will offer same-day support for all major version 6. When all bugs are fixed, follow the [writing a feature guide](https://fleetdm.com/handbook/engineering#write-a-feature-guide) process to publish an article announcing Fleet same-day support for the new major release. +### Record engineering KPIs + +We track the effectiveness of our processes by observing issue throughput and identifying where buildups (and therefore bottlenecks) are occurring. + +The metrics are: +* Number of bugs opened this week +* Total # bugs open +* Bugs in each state (inbox, acknowledged, reproduced) +* Number of bugs closed this week + +Each week these are tracked and shared in the weekly KPI sheet by Luke Heath. + + +### Edit a DNS record + +We use Cloudflare to manage the DNS records of fleetdm.com and our other domains. To make DNS changes in Cloudflare: +1. Log into your Cloudflare account and select the "Fleet" account. +2. Select the domain you want to change and go to the DNS panel on that domain's dashboard. +3. To add a record, click the "Add record" button, select the record's type, fill in the required values, and click "Save". If you're making changes to an existing record, you only need to click on the record, update the record's values, and save your changes. + +> If you need access to Fleet's Cloudflare account, please ask the [DRI](https://fleetdm.com/handbook/company/why-this-way#why-direct-responsibility) [Luke Heath](https://fleetdm.com/handbook/engineering#team) in Slack for an invitation. + + +### Accept new Apple developer account terms + +Engineering is responsible for managing third-party accounts required to support engineering infrastructure. We use the official Fleet Apple developer account to notarize installers we generate for Apple devices. Whenever Apple releases new terms of service, we are unable to notarize new packages until the new terms are accepted. + +When this occurs, we will begin receiving the following error message when attempting to notarize packages: "You must first sign the relevant contracts online." To resolve this error, follow the steps below. + +1. Visit the [Apple developer account login page](https://appleid.apple.com/account?appId=632&returnUrl=https%3A%2F%2Fdeveloper.apple.com%2Fcontact%2F). + +2. Log in using the credentials stored in 1Password under "Apple developer account". + +3. Contact the Head of Digital Experience to determine which phone number to use for 2FA. + +4. Complete the 2FA process to log in. + +5. Accept the new terms of service. + + +### Renew MDM certificate signing request (CSR) + +The certificate signing request (CSR) certificate expires every year. It needs to be renewed prior to expiring. This is notified to the team by the MDM calendar event [IMPORTANT: Renew MDM CSR certificate](https://calendar.google.com/calendar/u/0/r/eventedit/MmdqNTY4dG9nbWZycnNxbDBzYjQ5dGplM2FfMjAyNDA5MDlUMTczMDAwWiBjXzMyMjM3NjgyZGRlOThlMzI4MjVhNTY1ZDEyZjk0MDEyNmNjMWI0ZDljYjZjNjgyYzQ2MjcxZGY0N2UzNjM5NDZAZw) + +Steps to renew the certificate: + +1. Visit the [Apple developer account login page](https://developer.apple.com/account). +2. Log in using the credentials stored in 1Password under **Apple developer account**. +3. Verify you are using the **Enterprise** subaccount for Fleet Device Management Inc. +4. Generate a new certificate following the instructions in [MicroMDM](https://github.com/micromdm/micromdm/blob/c7e70b94d0cfc7710e5c92be20d4534d9d5a0640/docs/user-guide/quickstart.md?plain=1#L103-L118). +5. Note: `mdmctl` (a micromdm command for MDM vendors) will generate a `VendorPrivateKey.key` and `VendorCertificateRequest.csr` using an appropriate shared email relay and a passphrase (suggested generation method with pwgen available in brew / apt / yum `pwgen -s 32 -1vcy`) +6. Uploading `VendorCertificateRequest.csr` to Apple you will download a corresponding `mdm.cer` file +7. Convert the downloaded cert to PEM with `openssl x509 -inform DER -outform PEM -in mdm.cer -out server.crt.pem` +8. Update the **Config vars** in [Heroku](https://dashboard.heroku.com/apps/production-fleetdm-website/settings): +* Update `sails_custom__mdmVendorCertPem` with the results from step 7 `server.crt.pem` +* Update `sails_custom__mdmVendorKeyPassphrase` with the passphrase used in step 4 +* Update `sails_custom__mdmVendorKeyPem` with `VendorPrivateKey.key` from step 4 +9. Store updated values in [Confidential 1Password Vault](https://start.1password.com/open/i?a=N3F7LHAKQ5G3JPFPX234EC4ZDQ&v=lcvkjobeheaqdgnz33ontpuhxq&i=byyfn2knejwh42a2cbc5war5sa&h=fleetdevicemanagement.1password.com) +10. Verify by logging into a normal apple account (not billing@...) and Generate a new Push Certificate following our [setup MDM](https://fleetdm.com/docs/using-fleet/mdm-setup) steps and verify the Expiration date is 1 year from today. +11. Adjust calendar event to be between 2-4 weeks before the next expiration. + + ### Maintain TUF repo for secure agent updates Instructions for creating and maintaining a TUF repo are available on our [TUF handbook page](https://fleetdm.com/handbook/engineering/tuf). From f47c44f685a9d3c9350ddf220350aa40d539edf0 Mon Sep 17 00:00:00 2001 From: Allen Houchins <32207388+allenhouchins@users.noreply.github.com> Date: Mon, 25 Nov 2024 12:27:56 -0600 Subject: [PATCH 04/92] Enable macOS Setup Experience for Workstations team (#24125) In support of: https://github.com/fleetdm/confidential/issues/8790 I made the following changes to support the macOS Setup Experience in `dogfood` for the Workstations team - moved Software titles to their own dedicated folder and `yml` files so they could be called via path - edited the `macos_setup` configuration in the Workstations team yml file. - edited the `macos_setup_assistant` json file to skip certain items during initial setup - completed a `dry-run` successfully before submitting this pull request # Expected behavior When an ADE Mac boots for the first time, macOS Setup Experience will automatically install Google Chrome, Zoom, Slack, and 1Password. - Google Chrome will be downloaded via URL - Zoom will be downloaded via URL - Slack will be downloaded via VPP - 1Password will be downloaded via VPP No scripts have been included at this time and will be tracked in a separate issue. --- it-and-security/lib/automatic-enrollment.dep.json | 3 --- .../lib/software/mac-google-chrome.yml | 2 ++ it-and-security/lib/software/mac-zoom-arm.yml | 4 ++++ it-and-security/teams/workstations.yml | 15 ++++++++------- 4 files changed, 14 insertions(+), 10 deletions(-) create mode 100644 it-and-security/lib/software/mac-google-chrome.yml create mode 100644 it-and-security/lib/software/mac-zoom-arm.yml diff --git a/it-and-security/lib/automatic-enrollment.dep.json b/it-and-security/lib/automatic-enrollment.dep.json index b7a6289ee5..2836d719d3 100644 --- a/it-and-security/lib/automatic-enrollment.dep.json +++ b/it-and-security/lib/automatic-enrollment.dep.json @@ -6,11 +6,8 @@ "language": "en", "region": "US", "skip_setup_items": [ - "Accessibility", - "Appearance", "AppleID", "AppStore", - "Biometric", "Diagnostics", "FileVault", "iCloudDiagnostics", diff --git a/it-and-security/lib/software/mac-google-chrome.yml b/it-and-security/lib/software/mac-google-chrome.yml new file mode 100644 index 0000000000..fb3d1f7be9 --- /dev/null +++ b/it-and-security/lib/software/mac-google-chrome.yml @@ -0,0 +1,2 @@ +url: https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg +self_service: true \ No newline at end of file diff --git a/it-and-security/lib/software/mac-zoom-arm.yml b/it-and-security/lib/software/mac-zoom-arm.yml new file mode 100644 index 0000000000..38503c7aaf --- /dev/null +++ b/it-and-security/lib/software/mac-zoom-arm.yml @@ -0,0 +1,4 @@ +url: https://zoom.us/client/latest/Zoom.pkg?archType=arm64 +pre_install_query: + path: ../lib/macos-check-if-apple-silicon.queries.yml +self_service: true \ No newline at end of file diff --git a/it-and-security/teams/workstations.yml b/it-and-security/teams/workstations.yml index 4c30ac4b00..3aca539842 100644 --- a/it-and-security/teams/workstations.yml +++ b/it-and-security/teams/workstations.yml @@ -59,7 +59,12 @@ controls: macos_setup: bootstrap_package: "" enable_end_user_authentication: true - macos_setup_assistant: null + macos_setup_assistant: ../lib/automatic-enrollment.dep.json + software: + - package_path: ../lib/software/mac-google-chrome.yml # Google Chrome for macOS + - package_path: ../lib/software/mac-zoom-arm.yml # Zoom for macOS + - app_store_id: '803453959' # Slack Desktop + - app_store_id: '1333542190' # 1Password 7 Desktop macos_updates: deadline: "2024-12-02" minimum_version: "15.1.1" @@ -101,12 +106,8 @@ queries: observer_can_run: true software: packages: - - url: https://zoom.us/client/latest/Zoom.pkg?archType=arm64 - pre_install_query: - path: ../lib/macos-check-if-apple-silicon.queries.yml - self_service: true - - url: https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg - self_service: true + - path: ../lib/software/mac-zoom-arm.yml # Zoom for macOS + - path: ../lib/software/mac-google-chrome.yml # Google Chrome for macOS app_store_apps: - app_store_id: '803453959' # Slack Desktop - app_store_id: '1333542190' # 1Password 7 Desktop From bb4e3c632ed0ab3b9327c829ac56295df1509478 Mon Sep 17 00:00:00 2001 From: jacobshandling <61553566+jacobshandling@users.noreply.github.com> Date: Mon, 25 Nov 2024 10:28:58 -0800 Subject: [PATCH 05/92] UI - Fix another (less) flakey frontend test (#24110) - [x] Added/updated tests test-js (ubuntu-latest) SuccessCount / RunCount: 3/3 Co-authored-by: Jacob Shandling --- .../components/StatusIndicator/StatusIndicator.tests.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/components/StatusIndicator/StatusIndicator.tests.tsx b/frontend/components/StatusIndicator/StatusIndicator.tests.tsx index 73e4b665e4..418b6ce96d 100644 --- a/frontend/components/StatusIndicator/StatusIndicator.tests.tsx +++ b/frontend/components/StatusIndicator/StatusIndicator.tests.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { render, screen, fireEvent, act } from "@testing-library/react"; +import { render, screen, fireEvent } from "@testing-library/react"; import StatusIndicator from "./StatusIndicator"; @@ -16,9 +16,7 @@ describe("Status indicator", () => { ); - await act(async () => { - fireEvent.mouseEnter(screen.getByText("Online")); - }); + await fireEvent.mouseEnter(screen.getByText("Online")); expect(screen.getByText(TOOLTIP_TEXT)).toBeInTheDocument(); }); From 1446d280296c5488b1a40bf047ee589730f3a5c6 Mon Sep 17 00:00:00 2001 From: Konstantin Sykulev Date: Mon, 25 Nov 2024 12:32:10 -0600 Subject: [PATCH 06/92] github cli false negative vulnerability (#24100) Added a cpe translation for the `gh` command. The software is identified as `gh`, however, the cpe (`cpe:2.3:a:github:cli:2.62.0:*:*:*:*:*:*:*`) name is labeled as `cli`, thus the mismatch. https://github.com/fleetdm/fleet/issues/24009 # 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/24009-gh-translation | 1 + server/vulnerabilities/nvd/cpe_test.go | 18 ++++++++++++++++++ .../vulnerabilities/nvd/cpe_translations.json | 9 +++++++++ 3 files changed, 28 insertions(+) create mode 100644 changes/24009-gh-translation diff --git a/changes/24009-gh-translation b/changes/24009-gh-translation new file mode 100644 index 0000000000..103bd7b6eb --- /dev/null +++ b/changes/24009-gh-translation @@ -0,0 +1 @@ +* Fixed an issue where the github cli software name was not matching against the cpe vulnerability name \ No newline at end of file diff --git a/server/vulnerabilities/nvd/cpe_test.go b/server/vulnerabilities/nvd/cpe_test.go index 386234d8ac..0628f95d67 100644 --- a/server/vulnerabilities/nvd/cpe_test.go +++ b/server/vulnerabilities/nvd/cpe_test.go @@ -1663,6 +1663,24 @@ func TestCPEFromSoftwareIntegration(t *testing.T) { }, cpe: "cpe:2.3:a:oracle:virtualbox:7.0.12:*:*:*:*:macos:*:*", }, + { + software: fleet.Software{ + Name: "gh", + Source: "deb_packages", + Version: "2.61.0", + Vendor: "", + BundleIdentifier: "", + }, cpe: "cpe:2.3:a:github:cli:2.61.0:*:*:*:*:*:*:*", + }, + { + software: fleet.Software{ + Name: "gh", + Source: "homebrew_packages", + Version: "2.61.0", + Vendor: "", + BundleIdentifier: "", + }, cpe: "cpe:2.3:a:github:cli:2.61.0:*:*:*:*:macos:*:*", + }, } // NVD_TEST_CPEDB_PATH can be used to speed up development (sync cpe.sqlite only once). diff --git a/server/vulnerabilities/nvd/cpe_translations.json b/server/vulnerabilities/nvd/cpe_translations.json index ec03b2e26c..59518162bd 100644 --- a/server/vulnerabilities/nvd/cpe_translations.json +++ b/server/vulnerabilities/nvd/cpe_translations.json @@ -426,5 +426,14 @@ "product": ["virtualbox"], "vendor": ["oracle"] } + }, + { + "software": { + "name": ["gh"] + }, + "filter": { + "product": ["cli"], + "vendor": ["github"] + } } ] \ No newline at end of file From 7d04119245d95a0082b9f4b6cc3e8c8b42587fb2 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Rodriguez Date: Mon, 25 Nov 2024 16:34:09 -0300 Subject: [PATCH 07/92] Release fleetd 1.36.0 (#24136) --- .github/workflows/generate-desktop-targets.yml | 9 +++------ orbit/CHANGELOG.md | 18 ++++++++++++++++++ orbit/changes/21948-self-service-checked | 1 - orbit/changes/22047-linux-key-escrow | 1 - orbit/changes/22810-fleetd-enroll-activity | 2 -- orbit/changes/23164-delete-migrated-identifier | 1 - orbit/changes/23438-fleetd-with-mdm-removed | 1 - orbit/changes/add-codesign-table | 1 - ...pgrade-macadmins-osquery-extension-to-1.2.3 | 1 - 9 files changed, 21 insertions(+), 14 deletions(-) delete mode 100644 orbit/changes/21948-self-service-checked delete mode 100644 orbit/changes/22047-linux-key-escrow delete mode 100644 orbit/changes/22810-fleetd-enroll-activity delete mode 100644 orbit/changes/23164-delete-migrated-identifier delete mode 100644 orbit/changes/23438-fleetd-with-mdm-removed delete mode 100644 orbit/changes/add-codesign-table delete mode 100644 orbit/changes/upgrade-macadmins-osquery-extension-to-1.2.3 diff --git a/.github/workflows/generate-desktop-targets.yml b/.github/workflows/generate-desktop-targets.yml index 5633467a88..8f28f858fe 100644 --- a/.github/workflows/generate-desktop-targets.yml +++ b/.github/workflows/generate-desktop-targets.yml @@ -19,20 +19,17 @@ defaults: shell: bash env: - FLEET_DESKTOP_VERSION: 1.35.0 + FLEET_DESKTOP_VERSION: 1.36.0 permissions: contents: read jobs: desktop-macos: - # Set macOS version to '12' (current equivalent to macos-latest) for + # Set macOS version to '13' (previously was macos-12, and it was deprecated) for # building the binary. This ensures compatibility with macOS version 13 and # later, avoiding runtime errors on systems using macOS 13 or newer. - # - # Note: Update this version to '13' once GitHub marks macOS 13 as stable - # or if we revise our minimum supported macOS version. - runs-on: macos-12 + runs-on: macos-13 steps: - name: Harden Runner diff --git a/orbit/CHANGELOG.md b/orbit/CHANGELOG.md index 780120ca45..e2156c24dc 100644 --- a/orbit/CHANGELOG.md +++ b/orbit/CHANGELOG.md @@ -1,3 +1,21 @@ +## Orbit 1.36.0 (Nov 25, 2024) + +* Upgraded macadmins osquery-extension to v1.2.3. + +* Added `computer_name` and `hardware_model` for fleetd enrollment. + +* Added serial number for fleetd enrollment for Windows hosts (already present for macOS and Linux). + +* Added `codesign` table to provide the "Team identifier" of macOS applications. + +* Fixed stale Fleet Desktop token UUID after a macOS host completes Migration Assistant. + +* Added functionality to support linux disk encryption key escrow including end user prompts and LUKS key management + +* Fixed issue with fleetd not able to connect to Fleet server after Fleet MDM profiles have been removed. + +* Fixed cases where self-service menu item temporarily disappeared from Fleet Desktop menu when it should have stayed visible. + ## Orbit 1.35.0 (Nov 01, 2024) * Fixed orbit startup to not exit when "root.json", "snapshot.json", or "targets.json" TUF signatures have expired. diff --git a/orbit/changes/21948-self-service-checked b/orbit/changes/21948-self-service-checked deleted file mode 100644 index c8a10876f9..0000000000 --- a/orbit/changes/21948-self-service-checked +++ /dev/null @@ -1 +0,0 @@ -* Fixed cases where self-service menu item temporarily disappeared from Fleet Desktop menu when it should have stayed visible. diff --git a/orbit/changes/22047-linux-key-escrow b/orbit/changes/22047-linux-key-escrow deleted file mode 100644 index d8a3daa001..0000000000 --- a/orbit/changes/22047-linux-key-escrow +++ /dev/null @@ -1 +0,0 @@ -* added functionality to support linux disk encryption key escrow including end user prompts and LUKS key management \ No newline at end of file diff --git a/orbit/changes/22810-fleetd-enroll-activity b/orbit/changes/22810-fleetd-enroll-activity deleted file mode 100644 index 2b99a1a860..0000000000 --- a/orbit/changes/22810-fleetd-enroll-activity +++ /dev/null @@ -1,2 +0,0 @@ -Added computer_name and hardware_model for fleetd enrollment. -Added serial number for fleetd enrollment for Windows hosts (already present for macOS and Linux). diff --git a/orbit/changes/23164-delete-migrated-identifier b/orbit/changes/23164-delete-migrated-identifier deleted file mode 100644 index 367f7eebc2..0000000000 --- a/orbit/changes/23164-delete-migrated-identifier +++ /dev/null @@ -1 +0,0 @@ -* Fixed stale Fleet Desktop token UUID after a macOS host completes Migration Assistant. diff --git a/orbit/changes/23438-fleetd-with-mdm-removed b/orbit/changes/23438-fleetd-with-mdm-removed deleted file mode 100644 index 7112459566..0000000000 --- a/orbit/changes/23438-fleetd-with-mdm-removed +++ /dev/null @@ -1 +0,0 @@ -Fixed issue with fleetd not able to connect to Fleet server after Fleet MDM profiles have been removed. diff --git a/orbit/changes/add-codesign-table b/orbit/changes/add-codesign-table deleted file mode 100644 index 49b38025d6..0000000000 --- a/orbit/changes/add-codesign-table +++ /dev/null @@ -1 +0,0 @@ -* Added `codesign` table to provide the "Team identifier" of macOS applications. diff --git a/orbit/changes/upgrade-macadmins-osquery-extension-to-1.2.3 b/orbit/changes/upgrade-macadmins-osquery-extension-to-1.2.3 deleted file mode 100644 index 81025b3fbc..0000000000 --- a/orbit/changes/upgrade-macadmins-osquery-extension-to-1.2.3 +++ /dev/null @@ -1 +0,0 @@ -* Upgraded macadmins osquery-extension to v1.2.3. From c53332259f236d698da79b66d6466dc7cf0b0dc5 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 25 Nov 2024 13:55:56 -0600 Subject: [PATCH 08/92] Website: Update license dispenser form (#23838) Closes: https://github.com/fleetdm/confidential/issues/7696 Changes: - Added `stripe` as a dependency - Updated the license dispenser form to take users to a stripe hosted checkout page where they can provide their billing address and Tax ID depending on their location. - Updated the receive-from-stripe webhook to fulfill license dispenser purchases made via stripe checkout - Added a new action: get-stripe-checkout-session-url. This action creates a Stripe Checkout session and returns the URL - Updated the customer dashboard to have a link that users can visit to update their billing information, add more hosts to their Fleet premium license, or cancel their subscription. - Added a new action: redirect-to-stripe-billing-portal. An action that redirects users to a Stripe-hosted billing portal. --- .../get-stripe-checkout-session-url.js | 70 +++++++++++++++++++ .../redirect-to-stripe-billing-portal.js | 41 +++++++++++ .../controllers/customers/view-dashboard.js | 17 ++++- .../webhooks/receive-from-stripe.js | 43 ++++++++++-- website/assets/js/cloud.setup.js | 2 +- .../js/pages/customers/new-license.page.js | 11 ++- website/config/routes.js | 2 + website/package.json | 3 +- website/views/pages/customers/dashboard.ejs | 17 ++--- website/views/pages/customers/new-license.ejs | 34 +-------- 10 files changed, 187 insertions(+), 53 deletions(-) create mode 100644 website/api/controllers/customers/get-stripe-checkout-session-url.js create mode 100644 website/api/controllers/customers/redirect-to-stripe-billing-portal.js diff --git a/website/api/controllers/customers/get-stripe-checkout-session-url.js b/website/api/controllers/customers/get-stripe-checkout-session-url.js new file mode 100644 index 0000000000..324e428a91 --- /dev/null +++ b/website/api/controllers/customers/get-stripe-checkout-session-url.js @@ -0,0 +1,70 @@ +module.exports = { + + + friendlyName: 'Get Stripe checkout session url', + + + description: 'Creates a Stripe checkout session for a new Fleet Premium subscription and returns the URL', + + + inputs: { + quoteId: { + type: 'number', + required: true, + description: 'The quote to use (determines the price and number of hosts.)' + }, + + }, + + + exits: { + success: { + description: 'A Stripe checkout session was successfully created for a new Fleet Premium subscription.' + } + }, + + + fn: async function (inputs) { + // Configure Stripe + const stripe = require('stripe')(sails.config.custom.stripeSecret); + + // Find the quote record that was created. + let quoteRecord = await Quote.findOne({id: inputs.quoteId}); + if(!quoteRecord) { + throw new Error(`Consistency violation: The specified quote (${inputs.quoteId}) no longer seems to exist.`); + } + + // What if the stripe customer id doesn't already exist on the user? + if (!this.req.me.stripeCustomerId) { + throw new Error(`Consistency violation: The logged-in user's (${this.req.me.emailAddress}) Stripe customer id has somehow gone missing!`); + } + // Create a new Stripe checkout session for this subscription. + let stripeCheckoutSession = await stripe.checkout.sessions.create({ + customer: this.req.me.stripeCustomerId, + customer_update: {// eslint-disable-line camelcase + name: 'auto', + address: 'auto', + }, + success_url: `${sails.config.custom.baseUrl}/customers/dashboard?order-complete`,// eslint-disable-line camelcase + line_items: [// eslint-disable-line camelcase + { + price: sails.config.custom.stripeSubscriptionPriceId, + quantity: quoteRecord.numberOfHosts, + }, + ], + mode: 'subscription', + billing_address_collection: 'required',// eslint-disable-line camelcase + tax_id_collection: {// eslint-disable-line camelcase + enabled: true, + required: 'if_supported' + } + }); + + // Return the url of the Stripe checkout session. + // Users will be taken to this URL via the handleSubmitting function of the on the /customers/new-license page. + return stripeCheckoutSession.url; + + } + + +}; diff --git a/website/api/controllers/customers/redirect-to-stripe-billing-portal.js b/website/api/controllers/customers/redirect-to-stripe-billing-portal.js new file mode 100644 index 0000000000..9e094da429 --- /dev/null +++ b/website/api/controllers/customers/redirect-to-stripe-billing-portal.js @@ -0,0 +1,41 @@ +module.exports = { + + + friendlyName: 'Redirect to stripe billing portal', + + + description: 'Creates a Stripe billing portal session for a Fleet Premium subscriber and redirects them.', + + + exits: { + redirect: { + responseType: 'redirect', + description: 'The requesting user is being redirected to the Stripe customer billing portal.' + }, + noSubscription: { + responseType: 'redirect', + description: 'The Requesting user does not have a Fleet premium subscription.' + }, + }, + + + fn: async function () { + // Note: This action is covered by the 'is-logged-in' policy. + const stripe = require('stripe')(sails.config.custom.stripeSecret); + + let thisUsersSubscription = await Subscription.findOne({user: this.req.me.id}); + if(!thisUsersSubscription){ + throw {noSubscription: '/customers/new-license'}; + } + + let session = await stripe.billingPortal.sessions.create({ + customer: this.req.me.stripeCustomerId, + return_url: `${sails.config.custom.baseUrl}/customers/dashboard`,// eslint-disable-line camelcase + }); + // All done. + throw {redirect: session.url}; + + } + + +}; diff --git a/website/api/controllers/customers/view-dashboard.js b/website/api/controllers/customers/view-dashboard.js index 2cc99e4820..338159e6b4 100644 --- a/website/api/controllers/customers/view-dashboard.js +++ b/website/api/controllers/customers/view-dashboard.js @@ -22,7 +22,7 @@ module.exports = { fn: async function () { - + const stripe = require('stripe')(sails.config.custom.stripeSecret); const today = Date.now(); const oneYearInMs = (1000 * 60 * 60 * 24 * 365); const oneYearAgoAt = today - oneYearInMs; @@ -30,24 +30,33 @@ module.exports = { const thirtyDaysFromNowAt = today + (1000 * 60 * 60 * 24 * 30); let subscriptionHasBeenRecentlyRenewed = false; let subscriptionExpiresSoon = false; + let subscriptionIsExpired = false; // Get subscription Info let thisSubscription = await Subscription.findOne({user: this.req.me.id}); - // If the user does not have a subscription, then help them subscribe. if(!thisSubscription) { throw {redirect: '/customers/new-license'}; } + let stripeSubscriptionDetails = await stripe.subscriptions.retrieve(thisSubscription.stripeSubscriptionId); + let willSubscriptionRenew = true; + if(stripeSubscriptionDetails.cancel_at_period_end === true){ + willSubscriptionRenew = false; + } // If this subscription is over a year old, and was renewed in the past 30 days set subscriptionHasBeenRecentlyRenewed to true. if(thisSubscription.createdAt <= oneYearAgoAt && (thisSubscription.nextBillingAt - oneYearInMs) >= thirtyDaysAgoAt) { subscriptionHasBeenRecentlyRenewed = true; } // If this subscription will renew in the next 30 days, set subscriptionExpiresSoon to true. - if(thisSubscription.nextBillingAt <= thirtyDaysFromNowAt){ + if(thisSubscription.nextBillingAt <= thirtyDaysFromNowAt && willSubscriptionRenew){ subscriptionExpiresSoon = true; } + // If this subscription is expired, set subscriptionIsExpired to true. + if(thisSubscription.nextBillingAt <= Date.now()){ + subscriptionIsExpired = true; + } // Respond with view. return { @@ -55,6 +64,8 @@ module.exports = { thisSubscription, subscriptionExpiresSoon, subscriptionHasBeenRecentlyRenewed, + willSubscriptionRenew, + subscriptionIsExpired, }; } diff --git a/website/api/controllers/webhooks/receive-from-stripe.js b/website/api/controllers/webhooks/receive-from-stripe.js index 394c17851e..9f505c8d02 100644 --- a/website/api/controllers/webhooks/receive-from-stripe.js +++ b/website/api/controllers/webhooks/receive-from-stripe.js @@ -41,7 +41,7 @@ module.exports = { fn: async function ({id, type, data, webhookSecret}) { - + const stripe = require('stripe')(sails.config.custom.stripeSecret); let assert = require('assert'); if(!this.req.get('stripe-signature')) { @@ -77,6 +77,7 @@ module.exports = { 'invoice.payment_action_required',// Sent when a user's billing card requires additional verification from stripe. 'invoice.updated',// Sent before an incomplete invoice is voided. (~24 hours after a payment fails) 'invoice.voided',// Sent when an incomplete invoice is marked as voided. (~24 hours after a payment fails) + 'checkout.session.completed'// Sent when a user completes a Stripe Checkout session. ]; // If this event is for a subscription that was just created, we won't have a matching Subscription record in the database. This is because we wait until the subscription's invoice is paid to create the record in our database. @@ -86,15 +87,16 @@ module.exports = { throw new Error(`The Stripe subscription events webhook received a event for a subscription with stripeSubscriptionId: ${subscriptionIdToFind}, but no matching record was found in our database.`); } else { let userReferencedInStripeEvent = await User.findOne({stripeCustomerId: stripeEventData.customer}); - if(!userReferencedInStripeEvent){ + if(!userReferencedInStripeEvent) { throw new Error(`The receive-from-stripe webhook received an event for an invoice (type: ${type}) for a subscription (stripeSubscriptionId: ${subscriptionIdToFind}) but no matching Subscription or User record (stripeCustomerId: ${stripeEventData.customer}) was found in our databse.`); - } else { - return; } } } - let userForThisSubscription = subscriptionForThisEvent.user; + let userForThisSubscription = await User.findOne({stripeCustomerId: stripeEventData.customer}); + if(!userForThisSubscription){ + throw new Error(`The stripe subscription events webhook received a tpye ${type} event for a user with stripeCustomerId: ${stripeEventData.customer}, but no matching user was found in the databse. Stripe event ID: ${id}`); + } // ┬ ┬┌─┐┌─┐┌─┐┌┬┐┬┌┐┌┌─┐ ┬─┐┌─┐┌┐┌┌─┐┬ ┬┌─┐┬ // │ │├─┘│ │ │││││││││ ┬ ├┬┘├┤ │││├┤ │││├─┤│ // └─┘┴ └─┘└─┘┴ ┴┴┘└┘└─┘ ┴└─└─┘┘└┘└─┘└┴┘┴ ┴┴─┘ @@ -200,6 +202,37 @@ module.exports = { fleetLicenseKey: newLicenseKeyForThisSubscription, nextBillingAt: nextBillingAt }); + } else if(type === 'checkout.session.completed' && stripeEventData.payment_status === 'paid') { + // For handling successful payments from a Stripe checkout session. + // Note: This event is sent the moment the user's payment succeeds. + if(subscriptionForThisEvent){// Throw an error if there is an existing subscription with this ID that matches this event in the website's database. + throw new Error(`Consistency violation! The stripe webhook received a "${type}" event for a new subscription being created, but a subscription with the stripe ID ${subscriptionForThisEvent.stripeSubscriptionId} already exists.`); + } + // Retrieve the subscription details from Stripe. + let newSubscriptionDetails = await stripe.subscriptions.retrieve(stripeEventData.subscription); + // Convert the timestamp of the next time this subscription will be billed into a JS timestamp (Epoch MS) + let nextBillingAt = newSubscriptionDetails.current_period_end * 1000; + // Get the number of Hosts. + let numberOfHosts = newSubscriptionDetails.quantity; + // Get the whole dollar price per host. + let subscriptionPricePerHost = newSubscriptionDetails.plan.amount / 100; + // Determine the annual cost of this user's subscription + let subscriptionPrice = subscriptionPricePerHost * numberOfHosts; + // Generate a new license key. + let newLicenseKey = await sails.helpers.createLicenseKey.with({ + numberOfHosts, + organization: userForThisSubscription.organization, + expiresAt: nextBillingAt, + }); + // Create the database record for this subscription. + await Subscription.create({ + nextBillingAt, + numberOfHosts, + subscriptionPrice, + stripeSubscriptionId: newSubscriptionDetails.id, + fleetLicenseKey: newLicenseKey, + user: userForThisSubscription.id, + }); } // FUTURE: send emails about failed payments. (type === 'invoice.payment_failed' && stripeEventData.billing_reason === 'subscription_cycle') diff --git a/website/assets/js/cloud.setup.js b/website/assets/js/cloud.setup.js index 9c08e4dde1..35f7bcdf45 100644 --- a/website/assets/js/cloud.setup.js +++ b/website/assets/js/cloud.setup.js @@ -13,7 +13,7 @@ Cloud.setup({ /* eslint-disable */ - methods: {"downloadSitemap":{"verb":"GET","url":"/sitemap.xml","args":[]},"downloadRssFeed":{"verb":"GET","url":"/rss/:categoryName","args":["categoryName"]},"receiveUsageAnalytics":{"verb":"POST","url":"/api/v1/webhooks/receive-usage-analytics","args":["anonymousIdentifier","fleetVersion","licenseTier","numHostsEnrolled","numUsers","numTeams","numPolicies","numLabels","softwareInventoryEnabled","vulnDetectionEnabled","systemUsersEnabled","hostsStatusWebHookEnabled","numWeeklyActiveUsers","numWeeklyPolicyViolationDaysActual","numWeeklyPolicyViolationDaysPossible","hostsEnrolledByOperatingSystem","hostsEnrolledByOrbitVersion","hostsEnrolledByOsqueryVersion","storedErrors","numHostsNotResponding","organization","mdmMacOsEnabled","mdmWindowsEnabled","liveQueryDisabled","hostExpiryEnabled","numSoftwareVersions","numHostSoftwares","numSoftwareTitles","numHostSoftwareInstalledPaths","numSoftwareCPEs","numSoftwareCVEs"]},"receiveFromGithub":{"verb":"GET","url":"/api/v1/webhooks/github","args":["botSignature","action","sender","repository","changes","issue","comment","pull_request","label","release"]},"receiveFromStripe":{"verb":"POST","url":"/api/v1/webhooks/receive-from-stripe","args":["id","type","data","webhookSecret"]},"deliverContactFormMessage":{"verb":"POST","url":"/api/v1/deliver-contact-form-message","args":["emailAddress","firstName","lastName","message"]},"sendPasswordRecoveryEmail":{"verb":"POST","url":"/api/v1/entrance/send-password-recovery-email","args":["emailAddress"]},"signup":{"verb":"POST","url":"/api/v1/customers/signup","args":["emailAddress","password","organization","firstName","lastName","signupReason"]},"updateProfile":{"verb":"POST","url":"/api/v1/account/update-profile","args":["firstName","lastName","organization","emailAddress"]},"updatePassword":{"verb":"POST","url":"/api/v1/account/update-password","args":["oldPassword","newPassword"]},"updateBillingCard":{"verb":"POST","url":"/api/v1/account/update-billing-card","args":["stripeToken","billingCardLast4","billingCardBrand","billingCardExpMonth","billingCardExpYear"]},"login":{"verb":"POST","url":"/api/v1/customers/login","args":["emailAddress","password","rememberMe"]},"logout":{"verb":"GET","url":"/api/v1/account/logout","args":[]},"createQuote":{"verb":"POST","url":"/api/v1/customers/create-quote","args":["numberOfHosts"]},"saveBillingInfoAndSubscribe":{"verb":"POST","url":"/api/v1/customers/save-billing-info-and-subscribe","args":["quoteId","organization","firstName","lastName","paymentSource"]},"updatePasswordAndLogin":{"verb":"POST","url":"/api/v1/entrance/update-password-and-login","args":["password","token"]},"deliverDemoSignup":{"verb":"POST","url":"/api/v1/deliver-demo-signup","args":["emailAddress"]},"createOrUpdateOneNewsletterSubscription":{"verb":"POST","url":"/api/v1/create-or-update-one-newsletter-subscription","args":["emailAddress","subscribeTo"]},"unsubscribeFromAllNewsletters":{"verb":"GET","url":"/api/v1/unsubscribe-from-all-newsletters","args":["emailAddress"]},"buildLicenseKey":{"verb":"POST","url":"/api/v1/admin/build-license-key","args":["numberOfHosts","organization","expiresAt","partnerName"]},"createVantaAuthorizationRequest":{"verb":"POST","url":"/api/v1/create-vanta-authorization-request","args":["emailAddress","fleetInstanceUrl","fleetApiKey","redirectToExternalPageAfterAuthorization","sharedSecret"]},"redirectVantaAuthorizationRequest":{"verb":"GET","url":"/redirect-vanta-authorization-request","args":["vantaSourceId","state","vantaAuthorizationRequestURL","redirectAfterSetup"]},"deliverMdmBetaSignup":{"verb":"POST","url":"/api/v1/deliver-mdm-beta-signup","args":["emailAddress","fullName","jobTitle","numberOfHosts"]},"getHumanInterpretationFromOsquerySql":{"verb":"POST","url":"/api/v1/get-human-interpretation-from-osquery-sql","args":["sql"]},"deliverAppleCsr":{"verb":"POST","url":"/api/v1/deliver-apple-csr","args":["unsignedCsrData","deliveryMethod"]},"deliverMdmDemoEmail":{"verb":"POST","url":"/api/v1/deliver-mdm-demo-email","args":["emailAddress"]},"provisionSandboxInstanceAndDeliverEmail":{"verb":"POST","url":"/api/v1/admin/provision-sandbox-instance-and-deliver-email","args":["userId"]},"deliverTalkToUsFormSubmission":{"verb":"POST","url":"/api/v1/deliver-talk-to-us-form-submission","args":["emailAddress","firstName","lastName","organization","numberOfHosts","primaryBuyingSituation"]},"saveQuestionnaireProgress":{"verb":"POST","url":"/api/v1/save-questionnaire-progress","args":["currentStep","formData"]},"updateStartCtaVisibility":{"verb":"POST","url":"/api/v1/account/update-start-cta-visibility","args":[]},"deliverDealRegistrationSubmission":{"verb":"POST","url":"/api/v1/deliver-deal-registration-submission","args":["submittersFirstName","submittersLastName","submittersEmailAddress","submittersOrganization","customersFirstName","customersLastName","customersEmailAddress","linkedinUrl","customersOrganization","customersCurrentMdm","otherMdmEvaluated","preferredHosting","expectedDealSize","expectedCloseDate","notes"]}} + methods: {"redirectToStripeBillingPortal":{"verb":"GET","url":"/customers/update-subscription","args":[]},"downloadSitemap":{"verb":"GET","url":"/sitemap.xml","args":[]},"downloadRssFeed":{"verb":"GET","url":"/rss/:categoryName","args":["categoryName"]},"receiveUsageAnalytics":{"verb":"POST","url":"/api/v1/webhooks/receive-usage-analytics","args":["anonymousIdentifier","fleetVersion","licenseTier","numHostsEnrolled","numUsers","numTeams","numPolicies","numLabels","softwareInventoryEnabled","vulnDetectionEnabled","systemUsersEnabled","hostsStatusWebHookEnabled","numWeeklyActiveUsers","numWeeklyPolicyViolationDaysActual","numWeeklyPolicyViolationDaysPossible","hostsEnrolledByOperatingSystem","hostsEnrolledByOrbitVersion","hostsEnrolledByOsqueryVersion","storedErrors","numHostsNotResponding","organization","mdmMacOsEnabled","mdmWindowsEnabled","liveQueryDisabled","hostExpiryEnabled","numSoftwareVersions","numHostSoftwares","numSoftwareTitles","numHostSoftwareInstalledPaths","numSoftwareCPEs","numSoftwareCVEs","aiFeaturesDisabled","maintenanceWindowsEnabled","maintenanceWindowsConfigured","numHostsFleetDesktopEnabled"]},"receiveFromGithub":{"verb":"GET","url":"/api/v1/webhooks/github","args":["botSignature","action","sender","repository","changes","issue","comment","pull_request","label","release"]},"receiveFromStripe":{"verb":"POST","url":"/api/v1/webhooks/receive-from-stripe","args":["id","type","data","webhookSecret"]},"deliverContactFormMessage":{"verb":"POST","url":"/api/v1/deliver-contact-form-message","args":["emailAddress","firstName","lastName","message"]},"sendPasswordRecoveryEmail":{"verb":"POST","url":"/api/v1/entrance/send-password-recovery-email","args":["emailAddress"]},"signup":{"verb":"POST","url":"/api/v1/customers/signup","args":["emailAddress","password","organization","firstName","lastName","signupReason"]},"updateProfile":{"verb":"POST","url":"/api/v1/account/update-profile","args":["firstName","lastName","organization","emailAddress"]},"updatePassword":{"verb":"POST","url":"/api/v1/account/update-password","args":["oldPassword","newPassword"]},"updateBillingCard":{"verb":"POST","url":"/api/v1/account/update-billing-card","args":["stripeToken","billingCardLast4","billingCardBrand","billingCardExpMonth","billingCardExpYear"]},"login":{"verb":"POST","url":"/api/v1/customers/login","args":["emailAddress","password","rememberMe"]},"logout":{"verb":"GET","url":"/api/v1/account/logout","args":[]},"createQuote":{"verb":"POST","url":"/api/v1/customers/create-quote","args":["numberOfHosts"]},"saveBillingInfoAndSubscribe":{"verb":"POST","url":"/api/v1/customers/save-billing-info-and-subscribe","args":["quoteId","organization","firstName","lastName","paymentSource"]},"updatePasswordAndLogin":{"verb":"POST","url":"/api/v1/entrance/update-password-and-login","args":["password","token"]},"deliverDemoSignup":{"verb":"POST","url":"/api/v1/deliver-demo-signup","args":["emailAddress"]},"createOrUpdateOneNewsletterSubscription":{"verb":"POST","url":"/api/v1/create-or-update-one-newsletter-subscription","args":["emailAddress"]},"unsubscribeFromAllNewsletters":{"verb":"GET","url":"/api/v1/unsubscribe-from-all-newsletters","args":["emailAddress"]},"buildLicenseKey":{"verb":"POST","url":"/api/v1/admin/build-license-key","args":["numberOfHosts","organization","expiresAt","partnerName"]},"createVantaAuthorizationRequest":{"verb":"POST","url":"/api/v1/create-vanta-authorization-request","args":["emailAddress","fleetInstanceUrl","fleetApiKey","redirectToExternalPageAfterAuthorization","sharedSecret"]},"redirectVantaAuthorizationRequest":{"verb":"GET","url":"/redirect-vanta-authorization-request","args":["vantaSourceId","state","vantaAuthorizationRequestURL","redirectAfterSetup"]},"deliverMdmBetaSignup":{"verb":"POST","url":"/api/v1/deliver-mdm-beta-signup","args":["emailAddress","fullName","jobTitle","numberOfHosts"]},"getHumanInterpretationFromOsquerySql":{"verb":"POST","url":"/api/v1/get-human-interpretation-from-osquery-sql","args":["sql"]},"deliverAppleCsr":{"verb":"POST","url":"/api/v1/deliver-apple-csr","args":["unsignedCsrData","deliveryMethod"]},"deliverMdmDemoEmail":{"verb":"POST","url":"/api/v1/deliver-mdm-demo-email","args":["emailAddress"]},"provisionSandboxInstanceAndDeliverEmail":{"verb":"POST","url":"/api/v1/admin/provision-sandbox-instance-and-deliver-email","args":["userId"]},"deliverTalkToUsFormSubmission":{"verb":"POST","url":"/api/v1/deliver-talk-to-us-form-submission","args":["emailAddress","firstName","lastName","organization","numberOfHosts","primaryBuyingSituation"]},"saveQuestionnaireProgress":{"verb":"POST","url":"/api/v1/save-questionnaire-progress","args":["currentStep","formData"]},"updateStartCtaVisibility":{"verb":"POST","url":"/api/v1/account/update-start-cta-visibility","args":[]},"deliverDealRegistrationSubmission":{"verb":"POST","url":"/api/v1/deliver-deal-registration-submission","args":["submittersFirstName","submittersLastName","submittersEmailAddress","submittersOrganization","customersFirstName","customersLastName","customersEmailAddress","linkedinUrl","customersOrganization","customersCurrentMdm","otherMdmEvaluated","preferredHosting","expectedDealSize","expectedCloseDate","notes"]},"unsubscribeFromMarketingEmails":{"verb":"GET","url":"/api/v1/unsubscribe-from-marketing-emails","args":["emailAddress"]},"getStripeCheckoutSessionUrl":{"verb":"POST","url":"/api/v1/customers/get-stripe-checkout-session-url","args":["quoteId"]}} /* eslint-enable */ }); diff --git a/website/assets/js/pages/customers/new-license.page.js b/website/assets/js/pages/customers/new-license.page.js index e0e7e61654..e72ea5b395 100644 --- a/website/assets/js/pages/customers/new-license.page.js +++ b/website/assets/js/pages/customers/new-license.page.js @@ -19,6 +19,10 @@ parasails.registerPage('new-license', { selfHostedAcknowledgment: {required: true, is: true}, }, + checkoutFormRules: { + selfHostedAcknowledgment: {required: true, is: true}, + }, + // Syncing / loading state syncing: false, @@ -93,7 +97,12 @@ parasails.registerPage('new-license', { this.syncing = true; this.goto('/customers/dashboard?order-complete'); }, - + handleSubmittingCheckoutForm: async function() { + let redirectUrl = await Cloud.getStripeCheckoutSessionUrl.with({ + quoteId: this.formData.quoteId + }); + this.goto(redirectUrl); + }, submittedQuoteForm: async function(quote) { this.showQuotedPrice = true; this.quotedPrice = quote.quotedPrice; diff --git a/website/config/routes.js b/website/config/routes.js index 96f7701611..173a5673a6 100644 --- a/website/config/routes.js +++ b/website/config/routes.js @@ -136,6 +136,7 @@ module.exports.routes = { pageDescriptionForMeta: 'View and edit information about your Fleet Premium license.', } }, + 'GET /customers/update-subscription': { action: 'customers/redirect-to-stripe-billing-portal' }, 'GET /customers/forgot-password': { action: 'entrance/view-forgot-password', locals: { @@ -675,4 +676,5 @@ module.exports.routes = { 'POST /api/v1/account/update-start-cta-visibility': { action: 'account/update-start-cta-visibility' }, 'POST /api/v1/deliver-deal-registration-submission': { action: 'deliver-deal-registration-submission' }, '/api/v1/unsubscribe-from-marketing-emails': { action: 'unsubscribe-from-marketing-emails' }, + 'POST /api/v1/customers/get-stripe-checkout-session-url': { action: 'customers/get-stripe-checkout-session-url' }, }; diff --git a/website/package.json b/website/package.json index 23408ba0f6..d6029c1187 100644 --- a/website/package.json +++ b/website/package.json @@ -17,7 +17,8 @@ "sails-hook-organics": "^3.0.0", "sails-hook-orm": "^4.0.3", "sails-hook-sockets": "^3.0.0", - "sails-postgresql": "^5.0.1" + "sails-postgresql": "^5.0.1", + "stripe": "17.3.1" }, "devDependencies": { "eslint": "5.16.0", diff --git a/website/views/pages/customers/dashboard.ejs b/website/views/pages/customers/dashboard.ejs index aac4365721..a7f8830082 100644 --- a/website/views/pages/customers/dashboard.ejs +++ b/website/views/pages/customers/dashboard.ejs @@ -52,7 +52,7 @@

Your details

-
+
Organization:
@@ -85,19 +85,14 @@

Billing and payment

-
-
-
A credit card Icon
-
-

{{me.billingCardBrand}} ending in {{me.billingCardLast4}}A pencil icon indicating that this information can be editted

-
-
+
A calendar icon

{{thisSubscription.numberOfHosts}} devices @ ${{thisSubscription.subscriptionPrice / thisSubscription.numberOfHosts / 12}}.00/device/month

Billed annually at ${{thisSubscription.subscriptionPrice}}.00/yr

-

Next payment on

+

Next payment on

+

Your subscription will expire on

@@ -106,7 +101,7 @@ An icon indicating that this section has important information
-

Contact us to change your number of devices, or to cancel your subscription.

+

Click here to change your number of devices, or to cancel your subscription.

@@ -117,7 +112,7 @@ An icon indicating that this section has important information
-

Your subscription will expire on

+

Your subscription ended on

diff --git a/website/views/pages/customers/new-license.ejs b/website/views/pages/customers/new-license.ejs index 55ee148347..5d5dcb3cdb 100644 --- a/website/views/pages/customers/new-license.ejs +++ b/website/views/pages/customers/new-license.ejs @@ -43,35 +43,7 @@

Billing information

- -
- - -
-
-
- - -
Please enter the name of your organization.
-
-
-
-
- - -
Please enter your first name.
-
-
-
-
- - -
Please enter your last name.
-
-
-
-
+
@@ -84,12 +56,12 @@

The billing card provided could not be used without additional verification. Please use another card or contact support to complete your order.

- Get license key + Checkout

An error has occurred while processing your request.

We're sorry that this happened. A human has been informed of this error and is looking into it.

-

Feel free to reload the page and try again. A team member at Fleet will investigate and correct duplicate charges, if any occurred.

+

Feel free to reload the page and try again.

From da13fb15fa4517356ad060e55a6458b194ea8147 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 25 Nov 2024 14:20:19 -0600 Subject: [PATCH 09/92] =?UTF-8?q?Website:=20/endpoint-ops=20=C2=BB=20/obse?= =?UTF-8?q?rvability=20=20(#24111)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes: #24106 Changes: - Changed the /endpoint-ops page to be at /observability - Added a redirect for /ednpoint-ops that redirects users to the /observability page with original query parameters intact. --- .../{view-endpoint-ops.js => view-observability.js} | 6 +++--- .../{endpoint-ops.page.js => observability.page.js} | 2 +- website/assets/styles/importer.less | 2 +- .../pages/{endpoint-ops.less => observability.less} | 2 +- website/config/policies.js | 2 +- website/config/routes.js | 11 ++++++----- website/views/layouts/layout.ejs | 8 ++++---- website/views/pages/homepage.ejs | 10 +++++----- .../pages/{endpoint-ops.ejs => observability.ejs} | 2 +- 9 files changed, 23 insertions(+), 22 deletions(-) rename website/api/controllers/{view-endpoint-ops.js => view-observability.js} (96%) rename website/assets/js/pages/{endpoint-ops.page.js => observability.page.js} (95%) rename website/assets/styles/pages/{endpoint-ops.less => observability.less} (99%) rename website/views/pages/{endpoint-ops.ejs => observability.ejs} (99%) diff --git a/website/api/controllers/view-endpoint-ops.js b/website/api/controllers/view-observability.js similarity index 96% rename from website/api/controllers/view-endpoint-ops.js rename to website/api/controllers/view-observability.js index 7854159b65..6fa1a3381e 100644 --- a/website/api/controllers/view-endpoint-ops.js +++ b/website/api/controllers/view-observability.js @@ -1,16 +1,16 @@ module.exports = { - friendlyName: 'View endpoint ops', + friendlyName: 'View observability', - description: 'Display "Endpoint ops" page.', + description: 'Display "Observability" page.', exits: { success: { - viewTemplatePath: 'pages/endpoint-ops' + viewTemplatePath: 'pages/observability' }, badConfig: { responseType: 'badConfig' }, }, diff --git a/website/assets/js/pages/endpoint-ops.page.js b/website/assets/js/pages/observability.page.js similarity index 95% rename from website/assets/js/pages/endpoint-ops.page.js rename to website/assets/js/pages/observability.page.js index f79d51d830..0386d1f3f3 100644 --- a/website/assets/js/pages/endpoint-ops.page.js +++ b/website/assets/js/pages/observability.page.js @@ -1,4 +1,4 @@ -parasails.registerPage('endpoint-ops-page', { +parasails.registerPage('observability-page', { // ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗ // ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣ // ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝ diff --git a/website/assets/styles/importer.less b/website/assets/styles/importer.less index ef6d10bbcb..33420eb3ae 100644 --- a/website/assets/styles/importer.less +++ b/website/assets/styles/importer.less @@ -67,7 +67,7 @@ @import 'pages/vanta-authorization.less'; @import 'pages/admin/generate-license.less'; @import 'pages/device-management.less'; -@import 'pages/endpoint-ops.less'; +@import 'pages/observability.less'; @import 'pages/transparency.less'; @import 'pages/press-kit.less'; @import 'pages/software-management.less'; diff --git a/website/assets/styles/pages/endpoint-ops.less b/website/assets/styles/pages/observability.less similarity index 99% rename from website/assets/styles/pages/endpoint-ops.less rename to website/assets/styles/pages/observability.less index a2459aa9eb..b1a29e3dac 100644 --- a/website/assets/styles/pages/endpoint-ops.less +++ b/website/assets/styles/pages/observability.less @@ -1,4 +1,4 @@ -#endpoint-ops-page { +#observability-page { @heading-line-height: 120%; @text-line-height: 150%; diff --git a/website/config/policies.js b/website/config/policies.js index 1681d1d645..becc2c3931 100644 --- a/website/config/policies.js +++ b/website/config/policies.js @@ -45,7 +45,7 @@ module.exports.policies = { 'deliver-mdm-beta-signup': true, 'deliver-apple-csr': true, 'download-rss-feed': true, - 'view-endpoint-ops': true, + 'view-observability': true, 'view-software-management': true, 'deliver-mdm-demo-email': true, 'view-support': true, diff --git a/website/config/routes.js b/website/config/routes.js index 173a5673a6..f02a813ae5 100644 --- a/website/config/routes.js +++ b/website/config/routes.js @@ -228,10 +228,10 @@ module.exports.routes = { } }, - 'GET /endpoint-ops': { - action: 'view-endpoint-ops', + 'GET /observability': { + action: 'view-observability', locals: { - pageTitleForMeta: 'Endpoint ops', + pageTitleForMeta: 'Observability', pageDescriptionForMeta: 'Pulse check anything, build reports, and ship data to any platform with Fleet.', currentSection: 'platform', } @@ -540,11 +540,12 @@ module.exports.routes = { 'GET /endpoint-operations': '/endpoint-ops',// « just in case we type it the wrong way 'GET /example-dep-profile': 'https://github.com/fleetdm/fleet/blob/main/it-and-security/lib/automatic-enrollment.dep.json', 'GET /vulnerability-management': (req,res)=> { let originalQueryString = req.url.match(/\?(.+)$/) ? '?'+req.url.match(/\?(.+)$/)[1] : ''; return res.redirect(301, sails.config.custom.baseUrl+'/software-management'+originalQueryString);}, + 'GET /endpoint-ops': (req,res)=> { let originalQueryString = req.url.match(/\?(.+)$/) ? '?'+req.url.match(/\?(.+)$/)[1] : ''; return res.redirect(301, sails.config.custom.baseUrl+'/observability'+originalQueryString);}, // Shortlinks for texting friends, radio ads, etc 'GET /mdm': '/device-management?utm_content=mdm',// « alias for radio ad - 'GET /it': '/endpoint-ops?utm_content=eo-it', - 'GET /seceng': '/endpoint-ops?utm_content=eo-security', + 'GET /it': '/observability?utm_content=eo-it', + 'GET /seceng': '/observability?utm_content=eo-security', 'GET /vm': '/software-management?utm_content=vm', // Fleet UI diff --git a/website/views/layouts/layout.ejs b/website/views/layouts/layout.ejs index e33ef86293..3134e14af1 100644 --- a/website/views/layouts/layout.ejs +++ b/website/views/layouts/layout.ejs @@ -159,7 +159,7 @@ @@ -219,7 +219,7 @@ @@ -308,7 +308,7 @@
Multi platform Device management - Observability + Observability Software management Integrations Pricing @@ -477,7 +477,6 @@ - @@ -490,6 +489,7 @@ + diff --git a/website/views/pages/homepage.ejs b/website/views/pages/homepage.ejs index 6e89d9095d..9bc1868f26 100644 --- a/website/views/pages/homepage.ejs +++ b/website/views/pages/homepage.ejs @@ -9,7 +9,7 @@

<%- partial('../partials/primary-tagline.partial.ejs') %>

Replace the sprawl with <%= primaryBuyingSituation === 'vm'? 'secure, open-source reporting that works the way you want' : primaryBuyingSituation === 'eo-security'? 'open-source endpoint observability for every platform' : 'a modern device management platform that works the way you want' %>.

- Learn how + Learn how What people are saying
@@ -76,7 +76,7 @@ Osquery on easy mode

Use "read-only" mode or enable remote scripting to automate anything on every operating system, including Linux.

@@ -94,7 +94,7 @@ Ship data to any platform

Ship logs to any platform like Splunk, Snowflake, or any streaming infrastructure like AWS Kinesis and Apache Kafka.

@@ -122,7 +122,7 @@

<%= primaryBuyingSituation==='eo-security'? 'Instrument your endpoints' : 'Talk to your computers'%>

A <%= primaryBuyingSituation==='eo-security'? 'lightweight' : 'quick-fast' %> way to gather <%= primaryBuyingSituation==='vm'? 'patch level and custom reports across all your computing devices, even in OT and production environments' : primaryBuyingSituation==='eo-security'? 'deep context and custom telemetry across all your endpoints, even servers' : primaryBuyingSituation==='mdm'||primaryBuyingSituation==='eo-it'? 'compliance and inventory data across all your devices' : 'device data across all your computers' %>. Pulse check or automate anything on any platform.

- Start with <%= primaryBuyingSituation==='eo-security' ? 'security engineering' : 'IT engineering'%> + Start with <%= primaryBuyingSituation==='eo-security' ? 'security engineering' : 'IT engineering'%>
@@ -189,7 +189,7 @@

<%= primaryBuyingSituation==='vm'? 'Instrument your endpoints' : 'Talk to your computers'%>

A <%= primaryBuyingSituation==='vm'? 'lightweight' : 'quick-fast' %> way to gather <%= primaryBuyingSituation==='vm'? 'patch level and custom reports across all your computing devices, even in OT and production environments' : primaryBuyingSituation==='vm'? 'deep context and custom telemetry across all your endpoints, even servers' : primaryBuyingSituation==='mdm'||primaryBuyingSituation==='eo-it'? 'compliance and inventory data across all your devices' : 'device data across all your computers' %>. Pulse check or automate anything on any platform.

- Start with <%= primaryBuyingSituation==='mdm' ? 'IT engineering' : 'security engineering'%> + Start with <%= primaryBuyingSituation==='mdm' ? 'IT engineering' : 'security engineering'%>
diff --git a/website/views/pages/endpoint-ops.ejs b/website/views/pages/observability.ejs similarity index 99% rename from website/views/pages/endpoint-ops.ejs rename to website/views/pages/observability.ejs index fb80899a48..d3729f8566 100644 --- a/website/views/pages/endpoint-ops.ejs +++ b/website/views/pages/observability.ejs @@ -1,4 +1,4 @@ -
+
From 768d848946db6d5a6414434e1593d52e64f1f3e2 Mon Sep 17 00:00:00 2001 From: RachelElysia <71795832+RachelElysia@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:51:53 -0500 Subject: [PATCH 10/92] Fleet UI: 4.60 unreleased bug fix for scrollable content (#24139) --- frontend/components/Modal/_styles.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/Modal/_styles.scss b/frontend/components/Modal/_styles.scss index 433e2e886d..ef41106731 100644 --- a/frontend/components/Modal/_styles.scss +++ b/frontend/components/Modal/_styles.scss @@ -25,7 +25,7 @@ &__content-wrapper { margin-top: $pad-large; font-size: $x-small; - max-height: 800px; + // New pattern of max height modals pushed to 4.61 with PR #24019 overflow: visible; .input-field { From e8021462f5c0f80f97ff867e3709d555d5c5ee4e Mon Sep 17 00:00:00 2001 From: Luke Heath Date: Mon, 25 Nov 2024 14:52:33 -0600 Subject: [PATCH 11/92] Create mac-mozilla-firefox.yml (#24140) --- it-and-security/lib/software/mac-mozilla-firefox.yml | 2 ++ it-and-security/teams/workstations-canary.yml | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 it-and-security/lib/software/mac-mozilla-firefox.yml diff --git a/it-and-security/lib/software/mac-mozilla-firefox.yml b/it-and-security/lib/software/mac-mozilla-firefox.yml new file mode 100644 index 0000000000..9bd43612c0 --- /dev/null +++ b/it-and-security/lib/software/mac-mozilla-firefox.yml @@ -0,0 +1,2 @@ +url: https://download-installer.cdn.mozilla.net/pub/firefox/releases/132.0.2/mac/en-US/Firefox%20132.0.2.pkg +self_service: true diff --git a/it-and-security/teams/workstations-canary.yml b/it-and-security/teams/workstations-canary.yml index a8f4d7390a..0bcc07b20f 100644 --- a/it-and-security/teams/workstations-canary.yml +++ b/it-and-security/teams/workstations-canary.yml @@ -166,3 +166,5 @@ software: - app_store_id: '1333542190' # 1Password 7 Desktop - app_store_id: '1477376905' # GitHub - app_store_id: '1152747299' # Figma + packages: + - path: ../lib/software/mac-mozilla-firefox.yml # Mozilla Firefox for MacOS (universal) From 5f5b7bb273912e0abb7b0e935c5ae28e05c51bfe Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 25 Nov 2024 14:58:25 -0600 Subject: [PATCH 12/92] Website: Update CRM helper to handle contacts with no stage. (#24143) Closes: https://github.com/fleetdm/confidential/issues/8975 Changes: - Updated the update-or-create-contact-and-account helper to only check the current stage value of a contact record if the value is set. --- .../salesforce/update-or-create-contact-and-account.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/website/api/helpers/salesforce/update-or-create-contact-and-account.js b/website/api/helpers/salesforce/update-or-create-contact-and-account.js index 0f2c26c965..5d0b24dcd5 100644 --- a/website/api/helpers/salesforce/update-or-create-contact-and-account.js +++ b/website/api/helpers/salesforce/update-or-create-contact-and-account.js @@ -161,8 +161,9 @@ module.exports = { delete valuesToSet.Intent_signals__c; } } - // Check the existing contact record's psychologicalStage. - if(psychologicalStage) { + + // Check the existing contact record's psychologicalStage (If it is set). + if(psychologicalStage && existingContactRecord.Stage__c !== null) { let recordsCurrentPsyStage = existingContactRecord.Stage__c; // Because each psychological stage starts with a number, we'll get the first character in the record's current psychological stage and the new psychological stage to make comparison easier. let psyStageStageNumberToChangeTo = Number(psychologicalStage[0]); From 0fad882529b11e13fbc30a27ff4cf017e6d628cf Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:57:04 -0800 Subject: [PATCH 13/92] Fleet 4.59.0: fix typo (#24133) --- articles/fleet-4.59.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/articles/fleet-4.59.0.md b/articles/fleet-4.59.0.md index 749c981136..49549ed374 100644 --- a/articles/fleet-4.59.0.md +++ b/articles/fleet-4.59.0.md @@ -96,7 +96,7 @@ SET i.software_title_name = COALESCE(a.details->>"$.software_title", i.software_ ## Ready to upgrade? -Visit our [Upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs for instructions on updating to Fleet 4.58.0. +Visit our [Upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs for instructions on updating to Fleet 4.59.0. From a2b925c0f61f96af103c6642939395302e0c86bd Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:57:16 -0800 Subject: [PATCH 14/92] Email 2FA will be paid (#23986) Update the pricing page for this coming soon feature --- 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 06c85abd52..42b3e306f6 100644 --- a/handbook/company/pricing-features-table.yml +++ b/handbook/company/pricing-features-table.yml @@ -150,7 +150,7 @@ productCategories: [Endpoint operations,Device management,Vulnerability management] pricingTableCategories: [Configuration] usualDepartment: IT - tier: Free + tier: Premium jamfProHasFeature: yes jamfProtectHasFeature: yes waysToUse: From d4b0edf8c9e32f675da011c69203afdc572d95a4 Mon Sep 17 00:00:00 2001 From: Jahziel Villasana-Espinoza Date: Mon, 25 Nov 2024 18:00:07 -0500 Subject: [PATCH 15/92] fix: small typo (#24149) > No issue, just something I noticed # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files) for more information. - [x] Manual QA for all new/changed functionality --- changes/jve-fix-typo | 1 + .../AddFleetAppSoftwareModal/AddFleetAppSoftwareModal.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changes/jve-fix-typo diff --git a/changes/jve-fix-typo b/changes/jve-fix-typo new file mode 100644 index 0000000000..79379dadc5 --- /dev/null +++ b/changes/jve-fix-typo @@ -0,0 +1 @@ +- Fixes a typo in the loading modal when adding a Fleet-maintained app. \ No newline at end of file diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/AddFleetAppSoftwareModal/AddFleetAppSoftwareModal.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/AddFleetAppSoftwareModal/AddFleetAppSoftwareModal.tsx index 1c15a1b1b9..6608c05daa 100644 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/AddFleetAppSoftwareModal/AddFleetAppSoftwareModal.tsx +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/AddFleetAppSoftwareModal/AddFleetAppSoftwareModal.tsx @@ -18,7 +18,7 @@ const AddFleetAppSoftwareModal = () => {

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

From 9a10eb30daa5cc8895016030f8fc9392ec92e126 Mon Sep 17 00:00:00 2001 From: Rachael Shaw Date: Mon, 25 Nov 2024 17:47:42 -0600 Subject: [PATCH 16/92] Allow clicking through handbook back-to-top button container (#24154) This fix was already applied to the button on the docs pages, but not the handbook. --- website/assets/styles/pages/handbook/basic-handbook.less | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/assets/styles/pages/handbook/basic-handbook.less b/website/assets/styles/pages/handbook/basic-handbook.less index 431cb9ef60..ca7c09f970 100644 --- a/website/assets/styles/pages/handbook/basic-handbook.less +++ b/website/assets/styles/pages/handbook/basic-handbook.less @@ -580,6 +580,7 @@ position: sticky; bottom: 107px; overflow-x: hidden; + pointer-events: none; } [purpose='back-to-top-button'] { display: inline-block; @@ -593,6 +594,7 @@ cursor: pointer; border: 1px solid #E2E4EA; border-radius: 16px 0px 0px 16px; + pointer-events: auto; p { color: #515774; font-size: 11px; From 63d958d7623eaeeca85e8db4f33b5d29968d0beb Mon Sep 17 00:00:00 2001 From: Isabell Reedy <113355639+ireedy@users.noreply.github.com> Date: Mon, 25 Nov 2024 19:23:18 -0500 Subject: [PATCH 17/92] Removing finance KPIs we no longer use (#24147) --- handbook/finance/README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/handbook/finance/README.md b/handbook/finance/README.md index a92b7f8958..d7df002eb1 100644 --- a/handbook/finance/README.md +++ b/handbook/finance/README.md @@ -193,12 +193,6 @@ Use the following steps to update the [💸Finance department KPIs](https://docs **Non-personnel monthly burn**: - Copy the amount from the [numbers spreadsheet](https://docs.google.com/spreadsheets/d/1X-brkmUK7_Rgp7aq42drNcUg8ZipzEiS153uKZSabWc/edit#gid=1308221870&range=B3) and input in the cell for this week. -**SaaS metrics**: -- For "CAC", "CAC payback", "LTV" and "LTV:CAC" columns, drag the existing formula to this week's row. -> Note: the formula relies on inputs in other fields, so if those fields haven't received input yet, it will look odd. If formulas are still broken after inputs in the other columns are added, [create an issue on the Finance board](https://github.com/fleetdm/confidential/issues/new?assignees=&labels=%23g-finance&projects=&template=custom-request.md) noting which columns are affected. Once created, @ mention Finance Engineer in the issue to bring awareness. - -- For "Average customer age", pull the age in days from the [Salesforce "Account age" report](https://fleetdm.lightning.force.com/lightning/r/Report/00OUG0000012jwX2AQ/view), then convert to months by dividing the age in days by 30.417. Put the calculated number into the cell. This metric changes gradually up each week when no new deals close and no customer churns. Because we calculate the age of the customer based on the average lifetime deal length, expected behavior is that it will decrease when a new deal closes, but not drastically (as even a 1-year deal will still add 12 months to the average age). - ### Create an invoice From 2f0af27394a81e37890e650cbef4b4d26ec0fc07 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Mon, 25 Nov 2024 21:55:22 -0600 Subject: [PATCH 18/92] Website: Talk to an engineer (#24159) --- website/views/pages/articles/basic-article.ejs | 2 +- website/views/pages/contact.ejs | 2 +- website/views/pages/device-management.ejs | 4 ++-- website/views/pages/homepage.ejs | 2 +- website/views/pages/observability.ejs | 2 +- website/views/pages/pricing.ejs | 4 ++-- website/views/pages/software-management.ejs | 2 +- website/views/pages/testimonials.ejs | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/website/views/pages/articles/basic-article.ejs b/website/views/pages/articles/basic-article.ejs index 43af2b45c1..20048a7f4e 100644 --- a/website/views/pages/articles/basic-article.ejs +++ b/website/views/pages/articles/basic-article.ejs @@ -61,7 +61,7 @@ Docs Docs REST API REST API Guides Guides - Talk to us Talk to us + Talk to an engineer Talk to an engineer diff --git a/website/views/pages/contact.ejs b/website/views/pages/contact.ejs index 628bb4a354..8c4ac9994e 100644 --- a/website/views/pages/contact.ejs +++ b/website/views/pages/contact.ejs @@ -7,7 +7,7 @@

Schedule a personalized demo for your team and get support or training.

Schedule a personalized demo, or ask us anything. We’d love to chat.

-
Talk to us
+
Talk to an engineer
Send a message
diff --git a/website/views/pages/device-management.ejs b/website/views/pages/device-management.ejs index 62aa8ddc2a..06b03c32f3 100644 --- a/website/views/pages/device-management.ejs +++ b/website/views/pages/device-management.ejs @@ -29,7 +29,7 @@
- Talk to us + Talk to an engineer Try it yourself
@@ -524,7 +524,7 @@

Device management (MDM)

Your easiest MDM migration

- Talk to us + Talk to an engineer Try it yourself
diff --git a/website/views/pages/homepage.ejs b/website/views/pages/homepage.ejs index 9bc1868f26..c08c4df1da 100644 --- a/website/views/pages/homepage.ejs +++ b/website/views/pages/homepage.ejs @@ -391,7 +391,7 @@

<%- partial('../partials/primary-tagline.partial.ejs') %>

Try it yourself - Talk to us + Talk to an engineer
diff --git a/website/views/pages/observability.ejs b/website/views/pages/observability.ejs index d3729f8566..40bdc7bbd9 100644 --- a/website/views/pages/observability.ejs +++ b/website/views/pages/observability.ejs @@ -302,7 +302,7 @@

<%= pagePersonalization==='eo-security'? 'Instrument your endpoints' : 'Talk to your computers'%>

Start now - Talk to us + Talk to an engineer
diff --git a/website/views/pages/pricing.ejs b/website/views/pages/pricing.ejs index 34741268c7..3fc2be51ef 100644 --- a/website/views/pages/pricing.ejs +++ b/website/views/pages/pricing.ejs @@ -59,7 +59,7 @@ @@ -380,7 +380,7 @@
-

Couldn’t find an answer? Talk to us.

+

Couldn’t find an answer? Talk to an engineer.

diff --git a/website/views/pages/software-management.ejs b/website/views/pages/software-management.ejs index d87e69e670..8c8894cdd1 100644 --- a/website/views/pages/software-management.ejs +++ b/website/views/pages/software-management.ejs @@ -6,7 +6,7 @@

Manage software consistently

Pick from a curated app library or upload your own custom packages. Configure custom installation scripts if you need or let Fleet do it for you.

- Talk to us + Talk to an engineer Try it yourself
diff --git a/website/views/pages/testimonials.ejs b/website/views/pages/testimonials.ejs index 0a8a0b42e8..7cb0a2016e 100644 --- a/website/views/pages/testimonials.ejs +++ b/website/views/pages/testimonials.ejs @@ -145,7 +145,7 @@

Easily get security data

Try it yourself - Talk to us + Talk to an engineer
From 6fb9ddfdcd965959912d95cc25b78ed27095cb8a Mon Sep 17 00:00:00 2001 From: Ian Littman Date: Tue, 26 Nov 2024 09:08:10 -0600 Subject: [PATCH 19/92] Bump retries/timeouts for NVD pulls (#24160) For #24132 Drops retry wait a bit as there doesn't seem to be a point in waiting that long (I pulled feeds with a 10s retry interval in curl earlier). Total time to pull feeds and build the CPE DB was ~4 hours this evening (~6pm to ~10pm CST). # Checklist for submitter - [x] Manual QA for all new/changed functionality --- cmd/cpe/generate.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/cpe/generate.go b/cmd/cpe/generate.go index b2ed44ced7..5018f02003 100644 --- a/cmd/cpe/generate.go +++ b/cmd/cpe/generate.go @@ -22,10 +22,10 @@ import ( ) const ( - httpClientTimeout = 2 * time.Minute + httpClientTimeout = 3 * time.Minute waitTimeBetweenRequests = 6 * time.Second - waitTimeForRetry = 30 * time.Second - maxRetryAttempts = 10 + waitTimeForRetry = 10 * time.Second + maxRetryAttempts = 20 apiKeyEnvVar = "NVD_API_KEY" //nolint:gosec ) From c0c247476349b927904aad5a6caf992e504ca670 Mon Sep 17 00:00:00 2001 From: Allen Houchins <32207388+allenhouchins@users.noreply.github.com> Date: Tue, 26 Nov 2024 10:35:38 -0600 Subject: [PATCH 20/92] Updated Zoom download URL (#24151) Updated Zoom download URL to address this issue: fleetdm/confidential#8977 More info here: https://fleetdm.slack.com/archives/C019WG4GH0A/p1732570447472539 --- it-and-security/lib/software/mac-zoom-arm.yml | 4 ---- it-and-security/lib/software/mac-zoom.yml | 2 ++ it-and-security/teams/workstations.yml | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) delete mode 100644 it-and-security/lib/software/mac-zoom-arm.yml create mode 100644 it-and-security/lib/software/mac-zoom.yml diff --git a/it-and-security/lib/software/mac-zoom-arm.yml b/it-and-security/lib/software/mac-zoom-arm.yml deleted file mode 100644 index 38503c7aaf..0000000000 --- a/it-and-security/lib/software/mac-zoom-arm.yml +++ /dev/null @@ -1,4 +0,0 @@ -url: https://zoom.us/client/latest/Zoom.pkg?archType=arm64 -pre_install_query: - path: ../lib/macos-check-if-apple-silicon.queries.yml -self_service: true \ No newline at end of file diff --git a/it-and-security/lib/software/mac-zoom.yml b/it-and-security/lib/software/mac-zoom.yml new file mode 100644 index 0000000000..16f557ca45 --- /dev/null +++ b/it-and-security/lib/software/mac-zoom.yml @@ -0,0 +1,2 @@ +url: https://zoom.us/client/6.2.10.43047/ZoomInstallerIT.pkg +self_service: true \ No newline at end of file diff --git a/it-and-security/teams/workstations.yml b/it-and-security/teams/workstations.yml index 3aca539842..9d92c61051 100644 --- a/it-and-security/teams/workstations.yml +++ b/it-and-security/teams/workstations.yml @@ -62,7 +62,7 @@ controls: macos_setup_assistant: ../lib/automatic-enrollment.dep.json software: - package_path: ../lib/software/mac-google-chrome.yml # Google Chrome for macOS - - package_path: ../lib/software/mac-zoom-arm.yml # Zoom for macOS + - package_path: ../lib/software/mac-zoom.yml # Zoom for macOS - app_store_id: '803453959' # Slack Desktop - app_store_id: '1333542190' # 1Password 7 Desktop macos_updates: @@ -106,7 +106,7 @@ queries: observer_can_run: true software: packages: - - path: ../lib/software/mac-zoom-arm.yml # Zoom for macOS + - path: ../lib/software/mac-zoom.yml # Zoom for macOS - path: ../lib/software/mac-google-chrome.yml # Google Chrome for macOS app_store_apps: - app_store_id: '803453959' # Slack Desktop From c4404d9d68c192b2f0bc7c4bc0521c4fd5286d2d Mon Sep 17 00:00:00 2001 From: Martin Angers Date: Tue, 26 Nov 2024 11:52:56 -0500 Subject: [PATCH 21/92] Windows MDM Migration: API, CLI and activities (#24141) --- ...2897-add-windows-migration-enabled-setting | 1 + cmd/fleetctl/gitops_test.go | 25 ++++++ .../expectedGetConfigAppConfigJson.json | 1 + .../expectedGetConfigAppConfigYaml.yml | 1 + ...ectedGetConfigIncludeServerConfigJson.json | 1 + ...pectedGetConfigIncludeServerConfigYaml.yml | 1 + ...l_config_windows_migration_false_false.yml | 75 +++++++++++++++++ ...al_config_windows_migration_false_true.yml | 75 +++++++++++++++++ ...al_config_windows_migration_true_false.yml | 75 +++++++++++++++++ ...bal_config_windows_migration_true_true.yml | 75 +++++++++++++++++ .../macosSetupExpectedAppConfigEmpty.yml | 1 + .../macosSetupExpectedAppConfigSet.yml | 1 + docs/Contributing/Audit-logs.md | 12 +++ pkg/spec/gitops.go | 3 +- pkg/spec/gitops_test.go | 2 + pkg/spec/testdata/controls.yml | 1 + pkg/spec/testdata/global_config_no_paths.yml | 1 + pkg/spec/testdata/team_config_no_paths.yml | 1 + ...ddAppConfigWindowsMigrationEnabledField.go | 54 ++++++++++++ ...ConfigWindowsMigrationEnabledField_test.go | 33 ++++++++ server/datastore/mysql/schema.sql | 6 +- server/fleet/activities.go | 24 ++++++ server/fleet/app.go | 9 +- server/service/appconfig.go | 27 ++++++ server/service/client.go | 5 ++ server/service/integration_core_test.go | 6 ++ server/service/integration_enterprise_test.go | 10 +++ server/service/integration_mdm_test.go | 82 +++++++++++++++++++ .../generated_files/appconfig.txt | 1 + 29 files changed, 601 insertions(+), 8 deletions(-) create mode 100644 changes/22897-add-windows-migration-enabled-setting create mode 100644 cmd/fleetctl/testdata/gitops/global_config_windows_migration_false_false.yml create mode 100644 cmd/fleetctl/testdata/gitops/global_config_windows_migration_false_true.yml create mode 100644 cmd/fleetctl/testdata/gitops/global_config_windows_migration_true_false.yml create mode 100644 cmd/fleetctl/testdata/gitops/global_config_windows_migration_true_true.yml create mode 100644 server/datastore/mysql/migrations/tables/20241125150614_AddAppConfigWindowsMigrationEnabledField.go create mode 100644 server/datastore/mysql/migrations/tables/20241125150614_AddAppConfigWindowsMigrationEnabledField_test.go diff --git a/changes/22897-add-windows-migration-enabled-setting b/changes/22897-add-windows-migration-enabled-setting new file mode 100644 index 0000000000..15866a98c7 --- /dev/null +++ b/changes/22897-add-windows-migration-enabled-setting @@ -0,0 +1 @@ +* Added support for the new `windows_migration_enabled` setting (can be set via `fleetctl`, the `PATCH /api/latest/fleet/config` API endpoint and the UI). Requires a premium license. diff --git a/cmd/fleetctl/gitops_test.go b/cmd/fleetctl/gitops_test.go index 407351c154..7abc95dc63 100644 --- a/cmd/fleetctl/gitops_test.go +++ b/cmd/fleetctl/gitops_test.go @@ -3219,6 +3219,31 @@ software: } } +func TestGitOpsWindowsMigration(t *testing.T) { + cases := []struct { + file string + wantErr string + }{ + // booleans are Windows MDM enabled and Windows migration enabled + {"testdata/gitops/global_config_windows_migration_true_true.yml", ""}, + {"testdata/gitops/global_config_windows_migration_false_true.yml", "Windows MDM is not enabled"}, + {"testdata/gitops/global_config_windows_migration_true_false.yml", ""}, + {"testdata/gitops/global_config_windows_migration_false_false.yml", ""}, + } + for _, c := range cases { + t.Run(filepath.Base(c.file), func(t *testing.T) { + setupFullGitOpsPremiumServer(t) + + _, err := runAppNoChecks([]string{"gitops", "-f", c.file}) + if c.wantErr == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, c.wantErr) + } + }) + } +} + type memKeyValueStore struct { m sync.Map } diff --git a/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json b/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json index c377964177..1cbede5abf 100644 --- a/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json +++ b/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json @@ -118,6 +118,7 @@ "deadline_days": 7, "grace_period_days": 3 }, + "windows_migration_enabled": false, "macos_migration": { "enable": false, "mode": "", diff --git a/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml b/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml index ff6fbaa22e..042be51191 100644 --- a/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml +++ b/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml @@ -27,6 +27,7 @@ spec: volume_purchasing_program: null windows_enabled_and_configured: false enable_disk_encryption: false + windows_migration_enabled: false macos_migration: enable: false mode: "" diff --git a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json index dea76a995b..f8c421065b 100644 --- a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json +++ b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json @@ -70,6 +70,7 @@ "deadline_days": 7, "grace_period_days": 3 }, + "windows_migration_enabled": false, "macos_migration": { "enable": false, "mode": "", diff --git a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml index f6b8136407..5f6e877f16 100644 --- a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml +++ b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml @@ -27,6 +27,7 @@ spec: enabled_and_configured: false windows_enabled_and_configured: false enable_disk_encryption: false + windows_migration_enabled: false macos_migration: enable: false mode: "" diff --git a/cmd/fleetctl/testdata/gitops/global_config_windows_migration_false_false.yml b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_false_false.yml new file mode 100644 index 0000000000..645be877d3 --- /dev/null +++ b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_false_false.yml @@ -0,0 +1,75 @@ +controls: + macos_settings: + windows_settings: + scripts: + enable_disk_encryption: false + macos_migration: + enable: false + mode: "" + webhook_url: "" + macos_setup: + bootstrap_package: null + enable_end_user_authentication: false + macos_setup_assistant: null + macos_updates: + deadline: null + minimum_version: null + windows_enabled_and_configured: false + windows_migration_enabled: false + windows_updates: + deadline_days: null + grace_period_days: null +queries: +policies: +agent_options: + command_line_flags: + distributed_denylist_duration: 0 + config: + decorators: + load: + - SELECT uuid AS host_uuid FROM system_info; + - SELECT hostname AS hostname FROM system_info; + options: + disable_distributed: false + distributed_interval: 10 + distributed_plugin: tls + distributed_tls_max_attempts: 3 + logger_tls_endpoint: /api/v1/osquery/log + pack_delimiter: / +org_settings: + server_settings: + deferred_save_host: false + enable_analytics: true + live_query_disabled: false + query_report_cap: 2000 + query_reports_disabled: false + scripts_disabled: false + server_url: $FLEET_SERVER_URL + ai_features_disabled: true + org_info: + contact_url: https://fleetdm.com/company/contact + org_logo_url: "" + org_logo_url_light_background: "" + org_name: $ORG_NAME + smtp_settings: + sso_settings: + integrations: + mdm: + end_user_authentication: + webhook_settings: + fleet_desktop: # Applies to Fleet Premium only + transparency_url: https://fleetdm.com/transparency + host_expiry_settings: # Applies to all teams + host_expiry_enabled: false + activity_expiry_settings: + activity_expiry_enabled: true + activity_expiry_window: 60 + features: # Features added to all teams + enable_host_users: true + enable_software_inventory: true + vulnerability_settings: + databases_path: "" + secrets: # These secrets are used to enroll hosts to the "All teams" team + - secret: SampleSecret123 + - secret: ABC +software: diff --git a/cmd/fleetctl/testdata/gitops/global_config_windows_migration_false_true.yml b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_false_true.yml new file mode 100644 index 0000000000..99cdf07c39 --- /dev/null +++ b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_false_true.yml @@ -0,0 +1,75 @@ +controls: + macos_settings: + windows_settings: + scripts: + enable_disk_encryption: false + macos_migration: + enable: false + mode: "" + webhook_url: "" + macos_setup: + bootstrap_package: null + enable_end_user_authentication: false + macos_setup_assistant: null + macos_updates: + deadline: null + minimum_version: null + windows_enabled_and_configured: false + windows_migration_enabled: true + windows_updates: + deadline_days: null + grace_period_days: null +queries: +policies: +agent_options: + command_line_flags: + distributed_denylist_duration: 0 + config: + decorators: + load: + - SELECT uuid AS host_uuid FROM system_info; + - SELECT hostname AS hostname FROM system_info; + options: + disable_distributed: false + distributed_interval: 10 + distributed_plugin: tls + distributed_tls_max_attempts: 3 + logger_tls_endpoint: /api/v1/osquery/log + pack_delimiter: / +org_settings: + server_settings: + deferred_save_host: false + enable_analytics: true + live_query_disabled: false + query_report_cap: 2000 + query_reports_disabled: false + scripts_disabled: false + server_url: $FLEET_SERVER_URL + ai_features_disabled: true + org_info: + contact_url: https://fleetdm.com/company/contact + org_logo_url: "" + org_logo_url_light_background: "" + org_name: $ORG_NAME + smtp_settings: + sso_settings: + integrations: + mdm: + end_user_authentication: + webhook_settings: + fleet_desktop: # Applies to Fleet Premium only + transparency_url: https://fleetdm.com/transparency + host_expiry_settings: # Applies to all teams + host_expiry_enabled: false + activity_expiry_settings: + activity_expiry_enabled: true + activity_expiry_window: 60 + features: # Features added to all teams + enable_host_users: true + enable_software_inventory: true + vulnerability_settings: + databases_path: "" + secrets: # These secrets are used to enroll hosts to the "All teams" team + - secret: SampleSecret123 + - secret: ABC +software: diff --git a/cmd/fleetctl/testdata/gitops/global_config_windows_migration_true_false.yml b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_true_false.yml new file mode 100644 index 0000000000..c934e124a2 --- /dev/null +++ b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_true_false.yml @@ -0,0 +1,75 @@ +controls: + macos_settings: + windows_settings: + scripts: + enable_disk_encryption: false + macos_migration: + enable: false + mode: "" + webhook_url: "" + macos_setup: + bootstrap_package: null + enable_end_user_authentication: false + macos_setup_assistant: null + macos_updates: + deadline: null + minimum_version: null + windows_enabled_and_configured: true + windows_migration_enabled: false + windows_updates: + deadline_days: null + grace_period_days: null +queries: +policies: +agent_options: + command_line_flags: + distributed_denylist_duration: 0 + config: + decorators: + load: + - SELECT uuid AS host_uuid FROM system_info; + - SELECT hostname AS hostname FROM system_info; + options: + disable_distributed: false + distributed_interval: 10 + distributed_plugin: tls + distributed_tls_max_attempts: 3 + logger_tls_endpoint: /api/v1/osquery/log + pack_delimiter: / +org_settings: + server_settings: + deferred_save_host: false + enable_analytics: true + live_query_disabled: false + query_report_cap: 2000 + query_reports_disabled: false + scripts_disabled: false + server_url: $FLEET_SERVER_URL + ai_features_disabled: true + org_info: + contact_url: https://fleetdm.com/company/contact + org_logo_url: "" + org_logo_url_light_background: "" + org_name: $ORG_NAME + smtp_settings: + sso_settings: + integrations: + mdm: + end_user_authentication: + webhook_settings: + fleet_desktop: # Applies to Fleet Premium only + transparency_url: https://fleetdm.com/transparency + host_expiry_settings: # Applies to all teams + host_expiry_enabled: false + activity_expiry_settings: + activity_expiry_enabled: true + activity_expiry_window: 60 + features: # Features added to all teams + enable_host_users: true + enable_software_inventory: true + vulnerability_settings: + databases_path: "" + secrets: # These secrets are used to enroll hosts to the "All teams" team + - secret: SampleSecret123 + - secret: ABC +software: diff --git a/cmd/fleetctl/testdata/gitops/global_config_windows_migration_true_true.yml b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_true_true.yml new file mode 100644 index 0000000000..f6ab917ecf --- /dev/null +++ b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_true_true.yml @@ -0,0 +1,75 @@ +controls: + macos_settings: + windows_settings: + scripts: + enable_disk_encryption: false + macos_migration: + enable: false + mode: "" + webhook_url: "" + macos_setup: + bootstrap_package: null + enable_end_user_authentication: false + macos_setup_assistant: null + macos_updates: + deadline: null + minimum_version: null + windows_enabled_and_configured: true + windows_migration_enabled: true + windows_updates: + deadline_days: null + grace_period_days: null +queries: +policies: +agent_options: + command_line_flags: + distributed_denylist_duration: 0 + config: + decorators: + load: + - SELECT uuid AS host_uuid FROM system_info; + - SELECT hostname AS hostname FROM system_info; + options: + disable_distributed: false + distributed_interval: 10 + distributed_plugin: tls + distributed_tls_max_attempts: 3 + logger_tls_endpoint: /api/v1/osquery/log + pack_delimiter: / +org_settings: + server_settings: + deferred_save_host: false + enable_analytics: true + live_query_disabled: false + query_report_cap: 2000 + query_reports_disabled: false + scripts_disabled: false + server_url: $FLEET_SERVER_URL + ai_features_disabled: true + org_info: + contact_url: https://fleetdm.com/company/contact + org_logo_url: "" + org_logo_url_light_background: "" + org_name: $ORG_NAME + smtp_settings: + sso_settings: + integrations: + mdm: + end_user_authentication: + webhook_settings: + fleet_desktop: # Applies to Fleet Premium only + transparency_url: https://fleetdm.com/transparency + host_expiry_settings: # Applies to all teams + host_expiry_enabled: false + activity_expiry_settings: + activity_expiry_enabled: true + activity_expiry_window: 60 + features: # Features added to all teams + enable_host_users: true + enable_software_inventory: true + vulnerability_settings: + databases_path: "" + secrets: # These secrets are used to enroll hosts to the "All teams" team + - secret: SampleSecret123 + - secret: ABC +software: diff --git a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml index 49c129df69..50250bc34e 100644 --- a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml +++ b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml @@ -27,6 +27,7 @@ spec: enabled_and_configured: true windows_enabled_and_configured: false enable_disk_encryption: false + windows_migration_enabled: false macos_migration: enable: false mode: "" diff --git a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml index 27e6a2a545..b74e3c2d8f 100644 --- a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml +++ b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml @@ -27,6 +27,7 @@ spec: enabled_and_configured: true windows_enabled_and_configured: false enable_disk_encryption: false + windows_migration_enabled: false macos_migration: enable: false mode: "" diff --git a/docs/Contributing/Audit-logs.md b/docs/Contributing/Audit-logs.md index 26c8a02f3d..0650b61798 100644 --- a/docs/Contributing/Audit-logs.md +++ b/docs/Contributing/Audit-logs.md @@ -894,6 +894,18 @@ Generated when a user turns off MDM features for all Windows hosts. This activity does not contain any detail fields. +## enabled_windows_mdm_migration + +Generated when a user enables automatic MDM migration for Windows hosts, if Windows MDM is turned on. + +This activity does not contain any detail fields. + +## disabled_windows_mdm_migration + +Generated when a user disables automatic MDM migration for Windows hosts, if Windows MDM is turned on. + +This activity does not contain any detail fields. + ## ran_script Generated when a script is sent to be run for a host. diff --git a/pkg/spec/gitops.go b/pkg/spec/gitops.go index c26c9b500f..bdfdf1ceec 100644 --- a/pkg/spec/gitops.go +++ b/pkg/spec/gitops.go @@ -33,6 +33,7 @@ type Controls struct { WindowsUpdates interface{} `json:"windows_updates"` WindowsSettings interface{} `json:"windows_settings"` WindowsEnabledAndConfigured interface{} `json:"windows_enabled_and_configured"` + WindowsMigrationEnabled interface{} `json:"windows_migration_enabled"` EnableDiskEncryption interface{} `json:"enable_disk_encryption"` @@ -46,7 +47,7 @@ func (c Controls) Set() bool { c.IPadOSUpdates != nil || c.MacOSSettings != nil || c.MacOSSetup != nil || c.MacOSMigration != nil || c.WindowsUpdates != nil || c.WindowsSettings != nil || c.WindowsEnabledAndConfigured != nil || - c.EnableDiskEncryption != nil || len(c.Scripts) > 0 + c.WindowsMigrationEnabled != nil || c.EnableDiskEncryption != nil || len(c.Scripts) > 0 } type Policy struct { diff --git a/pkg/spec/gitops_test.go b/pkg/spec/gitops_test.go index 8ca334ce36..bdc5423f0a 100644 --- a/pkg/spec/gitops_test.go +++ b/pkg/spec/gitops_test.go @@ -216,6 +216,8 @@ func TestValidGitOpsYaml(t *testing.T) { assert.True(t, ok, "ipados_updates not found") _, ok = gitops.Controls.WindowsEnabledAndConfigured.(bool) assert.True(t, ok, "windows_enabled_and_configured not found") + _, ok = gitops.Controls.WindowsMigrationEnabled.(bool) + assert.True(t, ok, "windows_migration_enabled not found") _, ok = gitops.Controls.WindowsUpdates.(map[string]interface{}) assert.True(t, ok, "windows_updates not found") diff --git a/pkg/spec/testdata/controls.yml b/pkg/spec/testdata/controls.yml index 5da3567921..27fe44dc03 100644 --- a/pkg/spec/testdata/controls.yml +++ b/pkg/spec/testdata/controls.yml @@ -25,6 +25,7 @@ ipados_updates: deadline: null minimum_version: null windows_enabled_and_configured: true +windows_migration_enabled: false windows_updates: deadline_days: null grace_period_days: null diff --git a/pkg/spec/testdata/global_config_no_paths.yml b/pkg/spec/testdata/global_config_no_paths.yml index c8d68f9462..36ce19ca3e 100644 --- a/pkg/spec/testdata/global_config_no_paths.yml +++ b/pkg/spec/testdata/global_config_no_paths.yml @@ -27,6 +27,7 @@ controls: # Controls added to "No team" deadline: null minimum_version: null windows_enabled_and_configured: true + windows_migration_enabled: false windows_updates: deadline_days: null grace_period_days: null diff --git a/pkg/spec/testdata/team_config_no_paths.yml b/pkg/spec/testdata/team_config_no_paths.yml index e660d5eccc..2ff83e4730 100644 --- a/pkg/spec/testdata/team_config_no_paths.yml +++ b/pkg/spec/testdata/team_config_no_paths.yml @@ -60,6 +60,7 @@ controls: mode: "" webhook_url: "" windows_enabled_and_configured: true + windows_migration_enabled: false queries: - name: Scheduled query stats description: Collect osquery performance stats directly from osquery diff --git a/server/datastore/mysql/migrations/tables/20241125150614_AddAppConfigWindowsMigrationEnabledField.go b/server/datastore/mysql/migrations/tables/20241125150614_AddAppConfigWindowsMigrationEnabledField.go new file mode 100644 index 0000000000..1765d00c62 --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20241125150614_AddAppConfigWindowsMigrationEnabledField.go @@ -0,0 +1,54 @@ +package tables + +import ( + "database/sql" + "encoding/json" + "fmt" + + "github.com/pkg/errors" +) + +func init() { + MigrationClient.AddMigration(Up_20241125150614, Down_20241125150614) +} + +func Up_20241125150614(tx *sql.Tx) error { + var raw json.RawMessage + var id uint + row := tx.QueryRow(`SELECT id, json_value FROM app_config_json LIMIT 1;`) + if err := row.Scan(&id, &raw); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil + } + return fmt.Errorf("select app_config_json: %w", err) + } + + var config map[string]interface{} + if err := json.Unmarshal(raw, &config); err != nil { + return fmt.Errorf("unmarshal appconfig: %w", err) + } + + mdm, ok := config["mdm"] + if !ok { + return errors.New("missing mdm section") + } + mdmMap, ok := mdm.(map[string]interface{}) + if !ok { + return fmt.Errorf("invalid type for mdm: %T", mdm) + } + mdmMap["windows_migration_enabled"] = false + + b, err := json.Marshal(config) + if err != nil { + return fmt.Errorf("marshal updated appconfig: %w", err) + } + if _, err := tx.Exec(`UPDATE app_config_json SET json_value = ? WHERE id = ?;`, b, id); err != nil { + return fmt.Errorf("update app_config_json: %w", err) + } + + return nil +} + +func Down_20241125150614(tx *sql.Tx) error { + return nil +} diff --git a/server/datastore/mysql/migrations/tables/20241125150614_AddAppConfigWindowsMigrationEnabledField_test.go b/server/datastore/mysql/migrations/tables/20241125150614_AddAppConfigWindowsMigrationEnabledField_test.go new file mode 100644 index 0000000000..743f82de1d --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20241125150614_AddAppConfigWindowsMigrationEnabledField_test.go @@ -0,0 +1,33 @@ +package tables + +import ( + "encoding/json" + "testing" + + "github.com/jmoiron/sqlx" + "github.com/stretchr/testify/require" +) + +func TestUp_20241125150614(t *testing.T) { + db := applyUpToPrev(t) + + // Apply current migration. + applyNext(t, db) + + var appCfg json.RawMessage + err := sqlx.Get(db, &appCfg, `SELECT json_value FROM app_config_json LIMIT 1;`) + require.NoError(t, err) + + var config map[string]interface{} + err = json.Unmarshal(appCfg, &config) + require.NoError(t, err) + + mdm, ok := config["mdm"] + require.True(t, ok) + mdmMap, ok := mdm.(map[string]interface{}) + require.True(t, ok) + + _, ok = mdmMap["windows_enabled_and_configured"].(bool) + require.True(t, ok) + require.False(t, mdmMap["windows_migration_enabled"].(bool)) +} diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql index 038a0de892..450848024d 100644 --- a/server/datastore/mysql/schema.sql +++ b/server/datastore/mysql/schema.sql @@ -64,7 +64,7 @@ CREATE TABLE `app_config_json` ( PRIMARY KEY (`id`) ) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; -INSERT INTO `app_config_json` VALUES (1,'{\"mdm\": {\"ios_updates\": {\"deadline\": null, \"minimum_version\": null}, \"macos_setup\": {\"script\": null, \"software\": null, \"bootstrap_package\": null, \"macos_setup_assistant\": null, \"enable_end_user_authentication\": false, \"enable_release_device_manually\": false}, \"macos_updates\": {\"deadline\": null, \"minimum_version\": null}, \"ipados_updates\": {\"deadline\": null, \"minimum_version\": null}, \"macos_settings\": {\"custom_settings\": null}, \"macos_migration\": {\"mode\": \"\", \"enable\": false, \"webhook_url\": \"\"}, \"windows_updates\": {\"deadline_days\": null, \"grace_period_days\": null}, \"apple_server_url\": \"\", \"windows_settings\": {\"custom_settings\": null}, \"apple_bm_terms_expired\": false, \"apple_business_manager\": null, \"enable_disk_encryption\": false, \"enabled_and_configured\": false, \"end_user_authentication\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"issuer_uri\": \"\", \"metadata_url\": \"\"}, \"volume_purchasing_program\": null, \"windows_enabled_and_configured\": false, \"apple_bm_enabled_and_configured\": false}, \"scripts\": null, \"features\": {\"enable_host_users\": true, \"enable_software_inventory\": false}, \"org_info\": {\"org_name\": \"\", \"contact_url\": \"\", \"org_logo_url\": \"\", \"org_logo_url_light_background\": \"\"}, \"integrations\": {\"jira\": null, \"zendesk\": null, \"google_calendar\": null, \"ndes_scep_proxy\": null}, \"sso_settings\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"enable_sso\": false, \"issuer_uri\": \"\", \"metadata_url\": \"\", \"idp_image_url\": \"\", \"enable_jit_role_sync\": false, \"enable_sso_idp_login\": false, \"enable_jit_provisioning\": false}, \"agent_options\": {\"config\": {\"options\": {\"logger_plugin\": \"tls\", \"pack_delimiter\": \"/\", \"logger_tls_period\": 10, \"distributed_plugin\": \"tls\", \"disable_distributed\": false, \"logger_tls_endpoint\": \"/api/osquery/log\", \"distributed_interval\": 10, \"distributed_tls_max_attempts\": 3}, \"decorators\": {\"load\": [\"SELECT uuid AS host_uuid FROM system_info;\", \"SELECT hostname AS hostname FROM system_info;\"]}}, \"overrides\": {}}, \"fleet_desktop\": {\"transparency_url\": \"\"}, \"smtp_settings\": {\"port\": 587, \"domain\": \"\", \"server\": \"\", \"password\": \"\", \"user_name\": \"\", \"configured\": false, \"enable_smtp\": false, \"enable_ssl_tls\": true, \"sender_address\": \"\", \"enable_start_tls\": true, \"verify_ssl_certs\": true, \"authentication_type\": \"0\", \"authentication_method\": \"0\"}, \"server_settings\": {\"server_url\": \"\", \"enable_analytics\": false, \"query_report_cap\": 0, \"scripts_disabled\": false, \"deferred_save_host\": false, \"live_query_disabled\": false, \"ai_features_disabled\": false, \"query_reports_disabled\": false}, \"webhook_settings\": {\"interval\": \"0s\", \"activities_webhook\": {\"destination_url\": \"\", \"enable_activities_webhook\": false}, \"host_status_webhook\": {\"days_count\": 0, \"destination_url\": \"\", \"host_percentage\": 0, \"enable_host_status_webhook\": false}, \"vulnerabilities_webhook\": {\"destination_url\": \"\", \"host_batch_size\": 0, \"enable_vulnerabilities_webhook\": false}, \"failing_policies_webhook\": {\"policy_ids\": null, \"destination_url\": \"\", \"host_batch_size\": 0, \"enable_failing_policies_webhook\": false}}, \"host_expiry_settings\": {\"host_expiry_window\": 0, \"host_expiry_enabled\": false}, \"vulnerability_settings\": {\"databases_path\": \"\"}, \"activity_expiry_settings\": {\"activity_expiry_window\": 0, \"activity_expiry_enabled\": false}}','2020-01-01 01:01:01','2020-01-01 01:01:01'); +INSERT INTO `app_config_json` VALUES (1,'{\"mdm\": {\"ios_updates\": {\"deadline\": null, \"minimum_version\": null}, \"macos_setup\": {\"script\": null, \"software\": null, \"bootstrap_package\": null, \"macos_setup_assistant\": null, \"enable_end_user_authentication\": false, \"enable_release_device_manually\": false}, \"macos_updates\": {\"deadline\": null, \"minimum_version\": null}, \"ipados_updates\": {\"deadline\": null, \"minimum_version\": null}, \"macos_settings\": {\"custom_settings\": null}, \"macos_migration\": {\"mode\": \"\", \"enable\": false, \"webhook_url\": \"\"}, \"windows_updates\": {\"deadline_days\": null, \"grace_period_days\": null}, \"apple_server_url\": \"\", \"windows_settings\": {\"custom_settings\": null}, \"apple_bm_terms_expired\": false, \"apple_business_manager\": null, \"enable_disk_encryption\": false, \"enabled_and_configured\": false, \"end_user_authentication\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"issuer_uri\": \"\", \"metadata_url\": \"\"}, \"volume_purchasing_program\": null, \"windows_migration_enabled\": false, \"windows_enabled_and_configured\": false, \"apple_bm_enabled_and_configured\": false}, \"scripts\": null, \"features\": {\"enable_host_users\": true, \"enable_software_inventory\": false}, \"org_info\": {\"org_name\": \"\", \"contact_url\": \"\", \"org_logo_url\": \"\", \"org_logo_url_light_background\": \"\"}, \"integrations\": {\"jira\": null, \"zendesk\": null, \"google_calendar\": null, \"ndes_scep_proxy\": null}, \"sso_settings\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"enable_sso\": false, \"issuer_uri\": \"\", \"metadata_url\": \"\", \"idp_image_url\": \"\", \"enable_jit_role_sync\": false, \"enable_sso_idp_login\": false, \"enable_jit_provisioning\": false}, \"agent_options\": {\"config\": {\"options\": {\"logger_plugin\": \"tls\", \"pack_delimiter\": \"/\", \"logger_tls_period\": 10, \"distributed_plugin\": \"tls\", \"disable_distributed\": false, \"logger_tls_endpoint\": \"/api/osquery/log\", \"distributed_interval\": 10, \"distributed_tls_max_attempts\": 3}, \"decorators\": {\"load\": [\"SELECT uuid AS host_uuid FROM system_info;\", \"SELECT hostname AS hostname FROM system_info;\"]}}, \"overrides\": {}}, \"fleet_desktop\": {\"transparency_url\": \"\"}, \"smtp_settings\": {\"port\": 587, \"domain\": \"\", \"server\": \"\", \"password\": \"\", \"user_name\": \"\", \"configured\": false, \"enable_smtp\": false, \"enable_ssl_tls\": true, \"sender_address\": \"\", \"enable_start_tls\": true, \"verify_ssl_certs\": true, \"authentication_type\": \"0\", \"authentication_method\": \"0\"}, \"server_settings\": {\"server_url\": \"\", \"enable_analytics\": false, \"query_report_cap\": 0, \"scripts_disabled\": false, \"deferred_save_host\": false, \"live_query_disabled\": false, \"ai_features_disabled\": false, \"query_reports_disabled\": false}, \"webhook_settings\": {\"interval\": \"0s\", \"activities_webhook\": {\"destination_url\": \"\", \"enable_activities_webhook\": false}, \"host_status_webhook\": {\"days_count\": 0, \"destination_url\": \"\", \"host_percentage\": 0, \"enable_host_status_webhook\": false}, \"vulnerabilities_webhook\": {\"destination_url\": \"\", \"host_batch_size\": 0, \"enable_vulnerabilities_webhook\": false}, \"failing_policies_webhook\": {\"policy_ids\": null, \"destination_url\": \"\", \"host_batch_size\": 0, \"enable_failing_policies_webhook\": false}}, \"host_expiry_settings\": {\"host_expiry_window\": 0, \"host_expiry_enabled\": false}, \"vulnerability_settings\": {\"databases_path\": \"\"}, \"activity_expiry_settings\": {\"activity_expiry_window\": 0, \"activity_expiry_enabled\": false}}','2020-01-01 01:01:01','2020-01-01 01:01:01'); /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `calendar_events` ( @@ -1101,9 +1101,9 @@ CREATE TABLE `migration_status_tables` ( `is_applied` tinyint(1) NOT NULL, `tstamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) -) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=332 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=333 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; -INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20241002104104,1,'2020-01-01 01:01:01'),(314,20241002104105,1,'2020-01-01 01:01:01'),(315,20241002104106,1,'2020-01-01 01:01:01'),(316,20241002210000,1,'2020-01-01 01:01:01'),(317,20241003145349,1,'2020-01-01 01:01:01'),(318,20241004005000,1,'2020-01-01 01:01:01'),(319,20241008083925,1,'2020-01-01 01:01:01'),(320,20241009090010,1,'2020-01-01 01:01:01'),(321,20241017163402,1,'2020-01-01 01:01:01'),(322,20241021224359,1,'2020-01-01 01:01:01'),(323,20241022140321,1,'2020-01-01 01:01:01'),(324,20241025111236,1,'2020-01-01 01:01:01'),(325,20241025112748,1,'2020-01-01 01:01:01'),(326,20241025141855,1,'2020-01-01 01:01:01'),(327,20241110152839,1,'2020-01-01 01:01:01'),(328,20241110152840,1,'2020-01-01 01:01:01'),(329,20241110152841,1,'2020-01-01 01:01:01'),(330,20241116233322,1,'2020-01-01 01:01:01'),(331,20241122171434,1,'2020-01-01 01:01:01'); +INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20241002104104,1,'2020-01-01 01:01:01'),(314,20241002104105,1,'2020-01-01 01:01:01'),(315,20241002104106,1,'2020-01-01 01:01:01'),(316,20241002210000,1,'2020-01-01 01:01:01'),(317,20241003145349,1,'2020-01-01 01:01:01'),(318,20241004005000,1,'2020-01-01 01:01:01'),(319,20241008083925,1,'2020-01-01 01:01:01'),(320,20241009090010,1,'2020-01-01 01:01:01'),(321,20241017163402,1,'2020-01-01 01:01:01'),(322,20241021224359,1,'2020-01-01 01:01:01'),(323,20241022140321,1,'2020-01-01 01:01:01'),(324,20241025111236,1,'2020-01-01 01:01:01'),(325,20241025112748,1,'2020-01-01 01:01:01'),(326,20241025141855,1,'2020-01-01 01:01:01'),(327,20241110152839,1,'2020-01-01 01:01:01'),(328,20241110152840,1,'2020-01-01 01:01:01'),(329,20241110152841,1,'2020-01-01 01:01:01'),(330,20241116233322,1,'2020-01-01 01:01:01'),(331,20241122171434,1,'2020-01-01 01:01:01'),(332,20241125150614,1,'2020-01-01 01:01:01'); /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `mobile_device_management_solutions` ( diff --git a/server/fleet/activities.go b/server/fleet/activities.go index 751218ac6e..1298476dde 100644 --- a/server/fleet/activities.go +++ b/server/fleet/activities.go @@ -79,6 +79,8 @@ var ActivityDetailsList = []ActivityDetails{ ActivityTypeEnabledWindowsMDM{}, ActivityTypeDisabledWindowsMDM{}, + ActivityTypeEnabledWindowsMDMMigration{}, + ActivityTypeDisabledWindowsMDMMigration{}, ActivityTypeRanScript{}, ActivityTypeAddedScript{}, @@ -1236,6 +1238,28 @@ func (a ActivityTypeDisabledWindowsMDM) Documentation() (activity, details, deta `This activity does not contain any detail fields.`, `` } +type ActivityTypeEnabledWindowsMDMMigration struct{} + +func (a ActivityTypeEnabledWindowsMDMMigration) ActivityName() string { + return "enabled_windows_mdm_migration" +} + +func (a ActivityTypeEnabledWindowsMDMMigration) Documentation() (activity, details, detailsExample string) { + return `Generated when a user enables automatic MDM migration for Windows hosts, if Windows MDM is turned on.`, + `This activity does not contain any detail fields.`, `` +} + +type ActivityTypeDisabledWindowsMDMMigration struct{} + +func (a ActivityTypeDisabledWindowsMDMMigration) ActivityName() string { + return "disabled_windows_mdm_migration" +} + +func (a ActivityTypeDisabledWindowsMDMMigration) Documentation() (activity, details, detailsExample string) { + return `Generated when a user disables automatic MDM migration for Windows hosts, if Windows MDM is turned on.`, + `This activity does not contain any detail fields.`, `` +} + type ActivityTypeRanScript struct { HostID uint `json:"host_id"` HostDisplayName string `json:"host_display_name"` diff --git a/server/fleet/app.go b/server/fleet/app.go index 5622c438a9..662095832e 100644 --- a/server/fleet/app.go +++ b/server/fleet/app.go @@ -189,10 +189,11 @@ type MDM struct { // WindowsUpdates defines the OS update settings for Windows devices. WindowsUpdates WindowsUpdates `json:"windows_updates"` - MacOSSettings MacOSSettings `json:"macos_settings"` - MacOSSetup MacOSSetup `json:"macos_setup"` - MacOSMigration MacOSMigration `json:"macos_migration"` - EndUserAuthentication MDMEndUserAuthentication `json:"end_user_authentication"` + MacOSSettings MacOSSettings `json:"macos_settings"` + MacOSSetup MacOSSetup `json:"macos_setup"` + MacOSMigration MacOSMigration `json:"macos_migration"` + WindowsMigrationEnabled bool `json:"windows_migration_enabled"` + EndUserAuthentication MDMEndUserAuthentication `json:"end_user_authentication"` // WindowsEnabledAndConfigured indicates if Fleet MDM is enabled for Windows. // There is no other configuration required for Windows other than enabling diff --git a/server/service/appconfig.go b/server/service/appconfig.go index 030c2c6502..e0ee0317e6 100644 --- a/server/service/appconfig.go +++ b/server/service/appconfig.go @@ -337,6 +337,15 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle return nil, ctxerr.Wrap(ctx, err) } + // if turning off Windows MDM and Windows Migration is not explicitly set to + // on in the same update, set it to off (otherwise, if it is explicitly set + // to true, return an error that it can't be done when MDM is off, this is + // addressed in validateMDM). + if oldAppConfig.MDM.WindowsEnabledAndConfigured != appConfig.MDM.WindowsEnabledAndConfigured && + !appConfig.MDM.WindowsEnabledAndConfigured && !newAppConfig.MDM.WindowsMigrationEnabled { + appConfig.MDM.WindowsMigrationEnabled = false + } + type ndesStatusType string const ( ndesStatusAdded ndesStatusType = "added" @@ -869,6 +878,18 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle } } + if appConfig.MDM.WindowsEnabledAndConfigured && oldAppConfig.MDM.WindowsMigrationEnabled != appConfig.MDM.WindowsMigrationEnabled { + var act fleet.ActivityDetails + if appConfig.MDM.WindowsMigrationEnabled { + act = fleet.ActivityTypeEnabledWindowsMDMMigration{} + } else { + act = fleet.ActivityTypeDisabledWindowsMDMMigration{} + } + if err := svc.NewActivity(ctx, authz.UserFromContext(ctx), act); err != nil { + return nil, ctxerr.Wrapf(ctx, err, "create activity %s", act.ActivityName()) + } + } + return obfuscatedAppConfig, nil } @@ -958,6 +979,9 @@ func (svc *Service) validateMDM( if mdm.MacOSSetup.EnableEndUserAuthentication && oldMdm.MacOSSetup.EnableEndUserAuthentication != mdm.MacOSSetup.EnableEndUserAuthentication && !license.IsPremium() { invalid.Append("macos_setup.enable_end_user_authentication", ErrMissingLicense.Error()) } + if mdm.WindowsMigrationEnabled && !license.IsPremium() { + invalid.Append("windows_migration_enabled", ErrMissingLicense.Error()) + } // we want to use `oldMdm` here as this boolean is set by the fleet // server at startup and can't be modified by the user @@ -1133,6 +1157,9 @@ func (svc *Service) validateMDM( return nil } } + if !mdm.WindowsEnabledAndConfigured && mdm.WindowsMigrationEnabled { + invalid.Append("mdm.windows_migration_enabled", "Couldn't enable Windows MDM migration, Windows MDM is not enabled.") + } return nil } diff --git a/server/service/client.go b/server/service/client.go index 2a3b1a6c5d..935ca07434 100644 --- a/server/service/client.go +++ b/server/service/client.go @@ -1512,6 +1512,11 @@ func (c *Client) DoGitOps( } else { mdmAppConfig["windows_enabled_and_configured"] = false } + // Put in default values for windows_migration_enabled + mdmAppConfig["windows_migration_enabled"] = config.Controls.WindowsMigrationEnabled + if config.Controls.WindowsMigrationEnabled == nil { + mdmAppConfig["windows_migration_enabled"] = false + } if windowsEnabledAndConfiguredAssumption, ok := mdmAppConfig["windows_enabled_and_configured"].(bool); ok { teamAssumptions = &fleet.TeamSpecsDryRunAssumptions{ WindowsEnabledAndConfigured: optjson.SetBool(windowsEnabledAndConfiguredAssumption), diff --git a/server/service/integration_core_test.go b/server/service/integration_core_test.go index 0dfab1f02f..73171dfb3c 100644 --- a/server/service/integration_core_test.go +++ b/server/service/integration_core_test.go @@ -6314,6 +6314,12 @@ func (s *integrationTestSuite) TestPremiumEndpointsWithoutLicense() { }`), http.StatusUnprocessableEntity) errMsg = extractServerErrorText(res.Body) require.Contains(t, errMsg, "missing or invalid license") + + res = s.Do("PATCH", "/api/v1/fleet/config", json.RawMessage(`{ + "mdm": { "windows_migration_enabled": true } + }`), http.StatusUnprocessableEntity) + errMsg = extractServerErrorText(res.Body) + require.Contains(t, errMsg, "missing or invalid license") } func (s *integrationTestSuite) TestScriptsEndpointsWithoutLicense() { diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go index 93bac52410..dda4f93bf8 100644 --- a/server/service/integration_enterprise_test.go +++ b/server/service/integration_enterprise_test.go @@ -15353,3 +15353,13 @@ func (s *integrationEnterpriseTestSuite) TestMaintainedApps() { require.NoError(t, err) require.Equal(t, req.PostInstallScript, string(postinstall)) } + +func (s *integrationEnterpriseTestSuite) TestWindowsMigrateMDMNotEnabled() { + t := s.T() + + res := s.Do("PATCH", "/api/v1/fleet/config", json.RawMessage(`{ + "mdm": { "windows_migration_enabled": true } + }`), http.StatusUnprocessableEntity) + errMsg := extractServerErrorText(res.Body) + require.Contains(t, errMsg, "Windows MDM is not enabled") +} diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go index d8d2f6390d..14b26700d0 100644 --- a/server/service/integration_mdm_test.go +++ b/server/service/integration_mdm_test.go @@ -11896,6 +11896,88 @@ func (s *integrationMDMTestSuite) TestSetupExperience() { require.True(t, awaitingConfig) } +func (s *integrationMDMTestSuite) TestWindowsMigrationEnabled() { + t := s.T() + + var acResp appConfigResponse + s.DoJSON("GET", "/api/latest/fleet/config", nil, http.StatusOK, &acResp) + require.True(t, acResp.MDM.WindowsEnabledAndConfigured) + require.False(t, acResp.MDM.WindowsMigrationEnabled) + + s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{ + "mdm": { + "windows_migration_enabled": true + } + }`), http.StatusOK, &acResp) + require.True(t, acResp.MDM.WindowsEnabledAndConfigured) + require.True(t, acResp.MDM.WindowsMigrationEnabled) + s.lastActivityMatches(fleet.ActivityTypeEnabledWindowsMDMMigration{}.ActivityName(), "", 0) + + s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{ + "mdm": { + "windows_migration_enabled": false + } + }`), http.StatusOK, &acResp) + require.True(t, acResp.MDM.WindowsEnabledAndConfigured) + require.False(t, acResp.MDM.WindowsMigrationEnabled) + s.lastActivityMatches(fleet.ActivityTypeDisabledWindowsMDMMigration{}.ActivityName(), "", 0) + + // set migrations back to true to see if they turn false when turning MDM off + s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{ + "mdm": { + "windows_migration_enabled": true + } + }`), http.StatusOK, &acResp) + require.True(t, acResp.MDM.WindowsEnabledAndConfigured) + require.True(t, acResp.MDM.WindowsMigrationEnabled) + lastEnabledID := s.lastActivityMatches(fleet.ActivityTypeEnabledWindowsMDMMigration{}.ActivityName(), "", 0) + + // not providing any mdm update should leave the current values + s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{ + "mdm": {} + }`), http.StatusOK, &acResp) + require.True(t, acResp.MDM.WindowsEnabledAndConfigured) + require.True(t, acResp.MDM.WindowsMigrationEnabled) + // no new activity was created + s.lastActivityOfTypeMatches(fleet.ActivityTypeEnabledWindowsMDMMigration{}.ActivityName(), "", lastEnabledID) + + // set to true again does not generate a new activity, was already true + s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{ + "mdm": { + "windows_migration_enabled": true + } + }`), http.StatusOK, &acResp) + require.True(t, acResp.MDM.WindowsEnabledAndConfigured) + require.True(t, acResp.MDM.WindowsMigrationEnabled) + s.lastActivityOfTypeMatches(fleet.ActivityTypeEnabledWindowsMDMMigration{}.ActivityName(), "", lastEnabledID) + + res := s.Do("PATCH", "/api/latest/fleet/config", json.RawMessage(`{ + "mdm": { + "windows_enabled_and_configured": false, + "windows_migration_enabled": true + } + }`), http.StatusUnprocessableEntity) + errMsg := extractServerErrorText(res.Body) + require.Contains(t, errMsg, "Windows MDM is not enabled") + + // turn off Windows MDM and try to enable migrations in a distinct call + s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{ + "mdm": { + "windows_enabled_and_configured": false + } + }`), http.StatusOK, &acResp) + require.False(t, acResp.MDM.WindowsEnabledAndConfigured) + require.False(t, acResp.MDM.WindowsMigrationEnabled) + + res = s.Do("PATCH", "/api/latest/fleet/config", json.RawMessage(`{ + "mdm": { + "windows_migration_enabled": true + } + }`), http.StatusUnprocessableEntity) + errMsg = extractServerErrorText(res.Body) + require.Contains(t, errMsg, "Windows MDM is not enabled") +} + func (s *integrationMDMTestSuite) TestHostsCantTurnMDMOff() { t := s.T() iOSHost, _ := s.createAppleMobileHostThenEnrollMDM("ios") diff --git a/tools/cloner-check/generated_files/appconfig.txt b/tools/cloner-check/generated_files/appconfig.txt index 2454027fa9..6c48aadd23 100644 --- a/tools/cloner-check/generated_files/appconfig.txt +++ b/tools/cloner-check/generated_files/appconfig.txt @@ -158,6 +158,7 @@ github.com/fleetdm/fleet/v4/server/fleet/MDM MacOSMigration fleet.MacOSMigration github.com/fleetdm/fleet/v4/server/fleet/MacOSMigration Enable bool github.com/fleetdm/fleet/v4/server/fleet/MacOSMigration Mode fleet.MacOSMigrationMode string github.com/fleetdm/fleet/v4/server/fleet/MacOSMigration WebhookURL string +github.com/fleetdm/fleet/v4/server/fleet/MDM WindowsMigrationEnabled bool github.com/fleetdm/fleet/v4/server/fleet/MDM EndUserAuthentication fleet.MDMEndUserAuthentication github.com/fleetdm/fleet/v4/server/fleet/MDMEndUserAuthentication SSOProviderSettings fleet.SSOProviderSettings github.com/fleetdm/fleet/v4/server/fleet/MDM WindowsEnabledAndConfigured bool From ec7bb8be09e2283b4686a742b04486f07883ddc7 Mon Sep 17 00:00:00 2001 From: jacobshandling <61553566+jacobshandling@users.noreply.github.com> Date: Tue, 26 Nov 2024 10:09:57 -0800 Subject: [PATCH 22/92] UI - Fix DUP banners for Fedora disk encryption (#24153) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Addresses unreleased bug where banners were not functioning for Fedora host Screenshot 2024-11-25 at 4 39 05 PM - [x] Manual QA for all new/changed functionality Co-authored-by: Jacob Shandling --- .../pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx | 1 + .../components/DeviceUserBanners/DeviceUserBanners.tsx | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx b/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx index 868383d237..f2f44a48a8 100644 --- a/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx +++ b/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx @@ -371,6 +371,7 @@ const DeviceUserPage = ({
Date: Tue, 26 Nov 2024 12:13:42 -0600 Subject: [PATCH 23/92] Update fleet-4.57.0.md (#24064) --- articles/fleet-4.57.0.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/articles/fleet-4.57.0.md b/articles/fleet-4.57.0.md index 4c9f959f9a..8fa3366ae7 100644 --- a/articles/fleet-4.57.0.md +++ b/articles/fleet-4.57.0.md @@ -1,6 +1,8 @@ # Fleet 4.57.0 | Software improvements, policy automation, GitLab support. -![Fleet 4.57.0](../website/assets/images/articles/fleet-4.57.0-1600x900@2x.png) +
+ +
Fleet 4.57.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.57.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. From 0d8808a6f9c94e7ee61dbba83f0b742c89daab96 Mon Sep 17 00:00:00 2001 From: Onasis Munro Date: Tue, 26 Nov 2024 13:09:26 -0600 Subject: [PATCH 24/92] Update fleet-4.58.0.md (#24023) --- articles/fleet-4.58.0.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/articles/fleet-4.58.0.md b/articles/fleet-4.58.0.md index cfd2b7f9f6..05efdcc691 100644 --- a/articles/fleet-4.58.0.md +++ b/articles/fleet-4.58.0.md @@ -1,6 +1,8 @@ # Fleet 4.58.0 | Run script on policy failure, Fleet-maintained apps, Sequoia firewall status. -![Fleet 4.58.0](../website/assets/images/articles/fleet-4.58.0-1600x900@2x.png) +
+ +
Fleet 4.58.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.58.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. From 42a0c3aea167c02e48168030d9a84c671a19c7ef Mon Sep 17 00:00:00 2001 From: Onasis Munro Date: Tue, 26 Nov 2024 13:09:43 -0600 Subject: [PATCH 25/92] Update fleet-4.56.0.md (#24054) --- articles/fleet-4.56.0.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/articles/fleet-4.56.0.md b/articles/fleet-4.56.0.md index 14089ad9d6..152347f621 100644 --- a/articles/fleet-4.56.0.md +++ b/articles/fleet-4.56.0.md @@ -1,6 +1,8 @@ # Fleet 4.56.0 | Enhanced MDM migration, Exact CVE Search, and Self-Service VPP Apps. -![Fleet 4.56.0](../website/assets/images/articles/fleet-4.56.0-1600x900@2x.png) +
+ +
Fleet 4.56.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.56.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. From fa4af44d72c59c7b966473b029778d790a497ff0 Mon Sep 17 00:00:00 2001 From: Onasis Munro Date: Tue, 26 Nov 2024 13:09:55 -0600 Subject: [PATCH 26/92] Update fleet-4.54.0.md (#24056) --- articles/fleet-4.54.0.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/articles/fleet-4.54.0.md b/articles/fleet-4.54.0.md index 798090428f..4f2f2b003a 100644 --- a/articles/fleet-4.54.0.md +++ b/articles/fleet-4.54.0.md @@ -1,6 +1,8 @@ # Fleet 4.54.0 | Target hosts via label exclusion, arm64 support, script execution time. -![Fleet 4.54.0](../website/assets/images/articles/fleet-4.54.0-1600x900@2x.png) +
+ +
Fleet 4.54.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.54.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. From ed526be3bbc77412ef3584297651791bfa8f27a4 Mon Sep 17 00:00:00 2001 From: Onasis Munro Date: Tue, 26 Nov 2024 13:11:38 -0600 Subject: [PATCH 27/92] Update fleet-4.55.0.md (#24055) --- articles/fleet-4.55.0.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/articles/fleet-4.55.0.md b/articles/fleet-4.55.0.md index 309e0e70df..8f76ef5772 100644 --- a/articles/fleet-4.55.0.md +++ b/articles/fleet-4.55.0.md @@ -1,6 +1,8 @@ # Fleet 4.55.0 | MySQL 8, arm64 support, FileVault improvements, VPP support. -![Fleet 4.55.0](../website/assets/images/articles/fleet-4.55.0-1600x900@2x.png) +
+ +
Fleet 4.55.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.55.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. From 9d2481ef8c855c80043ff3b5120d570a68ca3f8c Mon Sep 17 00:00:00 2001 From: Onasis Munro Date: Tue, 26 Nov 2024 13:11:51 -0600 Subject: [PATCH 28/92] Update fleet-4.53.0.md (#24057) --- articles/fleet-4.53.0.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/articles/fleet-4.53.0.md b/articles/fleet-4.53.0.md index 3a238a69aa..88c2cf6158 100644 --- a/articles/fleet-4.53.0.md +++ b/articles/fleet-4.53.0.md @@ -1,6 +1,8 @@ # Fleet 4.53.0 | Better vuln matching, multi-issue hosts, & `fleetd` logs as tables. -![Fleet 4.53.0](../website/assets/images/articles/fleet-4.53.0-1600x900@2x.png) +
+ +
Fleet 4.53.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.53.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. From 9cfb1f7cde62971bf6e3ce416d612f00515d91a3 Mon Sep 17 00:00:00 2001 From: Onasis Munro Date: Tue, 26 Nov 2024 13:12:06 -0600 Subject: [PATCH 29/92] Update fleet-4.51.0.md (#24058) --- articles/fleet-4.51.0.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/articles/fleet-4.51.0.md b/articles/fleet-4.51.0.md index 7f43b9f66b..4d7e046ae4 100644 --- a/articles/fleet-4.51.0.md +++ b/articles/fleet-4.51.0.md @@ -1,6 +1,8 @@ # Fleet 4.51.0 | Global activity webhook, macOS TCC table, and software self-service. -![Fleet 4.51.0](../website/assets/images/articles/fleet-4.51.0-1600x900@2x.png) +
+ +
Fleet 4.51.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.51.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. From 754ef794497d59fe045873b3522cb2ee6e8644cd Mon Sep 17 00:00:00 2001 From: Onasis Munro Date: Tue, 26 Nov 2024 13:12:15 -0600 Subject: [PATCH 30/92] Update fleet-4.50.0.md (#24059) --- articles/fleet-4.50.0.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/articles/fleet-4.50.0.md b/articles/fleet-4.50.0.md index 661b08cc38..f331194749 100644 --- a/articles/fleet-4.50.0.md +++ b/articles/fleet-4.50.0.md @@ -1,6 +1,8 @@ # Fleet 4.50.0 | Security agent deployment, AI descriptions, and Mac Admins SOFA support. -![Fleet 4.50.0](../website/assets/images/articles/fleet-4.50.0-1600x900@2x.png) +
+ +
Fleet 4.50.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.50.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. From 7f73a1cbab57f2dd3c08e448f3d105937628a6ec Mon Sep 17 00:00:00 2001 From: Onasis Munro Date: Tue, 26 Nov 2024 13:12:26 -0600 Subject: [PATCH 31/92] Update fleet-4.49.0.md (#24060) --- articles/fleet-4.49.0.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/articles/fleet-4.49.0.md b/articles/fleet-4.49.0.md index bda46c13e2..3694b32f6d 100644 --- a/articles/fleet-4.49.0.md +++ b/articles/fleet-4.49.0.md @@ -1,6 +1,8 @@ # Fleet 4.49.0 | VulnCheck's NVD++, device health API, `fleetd` data parsing. -![Fleet 4.49.0](../website/assets/images/articles/fleet-4.49.0-1600x900@2x.png) +
+ +
Fleet 4.49.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.49.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. From 5680444595082eff470136c790b85c74e7ebf1a6 Mon Sep 17 00:00:00 2001 From: Onasis Munro Date: Tue, 26 Nov 2024 13:12:36 -0600 Subject: [PATCH 32/92] Update fleet-4.48.0.md (#24061) --- articles/fleet-4.48.0.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/articles/fleet-4.48.0.md b/articles/fleet-4.48.0.md index 4bfdcb5527..16867fb73f 100644 --- a/articles/fleet-4.48.0.md +++ b/articles/fleet-4.48.0.md @@ -1,6 +1,8 @@ # Fleet 4.48.0 | IdP local account creation, VS Code extensions. -![Fleet 4.48.0](../website/assets/images/articles/fleet-4.48.0-1600x900@2x.png) +
+ +
Fleet 4.48.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.48.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. From 8f1c9e32432ea4862a3860a9c351e3f01528bf0f Mon Sep 17 00:00:00 2001 From: Onasis Munro Date: Tue, 26 Nov 2024 13:12:46 -0600 Subject: [PATCH 33/92] Update fleet-4.47.0.md (#24062) --- articles/fleet-4.47.0.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/articles/fleet-4.47.0.md b/articles/fleet-4.47.0.md index 01a2fb3e62..69f42984e3 100644 --- a/articles/fleet-4.47.0.md +++ b/articles/fleet-4.47.0.md @@ -1,6 +1,8 @@ # Fleet 4.47.0 | Cross-platform remote wipe, vulnerabilities page, and scripting improvements. -![Fleet 4.47.0](../website/assets/images/articles/fleet-4.47.0-1600x900@2x.png) +
+ +
Fleet 4.47.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.47.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. From 3d50adbad53e31186cb08c30b26999501d6baced Mon Sep 17 00:00:00 2001 From: Onasis Munro Date: Tue, 26 Nov 2024 13:12:59 -0600 Subject: [PATCH 34/92] Update fleet-4.40.0.md (#24063) --- articles/fleet-4.40.0.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/articles/fleet-4.40.0.md b/articles/fleet-4.40.0.md index a532533165..6200a56635 100644 --- a/articles/fleet-4.40.0.md +++ b/articles/fleet-4.40.0.md @@ -1,6 +1,8 @@ # Fleet 4.40.0 | More Data, Rapid Security Response, CIS Benchmark updates. -![Fleet 4.40.0](../website/assets/images/articles/fleet-4.40.0-1600x900@2x.png) +
+ +
Fleet 4.40.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.40.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. From ea9b5ba776c97d21eeb285d44620b7c0005cd1a8 Mon Sep 17 00:00:00 2001 From: jacobshandling <61553566+jacobshandling@users.noreply.github.com> Date: Tue, 26 Nov 2024 11:29:50 -0800 Subject: [PATCH 35/92] =?UTF-8?q?UI=20=E2=80=93=2011/26=20Disk=20encryptio?= =?UTF-8?q?n=20spec=20updates=20(#24175)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## #24173 Screenshot 2024-11-26 at 11 08 25 AM Screenshot 2024-11-26 at 10 45 11 AM - [x] Manual QA for all new/changed functionality --------- Co-authored-by: Jacob Shandling --- .../cards/DiskEncryption/DiskEncryption.tsx | 23 +++++++++- .../HostDetailsBanners/HostDetailsBanners.tsx | 42 ++++++++++++++++--- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/DiskEncryption/DiskEncryption.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/DiskEncryption/DiskEncryption.tsx index 16e773809d..fd4fe03310 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/cards/DiskEncryption/DiskEncryption.tsx +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/DiskEncryption/DiskEncryption.tsx @@ -124,7 +124,24 @@ const DiskEncryption = ({ setIsLoadingTeam(false); } - const getTipContent = (platform: "windows" | "macOS") => { + const getTipContent = (platform: "windows" | "macOS" | "linux") => { + if (platform === "linux") { + return ( + <> + For Ubuntu and Fedora Linux. +
+ Currently, full disk encryption must be turned on{" "} + + during OS +
+ setup +
+ . If disk encryption is off, the end user must re-install +
+ their operating system. + + ); + } const [AppleOrWindows, DEMethod] = platform === "windows" ? ["Windows", "BitLocker"] @@ -149,7 +166,9 @@ const DiskEncryption = ({ Windows - , Ubuntu Linux, and Fedora Linux hosts. + , and{" "} + Linux{" "} + hosts. ); diff --git a/frontend/pages/hosts/details/HostDetailsPage/components/HostDetailsBanners/HostDetailsBanners.tsx b/frontend/pages/hosts/details/HostDetailsPage/components/HostDetailsBanners/HostDetailsBanners.tsx index 188282bab1..0e4eb8b851 100644 --- a/frontend/pages/hosts/details/HostDetailsPage/components/HostDetailsBanners/HostDetailsBanners.tsx +++ b/frontend/pages/hosts/details/HostDetailsPage/components/HostDetailsBanners/HostDetailsBanners.tsx @@ -1,15 +1,21 @@ import React, { useContext } from "react"; import { AppContext } from "context/app"; -import { DiskEncryptionStatus, MdmEnrollmentStatus } from "interfaces/mdm"; import { hasLicenseExpired } from "utilities/helpers"; -import InfoBanner from "components/InfoBanner"; + +import { DiskEncryptionStatus, MdmEnrollmentStatus } from "interfaces/mdm"; import { IOSSettings } from "interfaces/host"; import { HostPlatform, + isDiskEncryptionSupportedLinuxPlatform, platformSupportsDiskEncryption, } from "interfaces/platform"; +import InfoBanner from "components/InfoBanner"; +import CustomLink from "components/CustomLink"; +import { LEARN_MORE_ABOUT_BASE_LINK } from "utilities/constants"; +import { isDiskEncryptionProfile } from "pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingStatusCell/helpers"; + const baseClass = "host-details-banners"; export interface IHostBannersBaseProps { @@ -110,9 +116,35 @@ const HostDetailsBanners = ({ platformSupportsDiskEncryption(hostPlatform, hostOsVersion) && diskEncryptionOSSetting?.status ) { - // host either not in compliance with setting, or is but Fleet doesn't yet have a disk - // encryption key escrowed for the host (possible for Linux hosts) - if (!diskIsEncrypted || !diskEncryptionKeyAvailable) { + if ( + !diskIsEncrypted && + isDiskEncryptionSupportedLinuxPlatform(hostPlatform, hostOsVersion ?? "") + ) { + // linux host not in compliance with setting + return ( +
+ + } + > + Disk encryption: Disk encryption is off. Currently, to turn on{" "} + full disk encryption, the end user has to re-install their + operating system. + +
+ ); + } + if (!diskEncryptionKeyAvailable) { + // disk is encrypted, but Fleet doesn't yet have a disk + // encryption key escrowed (possible for Linux hosts) return (
From 5524daea2cd5acc72cb366e878b513c2d473c46f Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 26 Nov 2024 14:03:34 -0600 Subject: [PATCH 36/92] Website: update /software-management page (#24176) Closes: https://github.com/fleetdm/confidential/issues/8982 Changes: - Added an IT quote to the /sfotware-management page - Updated the CTAs on the /software-management page --- .../styles/pages/software-management.less | 68 ++++++++++++------- website/views/pages/software-management.ejs | 16 ++++- 2 files changed, 56 insertions(+), 28 deletions(-) diff --git a/website/assets/styles/pages/software-management.less b/website/assets/styles/pages/software-management.less index 32b96544be..c8ae0e0f3f 100644 --- a/website/assets/styles/pages/software-management.less +++ b/website/assets/styles/pages/software-management.less @@ -57,6 +57,7 @@ padding-bottom: 32px; } [purpose='button-row'] { + flex-direction: row; [purpose='contact-button'] { display: flex; height: 36px; @@ -218,35 +219,43 @@ padding-bottom: 64px; padding-top: 32px; text-align: center; - [purpose='testimonial-image'] { - height: 48px; - margin-bottom: 5px; - } - [purpose='testimonial-text'] { + } + [purpose='it-testimonial'] { + max-width: 524px; + margin-left: auto; + margin-right: auto; + padding-bottom: 32px; + padding-top: 32px; + text-align: center; + } + [purpose='testimonial-image'] { + height: 48px; + margin-bottom: 5px; + } + [purpose='testimonial-text'] { + color: @core-fleet-black-75; + text-align: center; + font-size: 18px; + font-style: italic; + font-weight: 400; + line-height: @text-lineheight; + margin-bottom: 24px; + } + [purpose='testimonial-attribution'] { + [purpose='name'] { color: @core-fleet-black-75; text-align: center; - font-size: 18px; - font-style: italic; + font-size: 12px; + font-weight: 700; + line-height: @text-lineheight; /* 150% */ + margin-bottom: 0px; + } + [purpose='job-title'] { + color: @core-fleet-black-75; + margin-bottom: 0px; + font-size: 12px; font-weight: 400; line-height: @text-lineheight; - margin-bottom: 24px; - } - [purpose='testimonial-attribution'] { - [purpose='name'] { - color: @core-fleet-black-75; - text-align: center; - font-size: 12px; - font-weight: 700; - line-height: @text-lineheight; /* 150% */ - margin-bottom: 0px; - } - [purpose='job-title'] { - color: @core-fleet-black-75; - margin-bottom: 0px; - font-size: 12px; - font-weight: 400; - line-height: @text-lineheight; - } } } @@ -471,6 +480,15 @@ padding-bottom: 32px; padding-top: 0px; } + [purpose='button-row'] { + flex-direction: column; + align-items: center; + [purpose='contact-button'] { + margin-bottom: 12px; + width: 100%; + margin-right: 0px; + } + } [purpose='section-heading'] { padding: 40px 0px; } diff --git a/website/views/pages/software-management.ejs b/website/views/pages/software-management.ejs index 8c8894cdd1..424077d3e3 100644 --- a/website/views/pages/software-management.ejs +++ b/website/views/pages/software-management.ejs @@ -5,7 +5,7 @@

Software management

Manage software consistently

Pick from a curated app library or upload your own custom packages. Configure custom installation scripts if you need or let Fleet do it for you.

-
+
Talk to an engineer Try it yourself
@@ -48,6 +48,16 @@
+
+ Eric Tan +

+ This is not just production osquery, but actually a way bigger opportunity than even something like Airwatch or Jamf. +

+
+

Eric Tan

+

CIO & Chief Security Officer at Flock Safety

+
+
@@ -163,9 +173,9 @@

Software management

Manage software consistently

-
+
Show me - See real data + Try it yourself
From d15fda2693d2bac0a83e6d91c1c7e2929684b53a Mon Sep 17 00:00:00 2001 From: Luke Heath Date: Tue, 26 Nov 2024 14:22:52 -0600 Subject: [PATCH 37/92] Bump Firefox version (#24181) --- it-and-security/teams/workstations-canary.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/it-and-security/teams/workstations-canary.yml b/it-and-security/teams/workstations-canary.yml index 0bcc07b20f..c628019906 100644 --- a/it-and-security/teams/workstations-canary.yml +++ b/it-and-security/teams/workstations-canary.yml @@ -139,7 +139,7 @@ policies: platform: darwin calendar_events_enabled: true - name: macOS - Upgrade Firefox - query: SELECT 1 FROM apps WHERE name = 'Firefox.app' AND version_compare(bundle_short_version, '130.0.1') >= 0; + query: SELECT 1 FROM apps WHERE name = 'Firefox.app' AND version_compare(bundle_short_version, '132.0.0') >= 0; critical: false description: The host may have an outdated or non-existent version of Firefox, potentially risking security vulnerabilities or compatibility issues. resolution: During maintenance, the Firefox app could be updated to the correct version or installed if it's missing. From c5d61c7490d6cc2cd574962a147037c14330c317 Mon Sep 17 00:00:00 2001 From: jacobshandling <61553566+jacobshandling@users.noreply.github.com> Date: Tue, 26 Nov 2024 12:29:50 -0800 Subject: [PATCH 38/92] UI - Improve side nav empty state UI under `/settings` (#24145) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## #23027 Screenshot 2024-11-25 at 1 09 50 PM - [x] Changes file added for user-visible changes in `changes/`, - [x] Manual QA for all new/changed functionality --------- Co-authored-by: Jacob Shandling --- changes/23027-settings-empty-states | 1 + .../IntegrationsPage/cards/Integrations/_styles.scss | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 changes/23027-settings-empty-states diff --git a/changes/23027-settings-empty-states b/changes/23027-settings-empty-states new file mode 100644 index 0000000000..ecc6736d05 --- /dev/null +++ b/changes/23027-settings-empty-states @@ -0,0 +1 @@ +* Improve side nav empty state UI under `/settings` \ No newline at end of file diff --git a/frontend/pages/admin/IntegrationsPage/cards/Integrations/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/Integrations/_styles.scss index deec080471..84251e7c97 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/Integrations/_styles.scss +++ b/frontend/pages/admin/IntegrationsPage/cards/Integrations/_styles.scss @@ -78,4 +78,15 @@ width: 24px; } } + .empty-table__container { + padding: px-to-rem(64) $pad-large; + border-radius: $pad-xsmall; + border: px-to-rem(1) solid $ui-fleet-black-10; + margin: 0; + max-width: none; + width: 100%; + h3 { + margin-bottom: px-to-rem(10); + } + } } From 86eb8fd05824b27370db5515ea5c04d9267f660a Mon Sep 17 00:00:00 2001 From: jacobshandling <61553566+jacobshandling@users.noreply.github.com> Date: Tue, 26 Nov 2024 12:30:30 -0800 Subject: [PATCH 39/92] UI - Update help text for Policy automations (scripts & software) (#24138) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Addresses #22527 Figma: Screenshot 2024-11-25 at 5 30 48 PM UI: Screenshot 2024-11-25 at 12 17 23 PM Screenshot 2024-11-25 at 12 03 53 PM - [x] Changes file added for user-visible changes in `changes/` - [x] Manual QA for all new/changed functionality --------- Co-authored-by: Jacob Shandling --- .../22527-policy-automation-ui-improvements | 1 + .../InstallSoftwareModal.tsx | 20 +++++++++++++++---- .../PolicyRunScriptModal.tsx | 17 ++++++++++++++-- 3 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 changes/22527-policy-automation-ui-improvements diff --git a/changes/22527-policy-automation-ui-improvements b/changes/22527-policy-automation-ui-improvements new file mode 100644 index 0000000000..6d56f7efa6 --- /dev/null +++ b/changes/22527-policy-automation-ui-improvements @@ -0,0 +1 @@ +- Update help text for policy automation Install software and Run script modals diff --git a/frontend/pages/policies/ManagePoliciesPage/components/InstallSoftwareModal/InstallSoftwareModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/InstallSoftwareModal/InstallSoftwareModal.tsx index 0d9681c203..d21cc7a6c3 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/InstallSoftwareModal/InstallSoftwareModal.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/InstallSoftwareModal/InstallSoftwareModal.tsx @@ -20,6 +20,7 @@ import TooltipTruncatedText from "components/TooltipTruncatedText"; import CustomLink from "components/CustomLink"; import Button from "components/buttons/Button"; import { ISoftwareTitle } from "interfaces/software"; +import TooltipWrapper from "components/TooltipWrapper"; const getPlatformDisplayFromPackageExtension = (ext: string | undefined) => { switch (ext) { @@ -223,6 +224,16 @@ const InstallSoftwareModal = ({ ); } + const compatibleTipContent = ( + <> + .pkg for macOS. +
+ .msi or .exe for Windows. +
+ .deb for Linux. + + ); + return (
@@ -233,10 +244,11 @@ const InstallSoftwareModal = ({ )} - Selected software will be installed when hosts fail the policy. Host - counts will reset when a new software is -
- selected.{" "} + Selected software, if{" "} + + compatible + {" "} + with the host, will be installed when hosts fail the chosen policy.{" "} + Shell (.sh) for macOS and Linux. +
+ PowerShell (.ps1) for Windows. + + ); + return (
@@ -201,8 +210,12 @@ const PolicyRunScriptModal = ({ )} - Selected script will run when hosts fail the policy. Host counts - will reset when a new script is selected.{" "} + Selected script, if{" "} + + compatible + {" "} + with the host, will run when hosts fail the policy. Host counts will + reset when a new script is selected.{" "} Date: Tue, 26 Nov 2024 14:45:17 -0600 Subject: [PATCH 40/92] Add redirect to disk encryption guide (#23846) For #22074 --- website/config/routes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/website/config/routes.js b/website/config/routes.js index f02a813ae5..711c31544e 100644 --- a/website/config/routes.js +++ b/website/config/routes.js @@ -599,6 +599,7 @@ module.exports.routes = { 'GET /learn-more-about/installing-fleetctl': '/guides/fleetctl#installing-fleetctl', 'GET /learn-more-about/mdm-disk-encryption': '/guides/enforce-disk-encryption', 'GET /contribute-to/policies': 'https://github.com/fleetdm/fleet/edit/main/docs/01-Using-Fleet/standard-query-library/standard-query-library.yml', + 'GET /learn-more-about/mdm-disk-encryption': '/guides/enforce-disk-encryption', // Sitemap // ============================================================================================================= From d7e946fccef94de2ce2686618c9368bc613db88f Mon Sep 17 00:00:00 2001 From: Rachael Shaw Date: Tue, 26 Nov 2024 15:06:05 -0600 Subject: [PATCH 41/92] Website: Remove duplicate redirect (#24184) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Just merged in an old PR with this change after we had already merged a newer PR adding it 🤦‍♀️ --- website/config/routes.js | 1 - 1 file changed, 1 deletion(-) diff --git a/website/config/routes.js b/website/config/routes.js index 711c31544e..f02a813ae5 100644 --- a/website/config/routes.js +++ b/website/config/routes.js @@ -599,7 +599,6 @@ module.exports.routes = { 'GET /learn-more-about/installing-fleetctl': '/guides/fleetctl#installing-fleetctl', 'GET /learn-more-about/mdm-disk-encryption': '/guides/enforce-disk-encryption', 'GET /contribute-to/policies': 'https://github.com/fleetdm/fleet/edit/main/docs/01-Using-Fleet/standard-query-library/standard-query-library.yml', - 'GET /learn-more-about/mdm-disk-encryption': '/guides/enforce-disk-encryption', // Sitemap // ============================================================================================================= From e7605d2d2fdc236e39f27c9780cff6b19423aa80 Mon Sep 17 00:00:00 2001 From: RachelElysia <71795832+RachelElysia@users.noreply.github.com> Date: Tue, 26 Nov 2024 16:30:22 -0500 Subject: [PATCH 42/92] Fleet UI: Fix app store icons with awkward borders (#24126) --- changes/23733-apple-app-store-icons | 1 + .../icons/SoftwareIcon/SoftwareIcon.tsx | 1 - .../icons/SoftwareIcon/_styles.scss | 27 ++++++++----------- 3 files changed, 12 insertions(+), 17 deletions(-) create mode 100644 changes/23733-apple-app-store-icons diff --git a/changes/23733-apple-app-store-icons b/changes/23733-apple-app-store-icons new file mode 100644 index 0000000000..f9b062ff82 --- /dev/null +++ b/changes/23733-apple-app-store-icons @@ -0,0 +1 @@ +- Fleet UI: Remove image borders that are included in Apple's app store icons diff --git a/frontend/pages/SoftwarePage/components/icons/SoftwareIcon/SoftwareIcon.tsx b/frontend/pages/SoftwarePage/components/icons/SoftwareIcon/SoftwareIcon.tsx index e7c29fec98..369621a50f 100644 --- a/frontend/pages/SoftwarePage/components/icons/SoftwareIcon/SoftwareIcon.tsx +++ b/frontend/pages/SoftwarePage/components/icons/SoftwareIcon/SoftwareIcon.tsx @@ -29,7 +29,6 @@ const SoftwareIcon = ({ url, }: ISoftwareIconProps) => { const classNames = classnames(baseClass, `${baseClass}__${size}`); - // If we are given a url to render as the icon, we need to render it // differently than the svg icons. We will use an img tag instead with the // src set to the url. diff --git a/frontend/pages/SoftwarePage/components/icons/SoftwareIcon/_styles.scss b/frontend/pages/SoftwarePage/components/icons/SoftwareIcon/_styles.scss index bb62bd5cb8..1d3e841549 100644 --- a/frontend/pages/SoftwarePage/components/icons/SoftwareIcon/_styles.scss +++ b/frontend/pages/SoftwarePage/components/icons/SoftwareIcon/_styles.scss @@ -1,6 +1,5 @@ .software-icon { flex-shrink: 0; - border: 1px solid $ui-fleet-black-10; &__small { border-radius: $border-radius; @@ -31,34 +30,30 @@ // TODO: we should change ".core-wrapper a img" selector in that file // as it too generic and can affect other parts of the app. > img.software-icon__software-img-small { - width: 22px; - height: 22px; + width: 24px; + height: 24px; border-radius: $border-radius-small; - padding: 1px; margin-left: 0; } - >img.software-icon__software-img-medium { - width: 38px; - height: 38px; + > img.software-icon__software-img-medium { + width: 40px; + height: 40px; border-radius: $border-radius-xlarge; - padding: 1px; margin-left: 0; } - >img.software-icon__software-img-large { - width: 60px; - height: 60px; + > img.software-icon__software-img-large { + width: 64px; + height: 64px; border-radius: $border-radius-xxlarge; - padding: 1px; margin-left: 0; } - >img.software-icon__software-img-xlarge { - width: 88px; - height: 88px; + > img.software-icon__software-img-xlarge { + width: 96px; + height: 96px; border-radius: $border-radius-xxlarge; - padding: $pad-xsmall; margin-left: 0; } } From 98917e7547d1cc263a21135c1029798add7864a0 Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Tue, 26 Nov 2024 16:57:32 -0600 Subject: [PATCH 43/92] Split up apple and windows profile management cron jobs (#23975) for #22824 This PR splits up the `newMDMProfileManager` function into two functions `newAppleMDMProfileManager` and `newWindowsMDMProfileManager`, the latter containing the code to start the windows-specific profile management job. This allows scheduling and scaling the Apple and Windows profile management jobs separately. Tested locally by checking that the jobs were scheduled at startup, and then triggering the `mdm_apple_profile_manager` and `mdm_windows_profile_manager` schedules via the `/trigger` API; nothing blew up. --- cmd/fleet/cron.go | 25 ++++++++++++++++++++++++- cmd/fleet/cron_test.go | 14 ++++++++++++-- cmd/fleet/serve.go | 13 ++++++++++++- server/fleet/cron_schedules.go | 1 + 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/cmd/fleet/cron.go b/cmd/fleet/cron.go index 90dc3fffa9..75d6f3ba83 100644 --- a/cmd/fleet/cron.go +++ b/cmd/fleet/cron.go @@ -1182,7 +1182,7 @@ func appleMDMDEPSyncerJob( } } -func newMDMProfileManager( +func newAppleMDMProfileManagerSchedule( ctx context.Context, instanceID string, ds fleet.Datastore, @@ -1207,6 +1207,29 @@ func newMDMProfileManager( schedule.WithJob("manage_apple_declarations", func(ctx context.Context) error { return service.ReconcileAppleDeclarations(ctx, ds, commander, logger) }), + ) + + return s, nil +} + +func newWindowsMDMProfileManagerSchedule( + ctx context.Context, + instanceID string, + ds fleet.Datastore, + logger kitlog.Logger, +) (*schedule.Schedule, error) { + const ( + name = string(fleet.CronMDMWindowsProfileManager) + // Note: per a request from #g-product we are running this cron + // every 30 seconds, we should re-evaluate how we handle the + // cron interval as we scale to more hosts. + defaultInterval = 30 * time.Second + ) + + logger = kitlog.With(logger, "cron", name) + s := schedule.New( + ctx, name, instanceID, defaultInterval, ds, ds, + schedule.WithLogger(logger), schedule.WithJob("manage_windows_profiles", func(ctx context.Context) error { return service.ReconcileWindowsProfiles(ctx, ds, logger) }), diff --git a/cmd/fleet/cron_test.go b/cmd/fleet/cron_test.go index 789b38c405..2f051d9f17 100644 --- a/cmd/fleet/cron_test.go +++ b/cmd/fleet/cron_test.go @@ -23,14 +23,24 @@ import ( kitlog "github.com/go-kit/log" ) -func TestNewMDMProfileManagerWithoutConfig(t *testing.T) { +func TestNewAppleMDMProfileManagerWithoutConfig(t *testing.T) { ctx := context.Background() mdmStorage := &mdmmock.MDMAppleStore{} ds := new(mock.Store) cmdr := apple_mdm.NewMDMAppleCommander(mdmStorage, nil) logger := kitlog.NewNopLogger() - sch, err := newMDMProfileManager(ctx, "foo", ds, cmdr, logger) + sch, err := newAppleMDMProfileManagerSchedule(ctx, "foo", ds, cmdr, logger) + require.NotNil(t, sch) + require.NoError(t, err) +} + +func TestNewWindowsMDMProfileManagerWithoutConfig(t *testing.T) { + ctx := context.Background() + ds := new(mock.Store) + logger := kitlog.NewNopLogger() + + sch, err := newWindowsMDMProfileManagerSchedule(ctx, "foo", ds, logger) require.NotNil(t, sch) require.NoError(t, err) } diff --git a/cmd/fleet/serve.go b/cmd/fleet/serve.go index a523a91bac..0b770ca43a 100644 --- a/cmd/fleet/serve.go +++ b/cmd/fleet/serve.go @@ -924,7 +924,7 @@ the way that the Fleet server works. } if err := cronSchedules.StartCronSchedule(func() (fleet.CronSchedule, error) { - return newMDMProfileManager( + return newAppleMDMProfileManagerSchedule( ctx, instanceID, ds, @@ -935,6 +935,17 @@ the way that the Fleet server works. initFatal(err, "failed to register mdm_apple_profile_manager schedule") } + if err := cronSchedules.StartCronSchedule(func() (fleet.CronSchedule, error) { + return newWindowsMDMProfileManagerSchedule( + ctx, + instanceID, + ds, + logger, + ) + }); err != nil { + initFatal(err, "failed to register mdm_windows_profile_manager schedule") + } + if err := cronSchedules.StartCronSchedule(func() (fleet.CronSchedule, error) { return newMDMAPNsPusher( ctx, diff --git a/server/fleet/cron_schedules.go b/server/fleet/cron_schedules.go index 42541a96d1..12fa1ef7ad 100644 --- a/server/fleet/cron_schedules.go +++ b/server/fleet/cron_schedules.go @@ -21,6 +21,7 @@ const ( CronWorkerIntegrations CronScheduleName = "integrations" CronActivitiesStreaming CronScheduleName = "activities_streaming" CronMDMAppleProfileManager CronScheduleName = "mdm_apple_profile_manager" + CronMDMWindowsProfileManager CronScheduleName = "mdm_windows_profile_manager" CronAppleMDMIPhoneIPadRefetcher CronScheduleName = "apple_mdm_iphone_ipad_refetcher" CronAppleMDMAPNsPusher CronScheduleName = "apple_mdm_apns_pusher" CronCalendar CronScheduleName = "calendar" From 84742c24014df595ce25f14b9120cc7b802cddfe Mon Sep 17 00:00:00 2001 From: Jahziel Villasana-Espinoza Date: Tue, 26 Nov 2024 18:48:45 -0500 Subject: [PATCH 44/92] fix: add fleet actor for setup experience global activities (#24196) > Follow up on #23310 # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Manual QA for all new/changed functionality --- .../cards/ActivityFeed/ActivityItem/ActivityItem.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx index 6318b9ce89..b162a22fcc 100644 --- a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx +++ b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx @@ -1385,6 +1385,14 @@ const ActivityItem = ({ ? addGravatarUrlToResource({ email: actor_email }) : { gravatar_url: DEFAULT_GRAVATAR_LINK }; + if ( + !activity.actor_email && + !activity.actor_full_name && + !activity.actor_id + ) { + activity.actor_full_name = "Fleet"; + } + const activityCreatedAt = new Date(activity.created_at); const indicatePremiumFeature = isSandboxMode && PREMIUM_ACTIVITIES.has(activity.type); From 25fe7c04534532753b631db0b9488927f6db950c Mon Sep 17 00:00:00 2001 From: Luke Heath Date: Tue, 26 Nov 2024 17:49:10 -0600 Subject: [PATCH 45/92] Install updated Firefox on policy failure (#24182) --- it-and-security/teams/workstations-canary.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/it-and-security/teams/workstations-canary.yml b/it-and-security/teams/workstations-canary.yml index c628019906..136f620f3c 100644 --- a/it-and-security/teams/workstations-canary.yml +++ b/it-and-security/teams/workstations-canary.yml @@ -144,7 +144,8 @@ policies: description: The host may have an outdated or non-existent version of Firefox, potentially risking security vulnerabilities or compatibility issues. resolution: During maintenance, the Firefox app could be updated to the correct version or installed if it's missing. platform: darwin - calendar_events_enabled: false + install_software: + package_path: "../lib/software/mac-mozilla-firefox.yml" - name: macOS - Upgrade Slack query: SELECT 1 FROM apps WHERE name = 'Slack.app' AND version_compare(bundle_short_version, '4.40.126') >= 0; critical: false From 7f1c0e4c417eacd3c5952b9c724fec4a2c9a1a65 Mon Sep 17 00:00:00 2001 From: Tim Lee Date: Tue, 26 Nov 2024 20:26:22 -0500 Subject: [PATCH 46/92] Linux OS settings + disk encryption host filter fixes (#24200) #24174 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. - [X] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [X] Added/updated tests - [X] Manual QA for all new/changed functionality --------- Co-authored-by: Ian Littman --- server/datastore/mysql/hosts.go | 43 +++++++++++--- server/datastore/mysql/hosts_test.go | 84 ++++++++++++++++++++------- server/datastore/mysql/labels_test.go | 4 +- server/datastore/mysql/linux_mdm.go | 34 +++++++++++ 4 files changed, 133 insertions(+), 32 deletions(-) diff --git a/server/datastore/mysql/hosts.go b/server/datastore/mysql/hosts.go index 0de58ba918..659aa12091 100644 --- a/server/datastore/mysql/hosts.go +++ b/server/datastore/mysql/hosts.go @@ -1404,21 +1404,39 @@ func (ds *Datastore) filterHostsByOSSettingsStatus(sql string, opt fleet.HostLis // or are servers. Similar logic could be applied to macOS hosts but is not included in this // current implementation. - sqlFmt := ` AND h.platform IN('windows', 'darwin', 'ios', 'ipados') AND (ne.id IS NOT NULL OR mwe.host_uuid IS NOT NULL) AND hmdm.enrolled = 1` + // TODO once testLabelsListHostsInLabelOSSettings enrolls hosts into the correct MDM, switch to this: + /*sqlFmt := ` AND ( + (h.platform = 'windows' AND mwe.host_uuid IS NOT NULL AND hmdm.enrolled = 1) -- windows + OR (h.platform IN ('darwin', 'ios', 'ipados') AND ne.id IS NOT NULL AND hmdm.enrolled = 1) -- apple + OR (h.platform = 'ubuntu' OR h.os_version LIKE 'Fedora%%') -- linux + )`*/ + + sqlFmt := ` AND ( + (h.platform IN('windows', 'darwin', 'ios', 'ipados') AND (ne.id IS NOT NULL OR mwe.host_uuid IS NOT NULL) AND hmdm.enrolled = 1) + OR (h.platform = 'ubuntu' OR h.os_version LIKE 'Fedora%%') + )` + if opt.TeamFilter == nil { // OS settings filter is not compatible with the "all teams" option so append the "no team" // filter here (note that filterHostsByTeam applies the "no team" filter if TeamFilter == 0) sqlFmt += ` AND h.team_id IS NULL` } - var whereMacOS, whereWindows string + var whereMacOS, whereWindows, whereLinux string sqlFmt += ` -AND ((h.platform = 'windows' AND (%s)) -OR ((h.platform = 'darwin' OR h.platform = 'ios' OR h.platform = 'ipados') AND (%s)))` +AND ( + (h.platform = 'windows' AND (%s)) + OR ((h.platform = 'darwin' OR h.platform = 'ios' OR h.platform = 'ipados') AND (%s)) + OR ((h.os_version LIKE 'Fedora%%' OR h.platform = 'ubuntu') AND (%s)) +)` // construct the WHERE for macOS whereMacOS = fmt.Sprintf(`(%s) = ?`, sqlCaseMDMAppleStatus()) paramsMacOS := []any{opt.OSSettingsFilter} + // construct the WHERE for linux + whereLinux = fmt.Sprintf(`(%s) = ?`, sqlCaseLinuxOSSettingsStatus()) + paramsLinux := []any{opt.OSSettingsFilter} + // construct the WHERE for windows whereWindows = `hmdm.is_server = 0` paramsWindows := []any{} @@ -1520,8 +1538,9 @@ OR ((h.platform = 'darwin' OR h.platform = 'ios' OR h.platform = 'ipados') AND ( paramsWindows = append(paramsWindows, opt.OSSettingsFilter) params = append(params, paramsWindows...) params = append(params, paramsMacOS...) + params = append(params, paramsLinux...) - return sql + fmt.Sprintf(sqlFmt, whereWindows, whereMacOS), params, nil + return sql + fmt.Sprintf(sqlFmt, whereWindows, whereMacOS, whereLinux), params, nil } func (ds *Datastore) filterHostsByOSSettingsDiskEncryptionStatus(sql string, opt fleet.HostListOptions, params []interface{}, enableDiskEncryption bool) (string, []interface{}) { @@ -1529,13 +1548,13 @@ func (ds *Datastore) filterHostsByOSSettingsDiskEncryptionStatus(sql string, opt return sql, params } - sqlFmt := " AND h.platform IN('windows', 'darwin')" + sqlFmt := " AND h.platform IN('windows', 'darwin', 'ubuntu', 'rhel')" if opt.TeamFilter == nil { // OS settings filter is not compatible with the "all teams" option so append the "no // team" filter here (note that filterHostsByTeam applies the "no team" filter if TeamFilter == 0) sqlFmt += ` AND h.team_id IS NULL` } - sqlFmt += ` AND ((h.platform = 'windows' AND %s) OR (h.platform = 'darwin' AND %s))` + sqlFmt += ` AND ((h.platform = 'windows' AND %s) OR (h.platform = 'darwin' AND %s) OR ((h.platform = 'ubuntu' OR h.os_version LIKE 'Fedora%%') AND %s))` var subqueryMacOS string var subqueryParams []interface{} @@ -1580,7 +1599,10 @@ func (ds *Datastore) filterHostsByOSSettingsDiskEncryptionStatus(sql string, opt whereMacOS = "EXISTS (" + subqueryMacOS + ")" } - return sql + fmt.Sprintf(sqlFmt, whereWindows, whereMacOS), append(params, subqueryParams...) + whereLinux := fmt.Sprintf(`(%s) = ?`, sqlCaseLinuxDiskEncryptionStatus()) + subqueryParams = append(subqueryParams, opt.OSSettingsDiskEncryptionFilter) + + return sql + fmt.Sprintf(sqlFmt, whereWindows, whereMacOS, whereLinux), append(params, subqueryParams...) } func filterHostsByMDMBootstrapPackageStatus(sql string, opt fleet.HostListOptions, params []interface{}) (string, []interface{}) { @@ -3839,16 +3861,19 @@ ON DUPLICATE KEY UPDATE `, hostID, encryptedBase64Passphrase, encryptedBase64Salt, keySlot) return err } + func (ds *Datastore) IsHostPendingEscrow(ctx context.Context, hostID uint) bool { var pendingEscrowCount uint _ = sqlx.GetContext(ctx, ds.reader(ctx), &pendingEscrowCount, ` SELECT COUNT(*) FROM host_disk_encryption_keys WHERE host_id = ? AND reset_requested = TRUE`, hostID) return pendingEscrowCount > 0 } + func (ds *Datastore) ClearPendingEscrow(ctx context.Context, hostID uint) error { _, err := ds.writer(ctx).ExecContext(ctx, `UPDATE host_disk_encryption_keys SET reset_requested = FALSE WHERE host_id = ?`, hostID) return err } + func (ds *Datastore) ReportEscrowError(ctx context.Context, hostID uint, errorMessage string) error { _, err := ds.writer(ctx).ExecContext(ctx, ` INSERT INTO host_disk_encryption_keys @@ -3856,6 +3881,7 @@ INSERT INTO host_disk_encryption_keys `, hostID, errorMessage) return err } + func (ds *Datastore) QueueEscrow(ctx context.Context, hostID uint) error { _, err := ds.writer(ctx).ExecContext(ctx, ` INSERT INTO host_disk_encryption_keys @@ -3863,6 +3889,7 @@ INSERT INTO host_disk_encryption_keys `, hostID) return err } + func (ds *Datastore) AssertHasNoEncryptionKeyStored(ctx context.Context, hostID uint) error { var hasKeyCount uint err := sqlx.GetContext(ctx, ds.reader(ctx), &hasKeyCount, ` diff --git a/server/datastore/mysql/hosts_test.go b/server/datastore/mysql/hosts_test.go index de3fe566e7..51e12566a6 100644 --- a/server/datastore/mysql/hosts_test.go +++ b/server/datastore/mysql/hosts_test.go @@ -790,6 +790,7 @@ func testHostsDelete(t *testing.T, ds *Datastore) { } func listHostsCheckCount(t *testing.T, ds *Datastore, filter fleet.TeamFilter, opt fleet.HostListOptions, expectedCount int) []*fleet.Host { + t.Helper() hosts, err := ds.ListHosts(context.Background(), filter, opt) require.NoError(t, err) count, err := ds.CountHosts(context.Background(), filter, opt) @@ -809,28 +810,35 @@ func testHostListOptionsTeamFilter(t *testing.T, ds *Datastore) { require.NoError(t, err) var hosts []*fleet.Host - for i := 0; i < 10; i++ { + for i := 0; i < 20; i++ { var opts []test.NewHostOption switch i { - case 5, 6: + case 0: opts = append(opts, test.WithPlatform("windows")) + case 1, 2: + opts = append(opts, test.WithPlatform("ubuntu")) // supported for linux encryption + case 3, 4, 5: + opts = append(opts, test.WithOSVersion("Fedora 33")) // supported for linux encryption + case 6, 7, 8, 9: + opts = append(opts, test.WithPlatform("foo")) // not supported for linux encryption } h := test.NewHost(t, ds, fmt.Sprintf("foo.local.%d", i), "1.1.1.1", - fmt.Sprintf("%d", i), fmt.Sprintf("%d", i), time.Now(), opts...) + fmt.Sprintf("%d", i), fmt.Sprintf("%d", i), time.Now(), opts...) // default macos platform hosts = append(hosts, h) nanoEnrollAndSetHostMDMData(t, ds, h, false) } + userFilter := fleet.TeamFilter{User: test.UserAdmin} - // confirm intial state + // confirm initial state listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{}, len(hosts)) listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterNil}, len(hosts)) listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterZero}, len(hosts)) listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: &team1.ID}, 0) listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: &team2.ID}, 0) - // assign three hosts to team 1 - require.NoError(t, ds.AddHostsToTeam(context.Background(), &team1.ID, []uint{hosts[0].ID, hosts[1].ID, hosts[2].ID})) + // assign three macos hosts to team 1 + require.NoError(t, ds.AddHostsToTeam(context.Background(), &team1.ID, []uint{hosts[10].ID, hosts[11].ID, hosts[12].ID})) listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{}, len(hosts)) listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterNil}, len(hosts)) listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterZero}, len(hosts)-3) @@ -838,7 +846,7 @@ func testHostListOptionsTeamFilter(t *testing.T, ds *Datastore) { listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: &team2.ID}, 0) // assign four hosts to team 2 - require.NoError(t, ds.AddHostsToTeam(context.Background(), &team2.ID, []uint{hosts[3].ID, hosts[4].ID, hosts[5].ID, hosts[6].ID})) + require.NoError(t, ds.AddHostsToTeam(context.Background(), &team2.ID, []uint{hosts[13].ID, hosts[14].ID, hosts[15].ID, hosts[16].ID})) listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{}, len(hosts)) listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterNil}, len(hosts)) listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterZero}, len(hosts)-7) @@ -851,7 +859,7 @@ func testHostListOptionsTeamFilter(t *testing.T, ds *Datastore) { { ProfileUUID: profUUID, ProfileIdentifier: "identifier", - HostUUID: hosts[0].UUID, // hosts[0] is assgined to team 1 + HostUUID: hosts[10].UUID, // hosts[10] is assgined to team 1 CommandUUID: "command-uuid-1", OperationType: fleet.MDMOperationTypeInstall, Status: &fleet.MDMDeliveryVerifying, @@ -869,46 +877,78 @@ func testHostListOptionsTeamFilter(t *testing.T, ds *Datastore) { { ProfileUUID: profUUID, ProfileIdentifier: "identifier", - HostUUID: hosts[9].UUID, // hosts[9] is assgined to no team + HostUUID: hosts[19].UUID, // hosts[19] is assgined to no team CommandUUID: "command-uuid-2", OperationType: fleet.MDMOperationTypeInstall, Status: &fleet.MDMDeliveryVerifying, Checksum: []byte("csum"), }, })) - listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: &team1.ID, MacOSSettingsFilter: fleet.OSSettingsVerifying}, 1) // hosts[0] + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: &team1.ID, MacOSSettingsFilter: fleet.OSSettingsVerifying}, 1) // hosts[10] listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: &team2.ID, MacOSSettingsFilter: fleet.OSSettingsVerifying}, 0) // wrong team // macos settings filter does not support "all teams" so both teamIDFilterNil acts the same as teamIDFilterZero - listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterZero, MacOSSettingsFilter: fleet.OSSettingsVerifying}, 1) // hosts[9] - listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterNil, MacOSSettingsFilter: fleet.OSSettingsVerifying}, 1) // hosts[9] - listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{MacOSSettingsFilter: fleet.OSSettingsVerifying}, 1) // hosts[9] + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterZero, MacOSSettingsFilter: fleet.OSSettingsVerifying}, 1) // hosts[19] + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterNil, MacOSSettingsFilter: fleet.OSSettingsVerifying}, 1) // hosts[19] + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{MacOSSettingsFilter: fleet.OSSettingsVerifying}, 1) // hosts[19] - // test team filter in combination with os settings filter - listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: &team1.ID, OSSettingsFilter: fleet.OSSettingsVerifying}, 1) // hosts[0] + // OS Settings Filters + + // team 1 + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: &team1.ID, OSSettingsFilter: fleet.OSSettingsVerifying}, 1) // hosts[10] + + // team 2 listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: &team2.ID, OSSettingsFilter: fleet.OSSettingsVerifying}, 0) // wrong team + // os settings filter does not support "all teams" so teamIDFilterNil acts the same as teamIDFilterZero - listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterZero, OSSettingsFilter: fleet.OSSettingsVerifying}, 1) // hosts[9] - listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterNil, OSSettingsFilter: fleet.OSSettingsVerifying}, 1) // hosts[9] + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterZero, OSSettingsFilter: fleet.OSSettingsVerifying}, 1) // hosts[19] + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterNil, OSSettingsFilter: fleet.OSSettingsVerifying}, 1) // hosts[19] listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{OSSettingsFilter: fleet.OSSettingsVerifying}, 1) + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterZero, OSSettingsFilter: fleet.OSSettingsPending}, 5) // pending supported linux hosts + + require.NoError(t, ds.SaveLUKSData(context.Background(), hosts[1].ID, "key1", "morton", 1)) // set host 1 to verified + require.NoError(t, ds.ReportEscrowError(context.Background(), hosts[2].ID, "error")) // set host 2 to failed + + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterZero, OSSettingsFilter: fleet.OSSettingsVerified}, 1) // hosts[1] + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterZero, OSSettingsFilter: fleet.OSSettingsFailed}, 1) // hosts[2] + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterZero, OSSettingsFilter: fleet.OSSettingsPending}, 3) // still-pending supported linux hosts + + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterZero, OSSettingsDiskEncryptionFilter: fleet.DiskEncryptionVerified}, 1) + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterZero, OSSettingsDiskEncryptionFilter: fleet.DiskEncryptionFailed}, 1) + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterZero, OSSettingsDiskEncryptionFilter: fleet.DiskEncryptionActionRequired}, 3) // test team filter in combination with os settings disk encryptionfilter require.NoError(t, ds.BulkUpsertMDMAppleHostProfiles(context.Background(), []*fleet.MDMAppleBulkUpsertHostProfilePayload{ { ProfileUUID: profUUID, ProfileIdentifier: mobileconfig.FleetFileVaultPayloadIdentifier, - HostUUID: hosts[8].UUID, // hosts[8] is assgined to no team + HostUUID: hosts[18].UUID, // hosts[18] is assgined to no team CommandUUID: "command-uuid-3", OperationType: fleet.MDMOperationTypeInstall, Status: &fleet.MDMDeliveryPending, Checksum: []byte("disk-encryption-csum"), }, })) - listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: &team1.ID, OSSettingsDiskEncryptionFilter: fleet.DiskEncryptionEnforcing}, 0) // hosts[0] + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: &team1.ID, OSSettingsDiskEncryptionFilter: fleet.DiskEncryptionEnforcing}, 0) // hosts[10] listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: &team2.ID, OSSettingsDiskEncryptionFilter: fleet.DiskEncryptionEnforcing}, 0) // wrong team // os settings filter does not support "all teams" so teamIDFilterNil acts the same as teamIDFilterZero - listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterZero, OSSettingsDiskEncryptionFilter: fleet.DiskEncryptionEnforcing}, 1) // hosts[8] - listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterNil, OSSettingsDiskEncryptionFilter: fleet.DiskEncryptionEnforcing}, 1) // hosts[8] - listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{OSSettingsDiskEncryptionFilter: fleet.DiskEncryptionEnforcing}, 1) // hosts[8] + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterZero, OSSettingsDiskEncryptionFilter: fleet.DiskEncryptionEnforcing}, 1) // hosts[18] + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterNil, OSSettingsDiskEncryptionFilter: fleet.DiskEncryptionEnforcing}, 1) // hosts[18] + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{OSSettingsDiskEncryptionFilter: fleet.DiskEncryptionEnforcing}, 1) // hosts[18] + + // move linux hosts to team 1 (un-escrows keys) + require.NoError(t, ds.AddHostsToTeam(context.Background(), &team1.ID, []uint{hosts[1].ID, hosts[2].ID, hosts[3].ID, hosts[4].ID, hosts[5].ID})) + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: &team1.ID, OSSettingsFilter: fleet.OSSettingsPending}, 5) // pending supported linux hosts + + require.NoError(t, ds.SaveLUKSData(context.Background(), hosts[1].ID, "key1", "mutton", 2)) // set host 1 to verified + require.NoError(t, ds.ReportEscrowError(context.Background(), hosts[2].ID, "error")) // set host 2 to failed + + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: &team1.ID, OSSettingsFilter: fleet.OSSettingsVerified}, 1) // hosts[1] + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: &team1.ID, OSSettingsFilter: fleet.OSSettingsFailed}, 1) // hosts[2] + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: &team1.ID, OSSettingsFilter: fleet.OSSettingsPending}, 3) // still-pending supported linux hosts + + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: &team1.ID, OSSettingsDiskEncryptionFilter: fleet.DiskEncryptionVerified}, 1) + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: &team1.ID, OSSettingsDiskEncryptionFilter: fleet.DiskEncryptionFailed}, 1) + listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: &team1.ID, OSSettingsDiskEncryptionFilter: fleet.DiskEncryptionActionRequired}, 3) // Bad team filter _, err = ds.ListHosts(context.Background(), userFilter, fleet.HostListOptions{TeamFilter: teamIDFilterBad}) diff --git a/server/datastore/mysql/labels_test.go b/server/datastore/mysql/labels_test.go index 52805cecc6..8db192ddba 100644 --- a/server/datastore/mysql/labels_test.go +++ b/server/datastore/mysql/labels_test.go @@ -1568,14 +1568,14 @@ func testLabelsListHostsInLabelOSSettings(t *testing.T, db *Datastore) { hosts := listHostsInLabelCheckCount(t, db, filter, l1.ID, fleet.HostListOptions{}, 3) checkHosts(t, hosts, []uint{h1.ID, h2.ID, h3.ID}) - t.Run("os_settings", func(t *testing.T) { + t.Run("os_settings_disk_encryption", func(t *testing.T) { hosts = listHostsInLabelCheckCount(t, db, filter, l1.ID, fleet.HostListOptions{OSSettingsDiskEncryptionFilter: fleet.DiskEncryptionVerified}, 1) checkHosts(t, hosts, []uint{h1.ID}) hosts = listHostsInLabelCheckCount(t, db, filter, l1.ID, fleet.HostListOptions{OSSettingsDiskEncryptionFilter: fleet.DiskEncryptionEnforcing}, 1) checkHosts(t, hosts, []uint{h2.ID}) }) - t.Run("os_settings_disk_encryption", func(t *testing.T) { + t.Run("os_settings", func(t *testing.T) { hosts = listHostsInLabelCheckCount(t, db, filter, l1.ID, fleet.HostListOptions{OSSettingsFilter: fleet.OSSettingsVerified}, 1) checkHosts(t, hosts, []uint{h1.ID}) hosts = listHostsInLabelCheckCount(t, db, filter, l1.ID, fleet.HostListOptions{OSSettingsFilter: fleet.OSSettingsPending}, 1) diff --git a/server/datastore/mysql/linux_mdm.go b/server/datastore/mysql/linux_mdm.go index 2cd88843ef..126cbc0a39 100644 --- a/server/datastore/mysql/linux_mdm.go +++ b/server/datastore/mysql/linux_mdm.go @@ -67,3 +67,37 @@ func (ds *Datastore) GetLinuxDiskEncryptionSummary(ctx context.Context, teamID * return summary, nil } + +func sqlCaseLinuxOSSettingsStatus() string { + return ` + CASE WHEN + hdek.base64_encrypted IS NOT NULL + AND hdek.base64_encrypted != '' + AND hdek.client_error = '' THEN + '` + string(fleet.OSSettingsVerified) + `' + WHEN hdek.client_error IS NOT NULL + AND hdek.client_error != '' THEN + '` + string(fleet.OSSettingsFailed) + `' + WHEN hdek.base64_encrypted IS NULL + OR (hdek.base64_encrypted = '' + AND hdek.client_error = '') THEN + '` + string(fleet.OSSettingsPending) + `' + END` +} + +func sqlCaseLinuxDiskEncryptionStatus() string { + return ` + CASE WHEN + hdek.base64_encrypted IS NOT NULL + AND hdek.base64_encrypted != '' + AND hdek.client_error = '' THEN + '` + string(fleet.DiskEncryptionVerified) + `' + WHEN hdek.client_error IS NOT NULL + AND hdek.client_error != '' THEN + '` + string(fleet.DiskEncryptionFailed) + `' + WHEN hdek.base64_encrypted IS NULL + OR (hdek.base64_encrypted = '' + AND hdek.client_error = '') THEN + '` + string(fleet.DiskEncryptionActionRequired) + `' + END` +} From 1d90bf08d137b5473379816c4e7cb27bae7c324a Mon Sep 17 00:00:00 2001 From: Sam Pfluger <108141731+Sampfluger88@users.noreply.github.com> Date: Tue, 26 Nov 2024 20:43:00 -0600 Subject: [PATCH 47/92] SC changes (#24194) --- CODEOWNERS | 1 - handbook/sales/README.md | 91 +++++++++++++++------------------------- 2 files changed, 34 insertions(+), 58 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 68cb118802..33a8c371f2 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -69,7 +69,6 @@ go.mod @fleetdm/go /docs/REST\ API/rest-api.md @rachaelshaw # « REST API reference documentation /docs/Contributing/API-for-contributors.md @rachaelshaw # « Advanced / contributors-only API reference documentation /schema @eashaw # « Data tables (osquery/fleetd schema) documentation -/docs/Deploy/_kubernetes/ @dherder # « Kubernetes best practice /render.yaml @edwardsb ############################################################################################## diff --git a/handbook/sales/README.md b/handbook/sales/README.md index ce0cd254c1..11bdfb4219 100644 --- a/handbook/sales/README.md +++ b/handbook/sales/README.md @@ -8,7 +8,7 @@ This handbook page details processes specific to working [with](#contact-us) and | Role                                  | Contributor(s) | |:--------------------------------------|:------------------------------------------------------------------------------------------------------------------------| | Chief Revenue Officer (CRO) | [Alex Mitchell](https://www.linkedin.com/in/alexandercmitchell/) _([@alexmitchelliii](https://github.com/alexmitchelliii))_ -| Solutions Consulting (SC) | [Dave Herder](https://www.linkedin.com/in/daveherder/) _([@dherder](https://github.com/dherder))_
[Allen Houchins](https://www.linkedin.com/in/allenhouchins/) _([@allenhouchins](https://github.com/allenhouchins))_
[Harrison Ravazzolo](https://www.linkedin.com/in/harrison-ravazzolo/) _([@harrisonravazzolo](https://github.com/harrisonravazzolo))_ +| Solutions Consulting (SC) | [Allen Houchins](https://www.linkedin.com/in/allenhouchins/) _([@allenhouchins](https://github.com/allenhouchins))_
[Harrison Ravazzolo](https://www.linkedin.com/in/harrison-ravazzolo/) _([@harrisonravazzolo](https://github.com/harrisonravazzolo))_ | Channel Sales | [Tom Ostertag](https://www.linkedin.com/in/tom-ostertag-77212791/) _([@tomostertag](https://github.com/TomOstertag))_ | Account Executive (AE) | [Patricia Ambrus](https://www.linkedin.com/in/pambrus/) _([@ambrusps](https://github.com/ambrusps))_
[Anthony Snyder](https://www.linkedin.com/in/anthonysnyder8/) _([@anthonysnyder8](https://github.com/AnthonySnyder8))_
[Paul Tardif](https://www.linkedin.com/in/paul-t-750833/) _([@phtardif1](https://github.com/phtardif1))_
[Kendra McKeever](https://www.linkedin.com/in/kendramckeever/) _([@KendraAtFleet](https://github.com/KendraAtFleet))_ @@ -53,25 +53,44 @@ Use the following steps to change a contact's organization in Salesforce: - If the contact's organization in Salesforce is incorrect and we know where they're moving to, navigate to the contact in Salesforce, change the "Account name" to the contact's new organization, and save. -### Send a quote +### Send an order form -During the buying cycle, the champion will need to start the process to secure funding in cooperation with the economic buyer and the finance org. +In order to be transparent, Fleet sends order forms within 30 days of opportunity creation in most cases. All quotes and purchase orders must be approved by the CRO and 🌐 [Head of Digital Experience](https://fleetdm.com/handbook/digital-experience#team) before being sent to the prospect or customer. Often, the CRO will request legal review of any unique terms required. To prepare and send a subscription order form the Fleet owner of the opportunity (usually AE or CSM) will: -All quotes and purchase orders must be approved by CRO before being sent to the prospect or customer. Often, the CRO will request legal review of any unique terms required. +1. Navigate to the ["Template gallery"](https://docs.google.com/document/u/0/?tgif=d&ftv=1) in Google Docs and create a copy of the "TEMPLATE - Order form". +2. Add/remove table rows as needed for multi-year deals. +3. Where possible, include a graphic of the customer's logo. Use good judgment and omit if a high-quality graphic is unavailable. If in doubt, ask Digital Experience for help. -The Fleet owner of the opportunity (usually AE or CSM) will prepare a quote and/or a Purchase Order when requested. -- Because the champion may need to socialize "what is Fleet" or "what are we getting when buying Fleet," it is most often best to send the quote in [slide form](https://docs.google.com/presentation/d/15kbqm0OYPf1OmmTZvDp4F7VvMERnX4K6TMYqCYNr-wI/edit?usp=sharing). -- Docusign can be used to create a [standard Purchase Order](https://www.loom.com/share/Loom-Message-16-January-2023-2ba8cf195ec645ebabac267d7df59823?sid=214f8c6b-beb3-427a-a3a8-e8c20b5dc350) if no special terms or pricing are needed. -- Before sending to prospect, work with the Finance team to verify if sales tax needs to be charged and, if so, how much. +> **Important** +> - All changes to the [subscription agreement template](https://docs.google.com/document/d/1X4fh2LsuFtAVyQDnU1ZGBggqg-Ec00vYHACyckEooqA/edit?tab=t.0), or [standard terms](http://fleetdm.com/terms) must be brought to ["🦢🗣 Design review (#g-digital-experience)"](https://app.zenhub.com/workspaces/-g-digital-experience-6451748b4eb15200131d4bab/board?sprints=none) for approval. +> - All non-standard (from another party) subscription agreements, NDAs, and similar contracts require legal review from Digital Experience before being signed. [Create an issue to request legal review](https://github.com/fleetdm/confidential/blob/main/.github/ISSUE_TEMPLATE/contract-review.md). + +4. In the internal Slack channel for the deal, at-mention the CRO and the Head of Digital Experience with a link to the docx version of the order and ask them to approve the order form. +5. Once approved, send the order to the prospect. -### Schedule a Solutions Consultant for prospect meeting +### Send an NDA to a customer + +- Fleet uses "Non-Disclosure Agreements" (NDAs) to protect the company and the companies we collaborate with. Always offer to send Fleet's NDA and, whenever possible, default to using the company's version. To send an NDA to a customer, follow these steps: +1. If a customer has no objections to using Fleet's NDA, route the NDA to them for signature using the "🙊 NDA (Non-disclosure agreement)" template in [DocuSign](https://apps.docusign.com/send/home). +> If a customer would like to review the NDA first, download a .docx of [Fleet's NDA](https://docs.google.com/document/d/1gQCrF3silBFG9dJgyCvpmLa6hPhX_T4V7pL3XAwgqEU/edit?usp=sharing) and send it to the customer. +2. If the customer has no objections, route the NDA using the template in DocuSign (do not upload and use the copy you emailed to the customer). +3. If the customer "redlines" (i.e. wants to change) the NDA, follow the [contract review process](https://fleetdm.com/handbook/company/communications#getting-a-contract-reviewed) so that Digital Experience can look over any proposed changes and provide guidance on how to proceed. + + +### Close a new customer deal + +To close a deal with a new customer (non-self-service), create and complete a GitHub issue using the ["Sale" issue template](https://github.com/fleetdm/confidential/issues/new?assignees=alexmitchelliii&labels=%23g-sales&projects=&template=3-sale.md&title=New+customer%3A+_____________). + + +### Process a security questionnaire + +- The AE will [use the handbook](https://fleetdm.com/handbook/company/communications#vendor-questionnaires) to answer most of the questions with links to appropriate sections in the handbook. After this first pass has been completed, and if there are outstanding questions, the AE will [assign the issue to Digital Experience (#g-digital-experience)](https://fleetdm.com/handbook/digital-experience#contact-us) with a requested timeline for completion defined. +- Digital Experience consults the handbook to validate that nothing was missed by the AE. After the second pass has been completed, and if there are outstanding questions, Digital Experience will [reassign the issue to Sales (#g-sales)](https://fleetdm.com/handbook/sales#contact-us) for intake. +- The issue will be assigned to the Solutions Consultant (SC) associated to the opportunity in order to complete any unanswered questions. +- The SC will search for unanswered questions and confirm again that nothing was missed from the handbook. Content missing from the handbook will need to be added via PR by the SC. Any unanswered questions after this pass has been completed by the SC will need to be [escalated to the Infrastructure team (#g-customer-success)](https://fleetdm.com/handbook/customer-success#contact-us) with the requested timeline for completion defined in the issue. Once complete, the infra team will assign the issue back to the #g-sales board. +- Any questions answered by the infra team will be added to the handbook by the SC. -To schedule an [ad hoc meeting](https://www.vocabulary.com/dictionary/ad%20hoc) with a Fleet prospect, the Account Executive (AE) will [open an issue](https://github.com/fleetdm/confidential/issues/new?assignees=&labels=%23g-sales%2C%23solutions-consultant%2C%3Adiscovery%2C%3Ademo%2C%3Ascoping%2C%3Atech-eval&projects=&template=custom-request.md&title=prospect+name+-+prep+%28date%29+-+discovery%2Cdemo%2Cscoping+%28date%29). - - Use [this calendly link](https://calendly.com/fleetdm/talk-to-a-solutions-consultant) to obtain SC availability. - - The AE will populate this issue with the appropriate dates for an internal prep meeting as well as the dates for the external prospect meeting. - - Do not assign the issue. The Director of Solutions Consulting will assign the issue. - - Ensure that the product category is defined ("Endpoint ops", "Device management", or "Vulnerability management") in the description of the issue. -### Send an NDA to a customer -- Fleet uses "Non-Disclosure Agreements" (NDAs) to protect the company and the companies we collaborate with. Always offer to send Fleet's NDA and, whenever possible, default to using the company's version. To send an NDA to a customer, follow these steps: -1. If a customer has no objections to using Fleet's NDA, route the NDA to them for signature using the "🙊 NDA (Non-disclosure agreement)" template in [DocuSign](https://apps.docusign.com/send/home). -> If a customer would like to review the NDA first, download a .docx of [Fleet's NDA](https://docs.google.com/document/d/1gQCrF3silBFG9dJgyCvpmLa6hPhX_T4V7pL3XAwgqEU/edit?usp=sharing) and send it to the customer. -2. If the customer has no objections, route the NDA using the template in DocuSign (do not upload and use the copy you emailed to the customer). -3. If the customer "redlines" (i.e. wants to change) the NDA, follow the [contract review process](https://fleetdm.com/handbook/company/communications#getting-a-contract-reviewed) so that Digital Experience can look over any proposed changes and provide guidance on how to proceed. - - -### Create a subscription agreement - -1. Create a copy of the [subscription agreement template](https://docs.google.com/document/d/1ri9sS2rlBbBLEFi7RrfKZ9bZj8e48orsFKZDfF0PgAg/copy?tab=t.0). -2. Add/remove table rows as needed for multi-year deals. -3. Where possible, include a graphic of the customer's logo. Use good judgement and omit if a high quality graphic is unavailable. If in doubt, ask Digital Experience for help. - -> **Important** -> - All changes to the [subscription agreement template](https://docs.google.com/document/d/1ri9sS2rlBbBLEFi7RrfKZ9bZj8e48orsFKZDfF0PgAg/copy?tab=t.0), or [standard terms](http://fleetdm.com/terms) must be brought to ["🦢🗣 Design review (#g-digital-experience)"](https://app.zenhub.com/workspaces/-g-digital-experience-6451748b4eb15200131d4bab/board?sprints=none) for approval. -> - All non-standard (from another party) subscription agreements, NDAs, and similar contracts require legal review from Digital Experience before being signed. [Create an issue to request legal review](https://github.com/fleetdm/confidential/blob/main/.github/ISSUE_TEMPLATE/contract-review.md). - - -### Close a new customer deal - -To close a deal with a new customer (non-self-service), create and complete a GitHub issue using the ["Sale" issue template](https://github.com/fleetdm/confidential/issues/new?assignees=alexmitchelliii&labels=%23g-sales&projects=&template=3-sale.md&title=New+customer%3A+_____________). - - -### Process a security questionnaire - -- The AE will [use the handbook](https://fleetdm.com/handbook/company/communications#vendor-questionnaires) to answer most of the questions with links to appropriate sections in the handbook. After this first pass has been completed, and if there are outstanding questions, the AE will [assign the issue to Digital Experience (#g-digital-experience)](https://fleetdm.com/handbook/digital-experience#contact-us) with a requested timeline for completion defined. -- Digital Experience consults the handbook to validate that nothing was missed by the AE. After the second pass has been completed, and if there are outstanding questions, Digital Experience will [reassign the issue to Sales (#g-sales)](https://fleetdm.com/handbook/sales#contact-us) for intake. -- The issue will be assigned to the Solutions Consultant (SC) associated to the opportunity in order to complete any unanswered questions. -- The SC will search for unanswered questions and confirm again that nothing was missed from the handbook. Content missing from the handbook will need to be added via PR by the SC. Any unanswered questions after this pass has been completed by the SC will need to be [escalated to the Infrastructure team (#g-customer-success)](https://fleetdm.com/handbook/customer-success#contact-us) with the requested timeline for completion defined in the issue. Once complete, the infra team will assign the issue back to the #g-sales board. -- Any questions answered by the infra team will be added to the handbook by the SC. ## Rituals @@ -225,17 +213,6 @@ Please see [handbook/company/communications#customer-support-service-level-agree ##### Submit a customer contract Please see [handbook/sales#create-a-customer-agreement](https://fleetdm.com/handbook/sales#create-a-customer-agreement) for all sections above. -##### Customer codenames -Please see [Handbook/customer-success#assign-a-customer-codename](https://www.fleetdm.com/handbook/customer-success#assign-a-customer-codename) - -##### Document customer requests -Please see [handbook/customer-success#document-customer-requests](https://fleetdm.com/handbook/customer-success#document-customer-requests) - -##### Generate a trial license key -Please see [handbook/customer-success#generate-a-trial-license-key](https://fleetdm.com/handbook/customer-success#generate-a-trial-license-key) - -#### Create customer support Issue -Please see [handbook/customer-success#create-customer-support-issue](https://fleetdm.com/handbook/customer-success#create-customer-support-issue) From 4aeace29fb8860667612f70042f4a6b4456931b7 Mon Sep 17 00:00:00 2001 From: Mike Thomas <78363703+mike-j-thomas@users.noreply.github.com> Date: Wed, 27 Nov 2024 14:03:02 +0900 Subject: [PATCH 48/92] Update testimonials.yml (#24199) Updated to use acronyms for both parts of their job title. It's shorter and more balanced. Also, having "Chief Security Officer" expanded suggests more emphasis on his security credentials. --- handbook/company/testimonials.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handbook/company/testimonials.yml b/handbook/company/testimonials.yml index a828ac0129..01307197ab 100644 --- a/handbook/company/testimonials.yml +++ b/handbook/company/testimonials.yml @@ -185,7 +185,7 @@ 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 + quoteAuthorJobTitle: CIO & CSO at Flock Safety productCategories: [Device management, Endpoint operations] - quote: We've been using Fleet for a few years at Stripe and we couldn't be happier. The fact that it's also open-source made it easy for us to try it out, customise it to our needs, and seamlessly integrate it into our existing environment. From b8969b80ec1cb9f5e69daec3cf0f94dd22b3a399 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 27 Nov 2024 01:17:57 -0600 Subject: [PATCH 49/92] Website: update statistics on homepage (#24187) Closes: #24155 Changes: - Updated the statistics on the homepage --------- Co-authored-by: Mike Thomas <78363703+mike-j-thomas@users.noreply.github.com> --- website/assets/styles/pages/homepage.less | 61 +++++++++++++++++++---- website/views/pages/homepage.ejs | 28 +++++++---- 2 files changed, 68 insertions(+), 21 deletions(-) diff --git a/website/assets/styles/pages/homepage.less b/website/assets/styles/pages/homepage.less index 38c1fa16ca..42e6ab48b7 100644 --- a/website/assets/styles/pages/homepage.less +++ b/website/assets/styles/pages/homepage.less @@ -89,7 +89,7 @@ color: #515774; text-align: center; - /* Body LG (bold) */ + text-transform: unset; font-family: Inter; font-size: 18px; font-style: normal; @@ -115,26 +115,36 @@ // flex-direction: column; // align-items: center; // } + [purpose='statistics-column'] { + display: flex; + flex-direction: row; + } [purpose='customers'] { border-right: 1px solid #E2E4EA; display: flex; - padding: 8px 64px; + padding: 8px 48px; flex-direction: column; align-items: center; } [purpose='devices'] { border-right: 1px solid #E2E4EA; display: flex; - padding: 8px 64px; + padding: 8px 48px; flex-direction: column; align-items: center; } [purpose='countries'] { display: flex; - padding: 8px 64px; + padding: 8px 48px; + flex-direction: column; + align-items: center; + border-right: 1px solid #E2E4EA; + } + [purpose='response-time'] { + display: flex; + padding: 8px 48px; flex-direction: column; align-items: center; - } } @@ -1099,6 +1109,31 @@ [purpose='integrations-section'] { margin-top: 80px; } + [purpose='statistics'] { + [purpose='statistics-column'] { + display: flex; + flex-direction: column; + width: 227px; + } + [purpose='countries'] { + border-right: none; + padding: 16px 32px; + + } + [purpose='customers'] { + padding: 16px 32px; + } + [purpose='devices'] { + padding: 16px 32px; + } + [purpose='response-time'] { + padding: 16px 32px; + } + + } + + + [purpose='homepage-text-block'] { margin-bottom: 80px; p { @@ -1399,12 +1434,13 @@ max-width: fit-content; margin-left: auto; margin-right: auto; - margin-top: 32px; + margin-top: 48px; + margin-bottom: 48px; h4 { margin-bottom: 0px; } [purpose='customers'] { - padding: 0px 64px 24px 64px; + padding: 0px 24px 24px 24px; border-right: none; } [purpose='devices'] { @@ -1413,13 +1449,15 @@ padding: 24px; border-right: none; } - + [purpose='response-time'] { + padding: 24px; + border-bottom: 1px solid #E2E4EA; + } [purpose='countries'] { - padding: 24px 64px 0px 64px; + order: 1; + padding: 24px 24px 0px 24px; border-right: none; } - - } [purpose='hero-background-image'] { background-size: auto 320px; @@ -1606,6 +1644,7 @@ font-size: 12px; } + [purpose='truncated-vulnerability-management-text'] { display: none; } diff --git a/website/views/pages/homepage.ejs b/website/views/pages/homepage.ejs index c08c4df1da..68ce46c347 100644 --- a/website/views/pages/homepage.ejs +++ b/website/views/pages/homepage.ejs @@ -21,17 +21,25 @@
-
-

100+

-

customers

+
+
+

100+

+

customers

+
+
+

2,000,000+

+

computing devices

+
-
-

2,000,000+

-

computing devices

-
-
-

90+

-

countries

+
+
+

90+

+

countries

+
+
+

8 minutes

+

avg support response time

+
From 24f83d880a22717d470557cd624a656d48e08110 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 27 Nov 2024 08:39:00 -0300 Subject: [PATCH 50/92] Update versions of fleetd components in Fleet's TUF [automated] (#24158) 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 081ff7518e..4582259cd9 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.35.0 | 1.35.0 | 1.35.0 | 1.35.0 | -| desktop | 1.35.0 | 1.35.0 | 1.35.0 | 1.35.0 | +| orbit | 1.36.0 | 1.36.0 | 1.36.0 | 1.36.0 | +| desktop | 1.36.0 | 1.36.0 | 1.36.0 | 1.36.0 | | osqueryd | 5.14.1 | 5.14.1 | 5.14.1 | 5.14.1 | | nudge | - | - | - | - | | swiftDialog | - | - | - | - | From eea90e5632e081901ccac70a88e183a9aa8d21d3 Mon Sep 17 00:00:00 2001 From: Martin Angers Date: Wed, 27 Nov 2024 12:11:08 -0500 Subject: [PATCH 51/92] Proposal fix/plan for 24024 (#24207) --- .../24024-bypass-setup-experience-if-empty | 2 + server/mdm/lifecycle/lifecycle.go | 17 ++-- server/service/apple_mdm.go | 13 +-- server/service/integration_mdm_dep_test.go | 83 +++++++++++++++++-- server/service/integration_mdm_test.go | 8 ++ server/service/orbit.go | 16 ++-- server/worker/apple_mdm.go | 45 +++++----- server/worker/apple_mdm_test.go | 58 ++++++++++--- 8 files changed, 181 insertions(+), 61 deletions(-) create mode 100644 changes/24024-bypass-setup-experience-if-empty diff --git a/changes/24024-bypass-setup-experience-if-empty b/changes/24024-bypass-setup-experience-if-empty new file mode 100644 index 0000000000..319df88c1c --- /dev/null +++ b/changes/24024-bypass-setup-experience-if-empty @@ -0,0 +1,2 @@ +* Bypass the setup experience UI if there is no setup experience item to process (no software to install, no script to execute), so that releasing the device is done without going through that window. +* Fixed releasing a DEP-enrolled macOS device if mTLS is configured for `fleetd`. diff --git a/server/mdm/lifecycle/lifecycle.go b/server/mdm/lifecycle/lifecycle.go index 33658a2367..fd96454274 100644 --- a/server/mdm/lifecycle/lifecycle.go +++ b/server/mdm/lifecycle/lifecycle.go @@ -32,13 +32,14 @@ const ( // Not all options are required for all actions, each individual action should // validate that it receives the required information. type HostOptions struct { - Action HostAction - Platform string - UUID string - HardwareSerial string - HardwareModel string - EnrollReference string - Host *fleet.Host + Action HostAction + Platform string + UUID string + HardwareSerial string + HardwareModel string + EnrollReference string + Host *fleet.Host + HasSetupExperienceItems bool } // HostLifecycle manages MDM host lifecycle actions @@ -174,6 +175,7 @@ func (t *HostLifecycle) turnOnDarwin(ctx context.Context, opts HostOptions) erro opts.Platform, tmID, opts.EnrollReference, + !opts.HasSetupExperienceItems, ) return ctxerr.Wrap(ctx, err, "queue DEP post-enroll task") } @@ -189,6 +191,7 @@ func (t *HostLifecycle) turnOnDarwin(ctx context.Context, opts HostOptions) erro opts.Platform, tmID, opts.EnrollReference, + false, ); err != nil { return ctxerr.Wrap(ctx, err, "queue manual post-enroll task") } diff --git a/server/service/apple_mdm.go b/server/service/apple_mdm.go index 04ea557deb..984e4df684 100644 --- a/server/service/apple_mdm.go +++ b/server/service/apple_mdm.go @@ -2778,20 +2778,21 @@ func (svc *MDMAppleCheckinAndCommandService) TokenUpdate(r *mdm.Request, m *mdm. return ctxerr.Wrap(r.Context, err, "cleaning SCEP refs") } + var hasSetupExpItems bool if m.AwaitingConfiguration { // Enqueue setup experience items and mark the host as being in setup experience - _, err := svc.ds.EnqueueSetupExperienceItems(r.Context, r.ID, info.TeamID) + hasSetupExpItems, err = svc.ds.EnqueueSetupExperienceItems(r.Context, r.ID, info.TeamID) if err != nil { return ctxerr.Wrap(r.Context, err, "queueing setup experience tasks") } - } return svc.mdmLifecycle.Do(r.Context, mdmlifecycle.HostOptions{ - Action: mdmlifecycle.HostActionTurnOn, - Platform: info.Platform, - UUID: r.ID, - EnrollReference: r.Params[mobileconfig.FleetEnrollReferenceKey], + Action: mdmlifecycle.HostActionTurnOn, + Platform: info.Platform, + UUID: r.ID, + EnrollReference: r.Params[mobileconfig.FleetEnrollReferenceKey], + HasSetupExperienceItems: hasSetupExpItems, }) } diff --git a/server/service/integration_mdm_dep_test.go b/server/service/integration_mdm_dep_test.go index 249dddb185..bf46168b94 100644 --- a/server/service/integration_mdm_dep_test.go +++ b/server/service/integration_mdm_dep_test.go @@ -121,12 +121,33 @@ func (s *integrationMDMTestSuite) TestDEPEnrollReleaseDeviceGlobal() { s.enableABM("fleet_ade_test") + // add a setup experience script to run for no team + extraArgs := make(map[string][]string) + body, headers := generateNewScriptMultipartRequest(t, + "script.sh", []byte(`echo "hello"`), s.token, extraArgs) + s.DoRawWithHeaders("POST", "/api/latest/fleet/setup_experience/script", body.Bytes(), http.StatusOK, headers) + + // test manual and automatic release with the new setup experience flow + for _, enableReleaseManually := range []bool{false, true} { + t.Run(fmt.Sprintf("enableReleaseManually=%t;new_flow", enableReleaseManually), func(t *testing.T) { + s.runDEPEnrollReleaseDeviceTest(t, globalDevice, enableReleaseManually, nil, "I1", false) + }) + } // test manual and automatic release with the old worker flow for _, enableReleaseManually := range []bool{false, true} { - t.Run(fmt.Sprintf("enableReleaseManually=%t", enableReleaseManually), func(t *testing.T) { + t.Run(fmt.Sprintf("enableReleaseManually=%t;old_flow", enableReleaseManually), func(t *testing.T) { s.runDEPEnrollReleaseDeviceTest(t, globalDevice, enableReleaseManually, nil, "I1", true) }) } + + // remove the setup experience script, run the new setup experience flow when + // there is no setup experience item to process (so it is bypassed) + s.Do("DELETE", "/api/latest/fleet/setup_experience/script", nil, http.StatusOK) + for _, enableReleaseManually := range []bool{false, true} { + t.Run(fmt.Sprintf("enableReleaseManually=%t;bypass_flow", enableReleaseManually), func(t *testing.T) { + s.runDEPEnrollReleaseDeviceTest(t, globalDevice, enableReleaseManually, nil, "I1", false) + }) + } } func (s *integrationMDMTestSuite) TestDEPEnrollReleaseDeviceTeam() { @@ -211,12 +232,35 @@ func (s *integrationMDMTestSuite) TestDEPEnrollReleaseDeviceTeam() { // enable FileVault s.Do("PATCH", "/api/latest/fleet/mdm/apple/settings", json.RawMessage([]byte(fmt.Sprintf(`{"enable_disk_encryption":true,"team_id":%d}`, tm.ID))), http.StatusNoContent) + // add a setup experience script to run for this team + extraArgs := map[string][]string{ + "team_id": {fmt.Sprintf("%d", tm.ID)}, + } + body, headers := generateNewScriptMultipartRequest(t, + "script.sh", []byte(`echo "hello"`), s.token, extraArgs) + s.DoRawWithHeaders("POST", "/api/latest/fleet/setup_experience/script", body.Bytes(), http.StatusOK, headers) + + // test manual and automatic release with the new setup experience flow + for _, enableReleaseManually := range []bool{false, true} { + t.Run(fmt.Sprintf("enableReleaseManually=%t;new_flow", enableReleaseManually), func(t *testing.T) { + s.runDEPEnrollReleaseDeviceTest(t, teamDevice, enableReleaseManually, &tm.ID, "I2", false) + }) + } // test manual and automatic release with the old worker flow for _, enableReleaseManually := range []bool{false, true} { - t.Run(fmt.Sprintf("enableReleaseManually=%t", enableReleaseManually), func(t *testing.T) { + t.Run(fmt.Sprintf("enableReleaseManually=%t;old_flow", enableReleaseManually), func(t *testing.T) { s.runDEPEnrollReleaseDeviceTest(t, teamDevice, enableReleaseManually, &tm.ID, "I2", true) }) } + + // remove the setup experience script, run the new setup experience flow when + // there is no setup experience item to process (so it is bypassed) + s.Do("DELETE", "/api/latest/fleet/setup_experience/script", nil, http.StatusOK, "team_id", fmt.Sprint(tm.ID)) + for _, enableReleaseManually := range []bool{false, true} { + t.Run(fmt.Sprintf("enableReleaseManually=%t;bypass_flow", enableReleaseManually), func(t *testing.T) { + s.runDEPEnrollReleaseDeviceTest(t, teamDevice, enableReleaseManually, &tm.ID, "I2", false) + }) + } } func (s *integrationMDMTestSuite) TestDEPEnrollReleaseIphoneTeam() { @@ -286,6 +330,11 @@ func (s *integrationMDMTestSuite) TestDEPEnrollReleaseIphoneTeam() { func (s *integrationMDMTestSuite) runDEPEnrollReleaseDeviceTest(t *testing.T, device godep.Device, enableReleaseManually bool, teamID *uint, customProfileIdent string, useOldFleetdFlow bool) { ctx := context.Background() + var isIphone bool + if device.DeviceFamily == "iPhone" { + isIphone = true + } + // set the enable release device manually option payload := map[string]any{ "enable_release_device_manually": enableReleaseManually, @@ -359,15 +408,22 @@ func (s *integrationMDMTestSuite) runDEPEnrollReleaseDeviceTest(t *testing.T, de // enroll the host depURLToken := loadEnrollmentProfileDEPToken(t, s.ds) mdmDevice := mdmtest.NewTestMDMClientAppleDEP(s.server.URL, depURLToken) - var isIphone bool - if device.DeviceFamily == "iPhone" { + if isIphone { mdmDevice.Model = "iPhone 14,6" - isIphone = true } mdmDevice.SerialNumber = device.SerialNumber err := mdmDevice.Enroll() require.NoError(t, err) + // check if it has setup experience items or not + hasSetupExpItems := true + _, err = s.ds.GetHostAwaitingConfiguration(ctx, mdmDevice.UUID) + if fleet.IsNotFound(err) { + hasSetupExpItems = false + } else if err != nil { + require.NoError(t, err) + } + // run the worker to process the DEP enroll request s.runWorker() // run the cron to assign configuration profiles @@ -525,8 +581,13 @@ func (s *integrationMDMTestSuite) runDEPEnrollReleaseDeviceTest(t *testing.T, de b, err := io.ReadAll(res.Body) require.NoError(t, err) require.NoError(t, json.Unmarshal(b, &orbitConfigResp)) - // should be notified of the setup experience flow - require.False(t, orbitConfigResp.Notifications.RunSetupExperience) + if hasSetupExpItems { + // should be notified of the setup experience flow + require.True(t, orbitConfigResp.Notifications.RunSetupExperience) + } else { + // should bypass the setup experience flow + require.False(t, orbitConfigResp.Notifications.RunSetupExperience) + } if enableReleaseManually { // get the worker's pending job from the future, there should not be any @@ -537,7 +598,7 @@ func (s *integrationMDMTestSuite) runDEPEnrollReleaseDeviceTest(t *testing.T, de return } - if useOldFleetdFlow { + if useOldFleetdFlow || !hasSetupExpItems { // there should be a Release Device pending job pending, err := s.ds.GetQueuedJobs(ctx, 2, time.Now().UTC().Add(time.Minute)) require.NoError(t, err) @@ -574,6 +635,12 @@ func (s *integrationMDMTestSuite) runDEPEnrollReleaseDeviceTest(t *testing.T, de require.NoError(t, err) require.Len(t, pending, 0) + // mark the setup experience script as done + mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error { + _, err := q.ExecContext(ctx, `UPDATE setup_experience_status_results SET status = 'success' WHERE host_uuid = ?`, mdmDevice.UUID) + return err + }) + // call the /status endpoint to automatically release the host var statusResp getOrbitSetupExperienceStatusResponse s.DoJSON("POST", "/api/fleet/orbit/setup_experience/status", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *enrolledHost.OrbitNodeKey)), http.StatusOK, &statusResp) diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go index d8d2f6390d..a32b66b742 100644 --- a/server/service/integration_mdm_test.go +++ b/server/service/integration_mdm_test.go @@ -677,6 +677,14 @@ func (s *integrationMDMTestSuite) TearDownTest() { _, err := tx.ExecContext(ctx, "DELETE FROM vpp_tokens;") return err }) + mysql.ExecAdhocSQL(t, s.ds, func(tx sqlx.ExtContext) error { + _, err := tx.ExecContext(ctx, "DELETE FROM setup_experience_status_results;") + return err + }) + mysql.ExecAdhocSQL(t, s.ds, func(tx sqlx.ExtContext) error { + _, err := tx.ExecContext(ctx, "DELETE FROM setup_experience_scripts;") + return err + }) } func (s *integrationMDMTestSuite) mockDEPResponse(orgName string, handler http.Handler) { diff --git a/server/service/orbit.go b/server/service/orbit.go index df75ce21c5..e8e622a327 100644 --- a/server/service/orbit.go +++ b/server/service/orbit.go @@ -249,15 +249,13 @@ func (svc *Service) GetOrbitConfig(ctx context.Context) (fleet.OrbitConfig, erro notifs.RunSetupExperience = true } - if inSetupAssistant || fleet.IsNotFound(err) { - // If the client is running a fleetd that doesn't support setup experience, or if no - // software/script has been configured for setup experience, then we should fall back to - // the "old way" of releasing the device. We do an additional check for - // !inSetupAssistant to prevent enqueuing a new job every time the /config - // endpoint is hit. + if inSetupAssistant { + // If the client is running a fleetd that doesn't support setup + // experience, then we should fall back to the "old way" of releasing + // the device. mp, ok := capabilities.FromContext(ctx) - if !ok || !mp.Has(fleet.CapabilitySetupExperience) || !inSetupAssistant { - level.Debug(svc.logger).Log("msg", "host doesn't support setup experience or no setup experience configured, falling back to worker-based device release", "host_uuid", host.UUID) + if !ok || !mp.Has(fleet.CapabilitySetupExperience) { + level.Debug(svc.logger).Log("msg", "host doesn't support setup experience, falling back to worker-based device release", "host_uuid", host.UUID) if err := svc.processReleaseDeviceForOldFleetd(ctx, host); err != nil { return fleet.OrbitConfig{}, err } @@ -521,7 +519,7 @@ func (svc *Service) processReleaseDeviceForOldFleetd(ctx context.Context, host * // Enroll reference arg is not used in the release device task, passing empty string. if err := worker.QueueAppleMDMJob(ctx, svc.ds, svc.logger, worker.AppleMDMPostDEPReleaseDeviceTask, - host.UUID, host.Platform, host.TeamID, "", bootstrapCmdUUID, acctConfigCmdUUID); err != nil { + host.UUID, host.Platform, host.TeamID, "", false, bootstrapCmdUUID, acctConfigCmdUUID); err != nil { return ctxerr.Wrap(ctx, err, "queue Apple Post-DEP release device job") } } diff --git a/server/worker/apple_mdm.go b/server/worker/apple_mdm.go index 01ac59ea79..235a3a7333 100644 --- a/server/worker/apple_mdm.go +++ b/server/worker/apple_mdm.go @@ -50,12 +50,13 @@ func (a *AppleMDM) Name() string { // appleMDMArgs is the payload for the Apple MDM job. type appleMDMArgs struct { - Task AppleMDMTask `json:"task"` - HostUUID string `json:"host_uuid"` - TeamID *uint `json:"team_id,omitempty"` - EnrollReference string `json:"enroll_reference,omitempty"` - EnrollmentCommands []string `json:"enrollment_commands,omitempty"` - Platform string `json:"platform,omitempty"` + Task AppleMDMTask `json:"task"` + HostUUID string `json:"host_uuid"` + TeamID *uint `json:"team_id,omitempty"` + EnrollReference string `json:"enroll_reference,omitempty"` + EnrollmentCommands []string `json:"enrollment_commands,omitempty"` + Platform string `json:"platform,omitempty"` + UseWorkerDeviceRelease bool `json:"use_worker_device_release,omitempty"` } // Run executes the apple_mdm job. @@ -163,9 +164,10 @@ func (a *AppleMDM) runPostDEPEnrollment(ctx context.Context, args appleMDMArgs) } } - // proceed to release the device only if it is not a macos, as those are - // released via the setup experience flow. - if !isMacOS(args.Platform) { + // proceed to release the device if it is not a macos, as those are released + // via the setup experience flow, or if we were told to use the worker based + // release. + if !isMacOS(args.Platform) || args.UseWorkerDeviceRelease { var manualRelease bool if args.TeamID == nil { ac, err := a.Datastore.AppConfig(ctx) @@ -187,7 +189,7 @@ func (a *AppleMDM) runPostDEPEnrollment(ctx context.Context, args appleMDMArgs) // be final and same for MDM profiles of that host; it means the DEP // enrollment process is done and the device can be released. if err := QueueAppleMDMJob(ctx, a.Datastore, a.Log, AppleMDMPostDEPReleaseDeviceTask, - args.HostUUID, args.Platform, args.TeamID, args.EnrollReference, awaitCmdUUIDs...); err != nil { + args.HostUUID, args.Platform, args.TeamID, args.EnrollReference, false, awaitCmdUUIDs...); err != nil { return ctxerr.Wrap(ctx, err, "queue Apple Post-DEP release device job") } } @@ -198,10 +200,11 @@ func (a *AppleMDM) runPostDEPEnrollment(ctx context.Context, args appleMDMArgs) // This job is deprecated for macos because releasing devices is now done via // the orbit endpoint /setup_experience/status that is polled by a swift dialog -// UI window during the setup process, and automatically releases the device -// once all pending setup tasks are done. However, it must remain implemented -// for iOS and iPadOS and in case there are such jobs to process after a Fleet -// migration to a new version. +// UI window during the setup process (unless there are no setup experience +// items, in which case this worker job is used), and automatically releases +// the device once all pending setup tasks are done. However, it must remain +// implemented for iOS and iPadOS and in case there are such jobs to process +// after a Fleet migration to a new version. func (a *AppleMDM) runPostDEPReleaseDevice(ctx context.Context, args appleMDMArgs) error { // Edge cases: // - if the device goes offline for a long time, should we go ahead and @@ -355,6 +358,7 @@ func QueueAppleMDMJob( platform string, teamID *uint, enrollReference string, + useWorkerDeviceRelease bool, enrollmentCommandUUIDs ...string, ) error { attrs := []interface{}{ @@ -373,12 +377,13 @@ func QueueAppleMDMJob( level.Info(logger).Log(attrs...) args := &appleMDMArgs{ - Task: task, - HostUUID: hostUUID, - TeamID: teamID, - EnrollReference: enrollReference, - EnrollmentCommands: enrollmentCommandUUIDs, - Platform: platform, + Task: task, + HostUUID: hostUUID, + TeamID: teamID, + EnrollReference: enrollReference, + EnrollmentCommands: enrollmentCommandUUIDs, + Platform: platform, + UseWorkerDeviceRelease: useWorkerDeviceRelease, } // the release device task is always added with a delay diff --git a/server/worker/apple_mdm_test.go b/server/worker/apple_mdm_test.go index 8b497379ab..f27aa32bf1 100644 --- a/server/worker/apple_mdm_test.go +++ b/server/worker/apple_mdm_test.go @@ -141,7 +141,7 @@ func TestAppleMDM(t *testing.T) { // create a host and enqueue the job h := createEnrolledHost(t, 1, nil, true) - err := QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", nil, "") + err := QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", nil, "", false) require.NoError(t, err) // run the worker, should mark the job as done @@ -171,7 +171,7 @@ func TestAppleMDM(t *testing.T) { // create a host and enqueue the job h := createEnrolledHost(t, 1, nil, true) - err := QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMTask("no-such-task"), h.UUID, "darwin", nil, "") + err := QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMTask("no-such-task"), h.UUID, "darwin", nil, "", false) require.NoError(t, err) // run the worker, should mark the job as failed @@ -204,7 +204,7 @@ func TestAppleMDM(t *testing.T) { w.Register(mdmWorker) // use "" instead of "darwin" as platform to test a queued job after the upgrade to iOS/iPadOS support. - err := QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "", nil, "") + err := QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "", nil, "", false) require.NoError(t, err) // run the worker, should succeed @@ -239,7 +239,7 @@ func TestAppleMDM(t *testing.T) { w := NewWorker(ds, nopLog) w.Register(mdmWorker) - err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", nil, "") + err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", nil, "", false) require.NoError(t, err) // run the worker, should succeed @@ -281,7 +281,7 @@ func TestAppleMDM(t *testing.T) { w := NewWorker(ds, nopLog) w.Register(mdmWorker) - err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", nil, "") + err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", nil, "", false) require.NoError(t, err) // run the worker, should succeed @@ -330,7 +330,7 @@ func TestAppleMDM(t *testing.T) { w := NewWorker(ds, nopLog) w.Register(mdmWorker) - err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", &tm.ID, "") + err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", &tm.ID, "", false) require.NoError(t, err) // run the worker, should succeed @@ -380,7 +380,7 @@ func TestAppleMDM(t *testing.T) { w := NewWorker(ds, nopLog) w.Register(mdmWorker) - err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", &tm.ID, "") + err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", &tm.ID, "", false) require.NoError(t, err) // run the worker, should succeed @@ -418,7 +418,7 @@ func TestAppleMDM(t *testing.T) { w := NewWorker(ds, nopLog) w.Register(mdmWorker) - err := QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", nil, "abcd") + err := QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", nil, "abcd", false) require.NoError(t, err) // run the worker, should succeed @@ -461,7 +461,7 @@ func TestAppleMDM(t *testing.T) { w := NewWorker(ds, nopLog) w.Register(mdmWorker) - err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", nil, idpAcc.UUID) + err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", nil, idpAcc.UUID, false) require.NoError(t, err) // run the worker, should succeed @@ -514,7 +514,7 @@ func TestAppleMDM(t *testing.T) { w := NewWorker(ds, nopLog) w.Register(mdmWorker) - err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", &tm.ID, idpAcc.UUID) + err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", &tm.ID, idpAcc.UUID, false) require.NoError(t, err) // run the worker, should succeed @@ -548,7 +548,7 @@ func TestAppleMDM(t *testing.T) { w := NewWorker(ds, nopLog) w.Register(mdmWorker) - err := QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostManualEnrollmentTask, h.UUID, "darwin", nil, "") + err := QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostManualEnrollmentTask, h.UUID, "darwin", nil, "", false) require.NoError(t, err) // run the worker, should succeed @@ -564,4 +564,40 @@ func TestAppleMDM(t *testing.T) { require.Empty(t, jobs) require.ElementsMatch(t, []string{"InstallEnterpriseApplication"}, getEnqueuedCommandTypes(t)) }) + + t.Run("use worker for automatic release", func(t *testing.T) { + mysql.SetTestABMAssets(t, ds, testOrgName) + defer mysql.TruncateTables(t, ds) + + h := createEnrolledHost(t, 1, nil, true) + + mdmWorker := &AppleMDM{ + Datastore: ds, + Log: nopLog, + Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}), + } + w := NewWorker(ds, nopLog) + w.Register(mdmWorker) + + err := QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", nil, "", true) + require.NoError(t, err) + + // run the worker, should succeed + err = w.ProcessJobs(ctx) + require.NoError(t, err) + + // ensure the job's not_before allows it to be returned if it were to run + // again + time.Sleep(time.Second) + + require.ElementsMatch(t, []string{"InstallEnterpriseApplication"}, getEnqueuedCommandTypes(t)) + + // the release device job got enqueued + jobs, err := ds.GetQueuedJobs(ctx, 1, time.Now().Add(time.Minute)) // release job is always added with a delay + require.NoError(t, err) + require.Len(t, jobs, 1) + require.Equal(t, fleet.JobStateQueued, jobs[0].State) + require.Equal(t, appleMDMJobName, jobs[0].Name) + require.Contains(t, string(*jobs[0].Args), AppleMDMPostDEPReleaseDeviceTask) + }) } From 806580bd7f1f2d4511c6b82d4f25f0bc724b48ed Mon Sep 17 00:00:00 2001 From: Tim Lee Date: Wed, 27 Nov 2024 12:39:25 -0500 Subject: [PATCH 52/92] Linux agent LVM volume detection on older Ubuntu versions (#24193) --- orbit/pkg/lvm/lvm.go | 5 ++ orbit/pkg/lvm/lvm_test.go | 136 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) diff --git a/orbit/pkg/lvm/lvm.go b/orbit/pkg/lvm/lvm.go index c662d80d78..ea55db731c 100644 --- a/orbit/pkg/lvm/lvm.go +++ b/orbit/pkg/lvm/lvm.go @@ -12,6 +12,7 @@ type BlockDevice struct { Name string `json:"name"` Type string `json:"type"` Mountpoints []string `json:"mountpoints"` + Mountpoint string `json:"mountpoint"` // on older ubuntu versions Children []BlockDevice `json:"children,omitempty"` } @@ -68,6 +69,10 @@ func findRootPartition(devices []BlockDevice) *BlockDevice { // searchForRoot recursively checks each device and its children // to find the one mounted at "/". func searchForRoot(device BlockDevice) *BlockDevice { + if device.Mountpoint == "/" { + return &device + } + for _, mountpoint := range device.Mountpoints { if mountpoint == "/" { return &device diff --git a/orbit/pkg/lvm/lvm_test.go b/orbit/pkg/lvm/lvm_test.go index 73058caf09..dde1a707dd 100644 --- a/orbit/pkg/lvm/lvm_test.go +++ b/orbit/pkg/lvm/lvm_test.go @@ -295,6 +295,134 @@ var testJsonFedora = `{ ] }` +var testJsonOther = `{ + "blockdevices": [ + { + "name": "loop0", + "maj:min": "7:0", + "rm": false, + "size": "4K", + "ro": true, + "type": "loop", + "mountpoint": "/snap/bare/5" + }, + { + "name": "loop1", + "maj:min": "7:1", + "rm": false, + "size": "346.3M", + "ro": true, + "type": "loop", + "mountpoint": "/snap/gnome-3-38-2004/119" + }, + { + "name": "loop2", + "maj:min": "7:2", + "rm": false, + "size": "49.9M", + "ro": true, + "type": "loop", + "mountpoint": "/snap/snapd/18357" + }, + { + "name": "loop3", + "maj:min": "7:3", + "rm": false, + "size": "46M", + "ro": true, + "type": "loop", + "mountpoint": "/snap/snap-store/638" + }, + { + "name": "loop4", + "maj:min": "7:4", + "rm": false, + "size": "63.3M", + "ro": true, + "type": "loop", + "mountpoint": "/snap/core20/1828" + }, + { + "name": "loop5", + "maj:min": "7:5", + "rm": false, + "size": "91.7M", + "ro": true, + "type": "loop", + "mountpoint": "/snap/gtk-common-themes/1535" + }, + { + "name": "nvme0n1", + "maj:min": "259:0", + "rm": false, + "size": "953.9G", + "ro": false, + "type": "disk", + "mountpoint": null, + "children": [ + { + "name": "nvme0n1p1", + "maj:min": "259:1", + "rm": false, + "size": "512M", + "ro": false, + "type": "part", + "mountpoint": "/boot/efi" + }, + { + "name": "nvme0n1p2", + "maj:min": "259:2", + "rm": false, + "size": "1.4G", + "ro": false, + "type": "part", + "mountpoint": "/boot" + }, + { + "name": "nvme0n1p3", + "maj:min": "259:3", + "rm": false, + "size": "952G", + "ro": false, + "type": "part", + "mountpoint": null, + "children": [ + { + "name": "nvme0n1p3_crypt", + "maj:min": "253:0", + "rm": false, + "size": "951.9G", + "ro": false, + "type": "crypt", + "mountpoint": null, + "children": [ + { + "name": "vgubuntu-root", + "maj:min": "253:1", + "rm": false, + "size": "930.4G", + "ro": false, + "type": "lvm", + "mountpoint": "/" + }, + { + "name": "vgubuntu-swap_1", + "maj:min": "253:2", + "rm": false, + "size": "976M", + "ro": false, + "type": "lvm", + "mountpoint": "[SWAP]" + } + ] + } + ] + } + ] + } + ] +}` + func TestFindRootDisk(t *testing.T) { var input bytes.Buffer _, err := input.WriteString(testJsonUbuntu) @@ -311,6 +439,14 @@ func TestFindRootDisk(t *testing.T) { output, err = rootDiskFromJson(input) assert.NoError(t, err) assert.Equal(t, "/dev/nvme0n1p3", output) + + input = bytes.Buffer{} + _, err = input.WriteString(testJsonOther) + assert.NoError(t, err) + + output, err = rootDiskFromJson(input) + assert.NoError(t, err) + assert.Equal(t, "/dev/nvme0n1p3", output) } func TestErrorNoMountPoint(t *testing.T) { From 99d51b32f942921e0e9d42d15e4da269ff9e5e91 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 27 Nov 2024 15:31:47 -0300 Subject: [PATCH 53/92] Update versions of fleetd components in Fleet's TUF [automated] (#24223) 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 4582259cd9..08a22d79b8 100644 --- a/orbit/TUF.md +++ b/orbit/TUF.md @@ -7,8 +7,8 @@ Following are the currently deployed versions of fleetd components on the `stabl | Component\OS | macOS | Linux | Windows | Linux (arm64) | |--------------|--------------|--------|---------|---------------| -| orbit | 1.35.0 | 1.35.0 | 1.35.0 | 1.35.0 | -| desktop | 1.35.0 | 1.35.0 | 1.35.0 | 1.35.0 | +| orbit | 1.36.0 | 1.36.0 | 1.36.0 | 1.36.0 | +| desktop | 1.36.0 | 1.36.0 | 1.36.0 | 1.36.0 | | osqueryd | 5.14.1 | 5.14.1 | 5.14.1 | 5.14.1 | | nudge | 1.1.10.81462 | - | - | - | | swiftDialog | 2.1.0 | - | - | - | From 8dbfbad16731c8945372461853037d3f30f8f8aa Mon Sep 17 00:00:00 2001 From: Lucas Manuel Rodriguez Date: Wed, 27 Nov 2024 16:13:54 -0300 Subject: [PATCH 54/92] Add github action to automate timestamp update (#24074) #23042 --- .github/workflows/tuf-update-timestamp.yaml | 59 +++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 .github/workflows/tuf-update-timestamp.yaml diff --git a/.github/workflows/tuf-update-timestamp.yaml b/.github/workflows/tuf-update-timestamp.yaml new file mode 100644 index 0000000000..692872aadd --- /dev/null +++ b/.github/workflows/tuf-update-timestamp.yaml @@ -0,0 +1,59 @@ +# This workflow update the timestamp of the TUF repository at https://tuf.fleetctl.com +name: Update TUF timestamp + +on: + schedule: + - cron: "0 14 * * TUE" # Every Tuesday at 2 PM UTC + workflow_dispatch: # Manual + +defaults: + run: + # fail-fast using bash -eo pipefail. See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference + shell: bash + +env: + AWS_REGION: us-east-1 + AWS_IAM_ROLE: arn:aws:iam::142412512209:role/github-actions-role + +permissions: + id-token: write # This is required for aws-actions/configure-aws-credentials + +jobs: + tuf-update-timestamp: + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + with: + egress-policy: audit + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@67fbcbb121271f7775d2e7715933280b06314838 # v1.7.0 + with: + role-to-assume: ${{ env.AWS_IAM_ROLE }} + aws-region: ${{ env.AWS_REGION }} + + - name: Install fleetctl + run: npm install -g fleetctl + + - name: Pull metadata files + run: | + mkdir -p keys repository staged + aws s3 cp s3://fleet-tuf-repo/timestamp.json ./repository/timestamp.json + aws s3 cp s3://fleet-tuf-repo/snapshot.json ./repository/snapshot.json + aws s3 cp s3://fleet-tuf-repo/targets.json ./repository/targets.json + aws s3 cp s3://fleet-tuf-repo/root.json ./repository/root.json + cat ./repository/timestamp.json + + - name: Update timestamp + env: + BASE64_ENCRYPTED_TIMESTAMP_KEY_CONTENTS: ${{ secrets.BASE64_ENCRYPTED_TIMESTAMP_KEY }} + FLEET_TIMESTAMP_PASSPHRASE: ${{ secrets.TUF_TIMESTAMP_PASSPHRASE }} + run: | + echo "$BASE64_ENCRYPTED_TIMESTAMP_KEY_CONTENTS" | base64 -d > ./keys/timestamp.json + fleetctl updates timestamp --path . + + - name: Push timestamp.json + run: | + cat ./repository/timestamp.json + aws s3 cp ./repository/timestamp.json s3://fleet-tuf-repo/timestamp.json From 459faf45d76da0165a73b6d3fc91ef807244e2b0 Mon Sep 17 00:00:00 2001 From: Luke Heath Date: Wed, 27 Nov 2024 13:42:04 -0600 Subject: [PATCH 55/92] Bump OS update deadline to not be Monday after holiday (#24225) --- it-and-security/teams/workstations-canary.yml | 2 +- it-and-security/teams/workstations.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/it-and-security/teams/workstations-canary.yml b/it-and-security/teams/workstations-canary.yml index 136f620f3c..43fdb0a143 100644 --- a/it-and-security/teams/workstations-canary.yml +++ b/it-and-security/teams/workstations-canary.yml @@ -92,7 +92,7 @@ controls: enable_end_user_authentication: true macos_setup_assistant: null macos_updates: - deadline: "2024-12-02" + deadline: "2024-12-04" minimum_version: "15.1.1" windows_settings: custom_settings: diff --git a/it-and-security/teams/workstations.yml b/it-and-security/teams/workstations.yml index 9d92c61051..6f6c4bfc8a 100644 --- a/it-and-security/teams/workstations.yml +++ b/it-and-security/teams/workstations.yml @@ -66,7 +66,7 @@ controls: - app_store_id: '803453959' # Slack Desktop - app_store_id: '1333542190' # 1Password 7 Desktop macos_updates: - deadline: "2024-12-02" + deadline: "2024-12-04" minimum_version: "15.1.1" windows_settings: custom_settings: null From 0654172fa2fd4aa7cbf93a59b360b7eb4dbe3daf Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 27 Nov 2024 13:43:23 -0600 Subject: [PATCH 56/92] Website: Add app library & app details page (#24205) Related to: #23792 Changes: - Added /app-library, a page that displays information about Fleet-maintained apps - Added the app details page (/app-library/{app identifier}), a page that gives users detailed information about a single Fleet-maintained app - Updated the build-static-content script to add information about Fleet-maintained apps to the website's configuration. --- website/api/controllers/view-app-details.js | 60 +++ website/api/controllers/view-app-library.js | 33 ++ .../images/app-icon-1password-60x60@2x.png | Bin 0 -> 3863 bytes ...app-icon-adobe-acrobat-reader-60x60@2x.png | Bin 0 -> 1206 bytes .../images/app-icon-box-drive-60x60@2x.png | Bin 0 -> 1117 bytes .../app-icon-brave-browser-60x60@2x.png | Bin 0 -> 1966 bytes .../app-icon-cloudflare-warp-60x60@2x.png | Bin 0 -> 1016 bytes .../images/app-icon-docker-60x60@2x.png | Bin 0 -> 977 bytes .../assets/images/app-icon-figma-60x60@2x.png | Bin 0 -> 1372 bytes .../images/app-icon-firefox-60x60@2x.png | Bin 0 -> 3915 bytes .../app-icon-google-chrome-60x60@2x.png | Bin 0 -> 3144 bytes .../app-icon-microsoft-edge-60x60@2x.png | Bin 0 -> 3365 bytes .../app-icon-microsoft-excel-60x60@2x.png | Bin 0 -> 1629 bytes .../app-icon-microsoft-teams-60x60@2x.png | Bin 0 -> 1924 bytes .../app-icon-microsoft-word-60x60@2x.png | Bin 0 -> 2788 bytes .../images/app-icon-notion-60x60@2x.png | Bin 0 -> 1232 bytes .../images/app-icon-postman-60x60@2x.png | Bin 0 -> 1068 bytes .../assets/images/app-icon-slack-60x60@2x.png | Bin 0 -> 1472 bytes .../images/app-icon-teamviewer-60x60@2x.png | Bin 0 -> 2338 bytes .../app-icon-visual-studio-code-60x60@2x.png | Bin 0 -> 1306 bytes .../images/app-icon-whatsapp-60x60@2x.png | Bin 0 -> 1538 bytes .../assets/images/app-icon-zoom-60x60@2x.png | Bin 0 -> 3531 bytes website/assets/js/pages/app-details.page.js | 47 ++ website/assets/js/pages/app-library.page.js | 25 ++ website/assets/styles/importer.less | 2 + website/assets/styles/pages/app-details.less | 418 ++++++++++++++++++ website/assets/styles/pages/app-library.less | 205 +++++++++ website/config/policies.js | 2 + website/config/routes.js | 14 +- website/scripts/build-static-content.js | 41 ++ website/views/layouts/layout.ejs | 2 + website/views/pages/app-details.ejs | 88 ++++ website/views/pages/app-library.ejs | 64 +++ 33 files changed, 1000 insertions(+), 1 deletion(-) create mode 100644 website/api/controllers/view-app-details.js create mode 100644 website/api/controllers/view-app-library.js create mode 100644 website/assets/images/app-icon-1password-60x60@2x.png create mode 100644 website/assets/images/app-icon-adobe-acrobat-reader-60x60@2x.png create mode 100644 website/assets/images/app-icon-box-drive-60x60@2x.png create mode 100644 website/assets/images/app-icon-brave-browser-60x60@2x.png create mode 100644 website/assets/images/app-icon-cloudflare-warp-60x60@2x.png create mode 100644 website/assets/images/app-icon-docker-60x60@2x.png create mode 100644 website/assets/images/app-icon-figma-60x60@2x.png create mode 100644 website/assets/images/app-icon-firefox-60x60@2x.png create mode 100644 website/assets/images/app-icon-google-chrome-60x60@2x.png create mode 100644 website/assets/images/app-icon-microsoft-edge-60x60@2x.png create mode 100644 website/assets/images/app-icon-microsoft-excel-60x60@2x.png create mode 100644 website/assets/images/app-icon-microsoft-teams-60x60@2x.png create mode 100644 website/assets/images/app-icon-microsoft-word-60x60@2x.png create mode 100644 website/assets/images/app-icon-notion-60x60@2x.png create mode 100644 website/assets/images/app-icon-postman-60x60@2x.png create mode 100644 website/assets/images/app-icon-slack-60x60@2x.png create mode 100644 website/assets/images/app-icon-teamviewer-60x60@2x.png create mode 100644 website/assets/images/app-icon-visual-studio-code-60x60@2x.png create mode 100644 website/assets/images/app-icon-whatsapp-60x60@2x.png create mode 100644 website/assets/images/app-icon-zoom-60x60@2x.png create mode 100644 website/assets/js/pages/app-details.page.js create mode 100644 website/assets/js/pages/app-library.page.js create mode 100644 website/assets/styles/pages/app-details.less create mode 100644 website/assets/styles/pages/app-library.less create mode 100644 website/views/pages/app-details.ejs create mode 100644 website/views/pages/app-library.ejs diff --git a/website/api/controllers/view-app-details.js b/website/api/controllers/view-app-details.js new file mode 100644 index 0000000000..e757164e3c --- /dev/null +++ b/website/api/controllers/view-app-details.js @@ -0,0 +1,60 @@ +module.exports = { + + + friendlyName: 'View app details', + + + description: 'Display "App details" page.', + + + inputs: { + appIdentifier: { + type: 'string', + required: true, + description: 'the identifier of an app in Fleet\'s maintained app library.', + example: '1password' + }, + }, + + exits: { + + success: { + viewTemplatePath: 'pages/app-details' + }, + + badConfig: { + responseType: 'badConfig' + }, + + notFound: { + responseType: 'notFound' + }, + + }, + + + fn: async function ({appIdentifier}) { + + if (!_.isObject(sails.config.builtStaticContent) || !_.isArray(sails.config.builtStaticContent.appLibrary) || !sails.config.builtStaticContent.appLibrary) { + throw {badConfig: 'builtStaticContent.appLibrary'}; + } + + let thisApp = _.find(sails.config.builtStaticContent.appLibrary, { identifier: appIdentifier }); + if (!thisApp) { + throw 'notFound'; + } + // FUTURE: make these better. + let pageTitleForMeta = thisApp.name + ' | Fleet app library'; + // let pageDescriptionForMeta = 'TODO' + + // Respond with view. + return { + thisApp, + // pageDescriptionForMeta, + pageTitleForMeta, + }; + + } + + +}; diff --git a/website/api/controllers/view-app-library.js b/website/api/controllers/view-app-library.js new file mode 100644 index 0000000000..1ea01f1e25 --- /dev/null +++ b/website/api/controllers/view-app-library.js @@ -0,0 +1,33 @@ +module.exports = { + + + friendlyName: 'View app library', + + + description: 'Display "App library" page.', + + + exits: { + + success: { + viewTemplatePath: 'pages/app-library' + }, + badConfig: { responseType: 'badConfig' }, + }, + + + fn: async function () { + + if (!_.isObject(sails.config.builtStaticContent) || !_.isArray(sails.config.builtStaticContent.appLibrary) || !sails.config.builtStaticContent.appLibrary) { + throw {badConfig: 'builtStaticContent.appLibrary'}; + } + + let allApps = sails.config.builtStaticContent.appLibrary; + allApps = _.sortBy(allApps, 'name'); + // Respond with view. + return {allApps}; + + } + + +}; diff --git a/website/assets/images/app-icon-1password-60x60@2x.png b/website/assets/images/app-icon-1password-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..99bf1fe9ba4f885b46c4b5882dfe87289fdf5428 GIT binary patch literal 3863 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

Mxws_#xkY<-5?kMHr1UF8D z07ranbVJPQr|H9FcF*=CVqe%8ULHJ!j`&V351q$Hd@KC%Vtvx`Ijj*bGCJ-9re^cx zc?g|{n(eMb&7|v4vmeeoxS>@r5$qUOagy&Y!8$6M!rz^0uWP`^xJ~H;&STtWxHRYF z&XS7u182-MlZnJ6AL@q5*UJ5Z%1s?Ixz^NcB{&xJsD3TS9Y_+pgP_v|xP)!y8!85- zK|FYK!kHX*I%kUR^GBzfA(6|S6E+HM5h7S5(wS0yCd9`jAjlQKfm&c0nrV+n zW<P($5BOq_j{}1BZF4TFvAZq5 zjqcpocu|erWse8#-LC;C)+X}=HvkL&S3jnhV0J97Lvv%HB6 zL>*R7>v#coW-(hot)eSDEEm8=Jb$3HFTl*}MfDS7wZ7_}HhG31yms3hLm*~JI4^BZ zVhG5y7xRuYz0Pw8%*rotSmFd59h&97UgOgyVL-Pk`Iy8^b zGfcudieVDs2qxmUiB(-IxB-K>F4C65z!f;epXrebNI1kIJy8Myi@0o!nbA9E)<7^$ zOc!hbRPNRp2pGgo>&vlnqGT3vg@so1e6ff>THOX(WJ>m$MO>h`OtI;GxWv_=bO#Y& zoD~;)u>bQyacQ>zpwhstwl25;Kp(_Fz!M;@PMZdvYHH z@&yPz)&*{k8z5j1Teq~TIfx|?a2*76c@Q^1z#xV%*W&CTu7Q9&Cs@RpPmhAyC^*FWY%3Ny zvCJIe(&sdOimZ1vGl)fs2%p+J=)fSBojXn2OF6rrC* zTFT%^ogYT+EA5c)%pJv9=Ln!`vrS1dIYLYqteOg?v|5njVFg=di>_o_nWh$dYTcCH zJYKAQC6}tJ!Q&VI9%5Hye}-fu;2USVe=^hK#il3SSnQ<4qI>@?$BTdVx3KkvZx^NT zf4P%bm*=ILZa!m_q=ivGemCR9P2sgWt**!E&KgtYctjZ?Rx(dLB~XYm?IB^P*lT?W zv|4s9v~L(JHW^U@fj-N#+`>??F0jEqEN`N;b-R?+gEt9v41ruKnGU$yGE`h2yId&{ z)MRmpKaXiETzC+f&mhJW)c)sw8gPgucDY3m-_bb4)`@bB>38uQ%OMu$E4m}1gG0PK zdHn+S(ttrM&Tg$(DzZ4l#$U3ZWa@H=U3WElsXIIzVvBbfBzpS;%_7#W)k>YVh}{J* zix|*&EjU=jBJ*D0=;^gY$f+?;;pFP)=kMg?>f_|= zS#B5bHZ8WxP2df+UM=ALTK?)`gB1XrFV~#BO}V<90bmtq;ralFBYC@y2LQhl^lu+d zS7JrS6ZjvUcn>+Tr2O8-9Qbf27Igss+0QFD<;0@$6RfI4100D}T>*ZQy@Ug?tiw;p z0S0VMM_2^j2F}E~&aio8`a-Ph0!!}`6tS)=Y`q2K#JX}=cmQMu0XXou9N2*!*nu6` zfnUmj9r$m8#aEi(lSObzDc$3vH87(OMFI_0!8B|h9oT^#*ny3}LM2JT1Q{qSl7xkC z0*kMhJ{2c=gEV^Dxop@Or(ADTlr#>F!25`zI&TeQ+R+moR-dyj7bpCQU`{o)?iZ16 z=j>rjX~j|K9Y%O}c5xh!Gg8GOGh({0U0*o}{-&jF@+#C?$rxOV)R^;SZM~}{`Yp2jwP@ztEiKx2>mkxOWi1<=Dh@k z1scjz>NeUs!?a^w%)otX_97C2!$@!(hvxh0zBR$|qAS9VVK~cK0Cy|m zRyQh$2{^>{c%-b;L2I;TIOI&gVl=XLZ63`;hC{0!%?4n?5ugV)R_<|Y3^_$f6L31x zPZA4uBw*gSfZ7{?*|4AXW3R+0Wv2PpL-yhS(-s&>4@QgWOwaBT{^eoqIBk`DEaBUMwRa5WCK z@w}e~#-h==0_PEp%ldmA%ov*tCPGqOvf^qnV+oe}l7xl{;8qFmzP@;|p-%%O+cD?)4t58NIgYYJ;CZV#4(~6k>k8J& z3j{uyIO`p jFX8=KtT5cUt`GhJz|!rci$Qd+00000NkvXXu0mjfyAmR7 literal 0 HcmV?d00001 diff --git a/website/assets/images/app-icon-brave-browser-60x60@2x.png b/website/assets/images/app-icon-brave-browser-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..53fe7548c40aa1b0009270985b9813df89bf42fe GIT binary patch literal 1966 zcmV;f2T}NmP)gDI{;^pe%f+?;;^gb%fh(*Ez_^|NsC0RRH4T z>ifirW0{`{!|7#%ss(}As5dVNc|CM0>qLlx&kpD(1|G%LBpK$-xzxw<9 z|8+6{-_QR=Dedp_>g@3U%CG;RaQ|u||4bSGQ49ak%Ky~A^7HopP&faVhyQe6|LEoa zt)l;ndjDKR|GBUKi%{n0?d9j~|J>LA%CP^7QU8ii|3@YN#k~K3Z2xFd|5hsW_4oh4 zp#Q+1%Lx3S0000GbW%=J01%KMpWokL@9)nbFwdYc5CE%C000JcNkl1T~7T!cha_d{RL#e{EXQzLLe9+e?w5xJ-cyABNq z7bSp>?cljEua%7!E=B81M zbx6&27xeqg}M-vMo?6#Wl>=y@T-d~oF?}_Q^yR= z7-wTg{0kmq51;3Od?8TAW3)VP2ET3xcb+XGz@i_1+jdY+F+P{p zL?+MU#w~T6Ru?t9g`e(8um~PV3{m>t&XEtoH5D1GCckD}Jp~q@4anNbP*9q#&eBNY z>G@=B1Cl$y#I2GVYZiGmM4{-AXwp2{rb?3A!Neui*64^`mJ~ZGSKQmWBFVkr$R$}i zng;Llob$M}xu@I*mUWfucF!wz+8CUGytrXZy$oa2EhUM_d%&_<=_p=YA6kzT9~`|4 zY%H&b8*~8K=|Xzx1wG{lhZ z4B~N*g=&3FMs2FDN5Lyq3Ty@Q#pcy=oa&X2$*duuweQShS^F!ULA zxm;vn>7>)sfB8XhR$JAf?06ZwXcWkO`o9l?8BM1zsoL2S6?ohJc+}%VtxzE&p{f#; zW80|57(jkcU47Ihgu6@&MvzhP@=c>Y>fcHfr=Ak7r(v+CJ{kMUmr`8bUlV@3Wp3}t z94 zG*2LXGP{(B_eRSxtuV~aHOE7yWiyu7ZH)?en3({eC>F_SYtwt8C)hGoA_783y}wpuWD zluEhj1m@A}K_#XmnBpkn>cQ=qE8yzD_#Y1Oyn&KhnC@Uyr-!w&MhI5!XYL@+17l=n zIe}db7^WHLsBLuybGzr9g5sTdG8mn=WwCkw)Pq?EZ=~`3r#CPU(0JU{Cm7X)?e+e|r} z*N!UBqg=GY)v_NWl>TZNOwX46>vamdV_hHo1$-#Qpopjbi~s-t07*qoM6N<$f@*r` Aw*UYD literal 0 HcmV?d00001 diff --git a/website/assets/images/app-icon-cloudflare-warp-60x60@2x.png b/website/assets/images/app-icon-cloudflare-warp-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3c38697db8ce0a5804bf9597c3ea0fec61900350 GIT binary patch literal 1016 zcmV*D0<;^pe%f+?<;^gY%=kMd^=j7z<;o|B4|NrxVAmZfe`mR9z@8kaU z=k<_0`}_R--^csUtM;H+`M;0!h%Wy3@$K*O>g@3QxLEl3`v3a#{_D^8uXE<-?fvJ~ z`p2F4xPtq_aQCQY`?E~)^Y;Js=ls^6{_xxV;kW$CgZHp>{L+*8z>xKrO8eKj{oJej zy=3z9_5S9=_orqONHhfi000eiQchC<5buzm-`~$*ARsWH>(6&40008-NkluBX4v=#l=qCbQN=DOuZvZ~!Um`hEdvn>2vNhA)3Am$tYHmnn45;bqW-Y8%lATs zaOh70a_VI?Py^G^QDwF-9}Y+4Qm!u<_FTegO=ZF@!q27h;5E5cFpvW?moLkKMo`E0zL-446?VG3jC*lKno zJjm}p0XT`~x4~Y1*n`+f)|sh+UjW{m@A5*)Qu}b&V%Bt#8K$1d+oQuww%X}%IGh9| zZ!TqUqH#JLZg;Af3mfOGw={U*5`mf&_ASogY41Ahi?G2E4!xbra}F%%2Cn<`8p5f^ z)%%n)GK1597+U_mSMSh(9)qj(9^?39d3@61E3nIZB?#1eKfo&voc4$8So7HA>CmXP z@0BLCc(WRhJG zkNkfA*INy1Si>5I^fatt4Qp7#8s?_q9}O3E1h%+CzrPDQ=3LytZqq8_C_9>lQ7Yo_ zzHJyr9hHsD2Cf%*LBlXBY8N{?UP&?P6^zu)+{SV}dF@D%+-28}_SIEhw5ngaS_Tqh mSIfRtw^4ppiH%#-^}&ChAp8v|zU@~40000*D0=;^gY$f`JER*La*o%F}p z`#E>-j!iHv$<*rA;{4R&{D7zQPH&r)0000CbW%=J0N)_*pO6q>&oB_r0W>-` z0008;Nkl`y!-i#PGdq4Re}frs?*M_<@5K*aT`SO8fT`_&o|gYb`bUBqk#YhUvk630KCZxeR_Hr#xCiSgaN>Bl1nXI%=O984K^)-v4DbLC z@OOk`cFEK-%%V%;0UqE1{#Rjrfo%VmE0Y?YUXufq`hx`6=>iQLRnAbNKk(||8sJ`G z-V-b%CVPO-#kA}J7UzPn569<1I=2nWONA)|Sn2>M{j92mnw7p}XZfz)M%SpP;JLU) z0qAG`rBGH1@>bAWd`|KVT;0lsdn~GjN-1J?;pi4+6;5w3SCR|Cxw5>}5z`Nwk{;eh zwfP~b;XJC(J2lMjz=9fvxYlJr4%fFVAHkAeqX6{tykX&P%L(~it@3)85|(q3$7v0l z=I|KavX+VNB~2RLTyfN8()X#*tSwl=ElrlXD>sJc;7wPscoI%Jav3}c3z~)>!2%?-4IbK`}U?O>IXoUF$%?9AL9w1uVF zLaR2g`Hylcp*<`OcDV!t7=E^P6EK1yHgJt#nHsnzFmMXD9yb47kwZJ!@Q<&QLkrlv z=U75lFi6(C-MrI(l*>(vc8y>i!J4p~;(I5-TChg2PPx6WrU|F~rcH?rt3hL}62LmL z%%^e8*l&=rc)AnwZ~5KgB?COb1Ka@a?-kfLEWmp9IO***=l)*o4!u5JWyjEQf<9i} z_s`=B_Kufsc=S1-aaw}KEKV3)MpEo9_7UAzJ%n?#*7G7mcT^cWI$1i}#R=V){s*n} zXxW}}w5<3XEqnbsOy?tgyg;{G$t(T%V5Qq%BtpIBhE5{+00000NkvXXu0mjflD+oC literal 0 HcmV?d00001 diff --git a/website/assets/images/app-icon-figma-60x60@2x.png b/website/assets/images/app-icon-figma-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a30d4e902425c37c2c51753701d39a6a9a87ce03 GIT binary patch literal 1372 zcmV-i1*7_jP)*D0<;^*q$-|yn(>f+?<;^gY&Y`cy|8ipTP96%+ zgBra4%wkr;M&LBRpljs zr6_NUTt$Hwo@y^mS*BM_Fhop@Pe@ojIOa_thEU9TOS5=Ovy7LpfPsX6fP*Vub4WT_ z%wt!xT2LosOo2WsopW)z9NynX=b%^kE&>cnvjN)%fx9=SPV_80(-GD3JQyMqPKFP z5#yk=EaJ_@_G1hb7rPnRVgoBHjvmWs!7fl<7q%FFdY4ag7Tv1 zF%UsRh!2KKtJi

uRg(*G|7|ICp&zb{2BBa8Z;Ak2BT*JL-@I=l(Lw2|HUX%7Jr} zVGhiZiExRz9Jo#{Nq{H9&BLMJboeSP0*Cs^@T3^rMuk_yVsNOR2v3N@J4AR=6kepk zrLZU*Dv{uQad@8uuZqL{j}y-6y3`;J0}XB$h0lrbxG20wgiE4ukqEU#ICmfd zFBHizT#3MYK!;(xdAI|pFkj}l1ehc9;e|br1w(G);qZA8D6o^BdajVcN$?pL&hX$R65PjyZC<^P6J8d-)Y~L@g$ob4 zcXPoS;qRp90g|V|{JSfiaqp1{ha4CZw3meN$k2G?C6^Zhr<_*=6^5zimp(%v174YG zcCj~pXAEVL3B0Xi%q&kaa`1>_Ot5@EhvsAW?>lQ|-l` zMzi(q5YDu^LpU)sebvW~;-I7~J9eb}pq0;gv}|VH&7)=6naa_!R--nbAKlO6W#h71 e-9J89HSIsfpg+`TKZ1q;0000CzCV) literal 0 HcmV?d00001 diff --git a/website/assets/images/app-icon-firefox-60x60@2x.png b/website/assets/images/app-icon-firefox-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..1ff3b7c35dec0ca605b7e65c2011ed7bd56b3270 GIT binary patch literal 3915 zcmV-R547-!P)*3?;;^gY$fh(<;pFP)=kMp|=j7z-z^t0RJ>60EPeR z97O-;L;p}4NfmVesQ~}60RN@{0BP>#8cF}G0sn>r|GxnNc=-Q-2LNyK-WF2--2wn~ z_0JAt)e&6(#R321K>xY{|Fr-XNyXU{SK=5=|K2+PqW}Pq`TyEB|5q6QEh+(E=o&@8 z|APhzR@wj70s>v+5>3nhd+SL)J+me`tpS(wGA)|_)iEtGp}+=n|IjKy9D>>+L-h6d0Hg0l7<>QA zBmc%9GZt+B>=*yD13e*$|F;kS_T2x!7y04s0-EFnX4)nre(h`||NQU-gy8@3jpidj z|L!d1O(pV1B>wUA%qV{U??dWZHvC#D^~>Dff3Wh1p4By5%^pejb`$^exy?Y9BPx&H zO-z=|{q3p80#C#Fw7=draEGO8lZ9b!EnSj4PU$`@-_;xc*vA}r#Rf#Q6Fj9GGMn;V zbyzTI{po1jLMi{?B=LP6_LC3elEXGoSu+>RRJw-jNk^fi*W`l_Q)s-s(a zkA;eHUP)&Ex+}_WDBsH*@V5=a)AT^Es@qD6|JQ}RBW!k9NX)M!-`^emsRJ>r^@geH zZI9iNp4gh($4s=pZQvlx(@t=@j zAkQEmFwZbb)5gyL01NL)L_t(&-p$(yR8&d8QRJf5d(SdWwOZ%0p@=+%RnIB_(P zlRX^jo&?s*1p$4Ms`S}#QPw4ae5UN{in_A2(%HjRu9W#XW3Rki8f+LFw2NJ=_c`a# zustzo7iTn8_HcP&8g_4wUZ=ReFjYPUlXl4!tUYDVUT3)nKCM@8Hykk?a<6XOtx{($ z(x z;ij7s;_dDOk3RU|li!-EyoJnG`_+{v2G`DYyv6=daNn@I0Vl)@_|7|-`<@9$EK)`C z23zroz;pWnmSuk&`Owh&-~wio=7r%t`8$qS%F`R353{X20k~p!zgl^+zYMtt@GuFB z6DIgu3A1s3loo5u>pI+eO}|zzk)K8kNdk;X+mE;ppETU5CJxKe>9M|lb{U>~4InmefWmntA6b3qp5-6M8p-|OriA_~cq(9%mPInMin{<; z%$hX|?6j*c=^Joy@PMFDf4}+{ZLDzfmW2Ke@Cv}^Yh0u%qrD68%vs2^E3Ulcg1#4D zmogv(aM-|Dz=3ZSY>BsTHDnG?*Rsg984b?Evr+h>a|Z(s02~woH_b+qHoQL^*Ri}~ z0^SNZTgxIBj=IBnxMtRb^Ul3uuzk|9@GySolDF!qQ~#z}YgNh%rB1`!CtL*h%BwEu z8*p7Pn{*mrI^nxj_i6#ZL9+&{bs-l!3%6bhSR64X9m*#S_!9@1%b%N&Dv8S6TIwWR zd+Fr@#-st$q;2FznwzxGV^sc&PFc$$*IqlS*eN)_AA_|=4A^S+Y0^}FbZW^8A7r{< zrAjGv3f>Kv@0Rw66U{z8oA&K%TJcT^Dhs(}>mrh`b!^LXYq1k>?KObKZe6UvHlq=@ zX-le)l0{pk{GJb!JE%yhemH+OU}dT0Rc!QSu-PuJuy-yg*(YVNic7&M>xFA?2P~Ib zy<3Jj75rWwMSg&WO}P~+T%i&>YE*-6c%Fn+OC76)7bP%Qbl$qj$80wH+$vRKkvrP; z!m|h6cuham)igj2Cju7kVq8;pdUMG;o6M9KWLd@{#X8}6fce#=z@ZY3$`J6r#arV0 z3mH+#jx18D51x68XpGCaBb?0OO-;)Sh9|@enbVs~7JtI!QDVj-ceU$-Yh>dNu)z?4 zS#N5>OVDuKzaWFQ+gwgfHQyoGyC}uF;4l6gj!bPz8aZ?rUauWxq))-(Pg7Z5kSwJ^ z4?J`BEfVfH>WJvIbLQZ^{%$T4Eo>yA;Da0~L*yA(st2x6!B_qp+?<4;WYjWfVfRPn z$P9urQsD}A+_-jqqrPR(?ecQ!04J|a8mY*V7THqJ6m7`NU=b?iE?w}=5|%GMDtPaZ zdlZ>UgN7~ego4H9@IWDtQ{2ZN&wEYB857*`mRVo4apT5yd-q2q?;MfDWnNm)uqg!v z`{H~dXCfJbMfSYXIXwI3TW;$BhnB5hU$${wOH0d+y-P8l1T0^4+g*MrlC{8!4FczR40{NGI1~nZ(&i zld5$z-gYnG8|is`p8Z8&*)q6g!G{4u9wTIw7Bp;;f;r1g=IG>zL<&tdv2iTPV<6)vDJm^!vKc>o*qk{gQ(|Ovw1o#Ohx0mzD*=lO`3imq zS-5Orc{z%=-9J{!qBPO4l>lC3%1n=mv_g%Gi_3zVm6cV!q4UPuDsSdb3AtSh;nI$+ zzaO%gwkVC(uz`!*w`9p8lQ}9XJth$>0v73eK*v^BI&2r<0J!LlH?P$inbWX=^9&`} z$C?wB9>rniPPrhfQ#d~#u-LA1`5~8=W4GDW9LD9xNLFZWLuVPeWC@;{agd`>IWaMD z3fL)As>gK(Bb8WdY?r)S_q~AbU$}Alr5P1j(!z#LNm{xTu*C}53YKK#z=l;iU|MU3 zL+&ene)9JlX!L!nO_6u*TD!J+=NQ<8i!52X6R%#GR;v}25v!Hp4Y^--4&y>r4*4R$ zm^ENptx*54fkxb~*L}ES$BtcV4_K`jrsa4uY6Is%;az5eY1USj+$p)ahTH}naLsse z$jT#`;NX-HOjLOz5| z<7sK!MQ|Lz^z?N0OHOW1PR^>%VdQmo$f~_&u=;<4FgZq%Kiz><<7sR>o`EEi7!!lP z@kguARdcLii(O}L|&p8_7_rcd5JV0KAuT|#mq>A{;*0Hj6=p=i+hN= za!AOGXA!Q0U)NSxct2&VNMqqzizO2e2gXvOqoe7!JUuY-jI4Z~IAvBj#DC-b1$A|G z#2;DEHhJ&ftWU`e^-fvChi2;hh<;l|z&m6B*mbWiu*naEUfJ$57ZExYK<`FDh11@;O zZ(jOgte3j7eb`(sLTQbC{eHS#M6pPg#kgHJEGyqGEA!1x2_o6AXx)6ZJ%9g8(R9HA zk4cXN3YNKn$eLRHFtSZlR?7iW4oj>5c=dw$$b!`$zhuA$p(b!7LIf%jK4Z|UPQm%F zvujxlTWmI8Fd^efWuzxYcpz@ww7nc>CQD#=c(`xTpc#4kVR9c)hSpzY?~zFjn=F-;ez)IrG%`k zIKfXv)CjpB$uw?~sWj%zX=YP#=7)wLL3>d5gka=lO=nq}wdS_K(G-6kK^N#;e<4l$J6tEn2hM42OQ* z#S7~yE4O=fBaD-tcN^V3Y20kbI4x?knFk!J`|7K@HG3+$6-Mdpn$m3GG;xkEJa%kh zc|bMJw|ik^_B<}DX=Ve5P1%#*GoR<0Id}U!N%J)mHt;uJ!&TH1u)u3u`Mjw$Xl@UN zio@+GST?V1ZQaf6H{blD^|g2B3RFMrwvWKN?L)tA`kArg`$~$NzJ!B8^PG8->!wX#J95((t3y=S>#na4 Z{sXBAQ^B4b*D0=;^gYz-|yn&>fz+-;^gb$tR{2FuU;^gZ8|Nq=F zC*U_L-83j7lS1S>F5WdM{J#(4IxYOY2mHPY{J#t(mPP!lcM=6_0=Rh+2z!N5zM*P7Q{J;?)kU;#x87rMiE}~BS!51i+Nc_erET2sK z!yWs)1uULR<~}jzKQZY+GukmGDx66!piTY9EHI-_{KFso!W;a<9WkU({Kzl;#wRhQ zQ7xcNGNw`=k3aBz9r*eC`^GBre;oGr`t|ns>+JF9>hM#R?#boR{_n%;LNzFS>EG_= z`t#tL!u5^0^yTO5_sz=t<)`LoU;EQt-$6F0)wYt%qx{cJJeNJ=?fl~H{8N_g_1D&e z!5_Tl35zr6FbuWIm%?9Xs1aQxS8PO)iAr)4jeNvgCqimNoMvM%Aj zE8)H)`0wxe_U-%T4y;g|d1l54qv?}vZrbZbejW$R*B zLXk&Pl|k!HK^~7jrB*ZH!7#-@E73YB&^Rb!p6^DD?bGPm>D<@g+0FXw!`9=!)wGBFS!m{(dkM9cZ%K|qpFEAaNDW3Wi^ufr#U*h8fv&Ghs4NgC=b$mG!!*e;g}t0 zYOHY=iBQy7!ADu>YOb?LU{`Zbf;^~duCj_JtGSF9&_8&wPxmMY!vGY=uOMwPWUNr| z2(5=H9g9$hehS_}#7U5@y@HG4Jv@ys&qyLJLZg#^vj2D_nN0rhN7dbWldk$mO4+Z{ zVb|n8fa}vG^HGEVPapL|TYVEQ*XazXaSa`Y*HiT+c$?0UuVvEHCmf%O%^Cv*jLQ#$Hl=) zp3jTc30y7%l|Xd$VyPEfE`qDtm*e63;CXb>%VLRRV9I(jDyp$?e)JS97+oFNx5#$za)HacjoF-JR2VX>B43!#MsChN&P!n~mT`%px1RFr+~O z?I7tUnzSjgL`$Wlr4g}eZ6UpE+Eqh>rKFLdRP3@|R^kPV)OuNof-Xe-Ccfu6=gd3p zkmgMO6kNFY@tkuyv9nsoGHu~444Oe$b3*0IZ1O!2E$li!4czr|$?^J4U9YoS)H)_(lF9Bg=)e!Mn9!Mh*f z@)cvTe$=zog6(y6aUc?j^oe@=dUgH%^n=P_;2rnZAz*fS@wism-c&Ndz#;24LcqJO z+a#7>HQ(_T*Z`f@GF+DFpRzf8#dY1vfnLloxh1-Mudh&R6(w05F2w#_QLq9G7 zi;B{{sad8crF;RK70h(+v?2VE)IsVWVE#j3&KBi!RT@S} z4Zq4!C(5>x(+2R27#ve_B9Ul_$!~4yODA~Urk+Jbo-Az>7;_a%4GpzuG2WKdsT@9= z=NRVECNQPc|JB>cYg0iS2k?V>5GDA)2VRVVAwlC7HF(hwLxKo`O2ouN38oPV6>M%= zY}~0UQe0|9>W;fbP;t2^DxTaB^q`2Se~91r9cSk0P~SAQ34TdVJ$(57W|}<6%zVO6 zUQV98m2hCVt!Gl!v@NWsOm_LAMQ4TyLpEp&n5t~MbP=-SyBPI4yHUp>%hoD3n+vR1 z-KA*De3{Ho>H|UW1qUh7C9LVB&A$^|HD%I^7oR=lAp3*h^Zc5Kqq2yUXHT)&)m@l0 z(4}yNik#*L$oe37kAuX${8_r)0`BFmf~wtl^PYPjnLC5vjowFQg0iwn&n{WOWlq`8 zB2V60d`WC_kd8z`!g1Ytn+1Fcuyn~XANg4OcbJd4wS*Vd8MKFAwtx%lOIEr|mdv~U zij@Nkubr!5%A)n*0GR2MDqX_Jdp`QC3s_<2Bw&+{glyo)s;y>Yu~^%sMd4tLllV?S@?9}eC~X5bR`(TVs& zOSp$BVd)~#XmqoXo!z7ryUP*ohfET3(vagt8L)E?0Z6^+TP8_bsrDq*0vo zVQZM_6)IsYtw6QQOk-T85~ix?jBI@+G(5Pc?%Pzt#Y?D!L27pT^l#yCI0BrmkFFK% zV4Zt{C5pFEma;45fv|||tYq#iYnZ9RHQS1iRdtcu5>_$-xg#`uxMwp_)|ITexWc_j z)ucP`*uy%7I%H#4(PgMIl^;1%hTU0g><6zyb-@}_R%L&|w{p2$IA_q=!;SvLw*jn* zkCik`7G4Ngx!P_EH3U=Etbc9kLS*L5xxH|qNz-i%rrfVsM7U5H=?=g(=`7qvVXkwZ z@vqCpb%}cJClvk_-%@vCeH(_ELZ5UQRhRA@~tbD@#KTmZrW73p?fX)8A2u;8J454uitUtjU;Mz+2EWXHqH#sBGe zWUG$Aw(QVv%Z@p>?qK)0F|9btJ_#2VCbr`6{#agDXzuo6Vcrdy*!&9`lAg;pl5eP1@S4ylY3Tzi5SZp6FdI+c&yb%l54SGG45&uk_eSqn|P3 iQP;?X(Z4?EYx@%n6t9_oGub@=0000*D0<-{0@zfz+-;^gY#Hq)#;^gWAd({Mf z*A7yd27uQz!R-!In+{Qx4^NaU!_x?Y*bP;kF2ddqjodE6+X;l(3y0bPdDJY!)(wi= z3`~qM!s9E$&ka_d4^EOa!S5}@*b7RC6O!H#O^^(T+6`8p4pEmCmETg`fiS}05Rcs~ z#LQgebuGi$4@{0==W;T^=L}Dh3`T-g-+fNohFRiyGr{Xa)0PZQk2%b&KhU8(&Zk1s zn>5F7EW^qA`}|DViY&v%N!O26-*PR*zB9*yGsb&R+i^9=W%2a)6L6wW+ITd_bvMhh zJE0W2_Bkn+?aJ-yw_JDX!JJ?$1(ts2F{th~%7J&gpTq=Gyk-V65X7lG>%d*o36UfZf5S z>a&*Tt_oY8J8GPP+?06Wj%nw9%kcNh@bRL|>VUlJF_Ybvve2*ay&RCePtCeYezP!w zu_ULgLUyaL*7BX=?@_7cwd&+8y54ix)l#6*XxFq^)t_nDe#PbYXVLSJ#O=Y{>9ok* zE1B4{#MUUZ$e-%KTZXkBk+B+sue8YAHm1r?o5WVDy&L)SUH||94|GyaQve|EkPx5W z-(b%$5YM2X{W32a000TdNklCmk=2S9vPTn=zR13-AgXqT{sc9sF9*YkQ|l>(IQ24 z1EWs(h(H~lNa0niTSWZrc&~~&2d1Lecpwqo6m_FW(m2Pd!X76{kxfcRk0PyiEGy{s zQsn3+eaGpXmhr~UZc(K1IwxkF6j@&9h|+qUQ?p)*$ObJ*SYKzRq9oC9xo|UxR}nLG|f6%xCoNY1(70zlUxH!|6$GX^}USv z_^xvv>>KL;Bq)8pd=)BG?$DuSwc)p(Jb2{mXn5Tv|IBI9Btg=1x@F61)#kNob?eEa zb&i8C`R6H+IWuHIR|d`G0Rsj+d*m26^l%EKKml$;2F**)WAx-(r+n=WhXy5Mfz4_2 z(vwGwXbRe%@TJ4)^5n^rEE!%@TMV3)=JJ5GfScAk`5dxIMgTKeOq)7a>j=1P(=r5i ze!lw8VgCdP5|n^FhqvaP0rSp)AroA;blI|H>XifBxy#2tgx9@-i;xVO%W`MMu#IrJ zawVH}uF?gv?J(TK2{M7DVBWuzmkwue8G<_#TT&8l_eAa51mYBP_i{7veegKrq zqH*DLidjQuvj%L~TWr$IEn4)bEwHcBrR^mei$zw$G-(uOZt;tL*2C+9)Gj}k0Yhf4 z7&V7!iFskL$RTTCvA}9qH0E#EZ~^0JGKcf$&%fGQ*cV&bn8N#lZ+@Ag*H4}DRs{Rvir9r~aH+%3!{?MBze(3f-^{4mGg* zrT|^3?`4c$zn~M@3b%ssXhLBI%PxY-*1$m}SoZ0m_Ug5B=guaEOqn{hmmbAC1#?%x zd{b94f!nk}Wh>y&IdhsfZ~l17=9MdqlO_?a*Mt6GJpZZO5|g$7w?R-@ zI~-IP30?`7)Sf*zZr!@IKg-rNYk+h5Gj}Th7Mq&wEddu=tsTCU4q3B#<$SQqHg4Ry zWXZ&d6I)R5Cve!HAs-Q%e%cg+V&D!_h}HhJ>ASy^&Xprbg^RJv1Tc4j>f=RF3vi-6_q z*9wOwOLkMPyJT34>|hZAGn=)Z)CBTD`HY^O>D7~Awz=v<=JaW;uzw1;s5=*I+T;|H z&1f!vHl9=iX6IX7#5*SB{0%N>g@g8yJ9o*j?Ag--R>?f?_8fcqloqxOVN@fU8z*P!(CN4L;1=l&KJc)uoq&;7645T?S8^#^J6U zX0jFVw{7I6im?B^Uy$kNIV6bpoS0|U!n*x;L zrwtB^8#k_yO*Z#1Z%@xG9u`Oaj97B3&q8E)Nbey)kqbI)uuohei-ktvv)*t^t*`{k zD#GDDeTJ~KBYU+*IFMYt04uiGFmgSLB(iWRVZVW`QKJUnJ_vKWQ&3n7jAVxlm?foH zXt{8aL=$vodCpa&PMtb^D18yAh@WP7uL*3@Ig9MZJ853ns3PUdbC<)Xb(-OaU?~Eo zY@@<(6PhiuUuU6G+P81d+_4l4S~Glv!^+Kl7KaiQcF&-m^=P5WY^_?g8a4!qyqIkT zd|wIk(jGKvxH)p%)+utdj#>rVa1je!E8xI5aa1zkVcx88LiZdQRF!AX;;2DIdh}rK zAuIn6WG}%yDY1z@jq?!8B>s*nNeCx38o7PoFtY zIdpn8skXpJ1WYl3Ge8FHe$UGx7rbcJ3Y%cIskuwC{b6K}62@o74uW$snB;^BSL}8G ztgej4+gM`(L!RIO7?oA@2EN8J3>i7;0C;g~^)hE6I3C|zaFKW`PS_hpWymtj>3Al` zF+wi#JKF2ypmWy4VSp8#HxZ28B}NUI(Uy1Fgn$rFPj4?PVdQFRCA|%>VUgkT%9Nb9 zuZO9+ehmmIIB1ZkXMoOnIB>5@PRJJ7UELUP&P0hqZhty?;_ZY9Z%|;Og2D%Mw!?wfQVUw%*pOLYcev^` zM!1W@=e%r(h0L>w%YZ2sVJ0)zMp#T+$QZWhEMTRZk=z4tZGuIvn#voB$THMluQA*J zdtA7Fozz60>w)iWB%?8SK4~RfFyy+OF4gZ9a_YH7Jg9Tdcos(u7H*i%J}|~@Htocq zQCYpYsBeIm&R#G|<5SXD`fL^tP9q8!?12e(z5(URUQv2S5f9KAGbT=yC?~>T!P*%H zjn)dfAnc4{PjS}%Fa~~XFNbl~>7IpbBwIn41wBFnz&Z#<79YFD<+vMmoXv?7G3Kve zsv}|QegEpiYsQn$B@>?>x#Hu*Tjv6O!WJ(M^9lTSZWjD|VOQM(>$;nMW4q`!XV=}> z?H0imx3Wuacv`^~H}6Ms+2abXd_V(_q&Rojco-&z;UOGn?(A`R2**{A9l8ENE2lnM v#@APrjwLxh5h9AZA~B-Ia`fYaCT)KL-r<<4jswzu00000NkvXXu0mjfV-%r3 literal 0 HcmV?d00001 diff --git a/website/assets/images/app-icon-microsoft-excel-60x60@2x.png b/website/assets/images/app-icon-microsoft-excel-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8401843239d73190befb341d5a5bd8329c166ab6 GIT binary patch literal 1629 zcmV-j2BP_iP)*D0<;^pe%*D0=;^gY#Ez_^|Ns9OTsI-3 zX5!@P5PU&1#DNomMiPHS6oW_+enJ(5N)UNJ6@^O=cRu3l<_&N-4s|>YaXJrnJrYAG zkkPUihD;7~JDK6Y6;Ln`dqE6pG!1Sy4stpjnOzKQHW!CZhrXaJw|E|tT>Sn07E>|~ zH6Rm8EF6zl4KN-HDjF4JIt@c52}mX;t8EQyImO}A3uiKmt(#f8jPdvM;qT}9`upwg z^6KpHrqH=Bk6TiNX$w{@^7HoQ=j}|IZwp~CEQC`GWHH0u&`OnSA&62PeMk#bET_@C zrOmf$t%w_xSQ2zUJeOuIh*m6uQXhg$&*$4rqINBYRSQ)u<@fKV<;bww#jwY_lgX}& z!=`w$kw;G!E90000GbW%=J0N)UhpI{*G@6RA0FwdYcSmVak000Dh zNkl~hTMRK#v^2-`gfKSBdG(Jg<#K&y(-39L!%D(12@Z=1 z&*tTAhiTD5fa_&NA#m9dQL;R)gd>}t4$R9IYt2v(oLQb%7Co@(am(|@MI^LI6fMsy zigls2ULRsU$RQyt9}*&fTNl9pJv_g^UGEMq-@^ypz?ZhQ?cDZu9qb%_w5>Ckd(bg_ zsXO?gU+}>I4&VR|-~iSSe>$;D{c7R-g(WSVZqd|flvTp-2e<>&E@-FI4yRZQUa?na zzXPohe#$JXm{iH-Uj6zpFmMO!Ki;+#!ZU6c<;H38gL^%4WN#GPwXJRYR|d-^yQYN? zo|fG>ysnI#R}C|JfZc{A6N{;a<&x!=nbixsw%p>ZVl}1#X0`b(Bw;F{0YfOQp+fTlg|2;5 zEzByzv3VNV8Td9ITwuXf4NH|;aL(l_4$KFaR9!H$d{X5bZ=q-SRiU<*PvkW6VurwfbA=23B*{ zd<;?!lj^z*L1{dxaLb4S3-ca6tQcmu;c}3MTn88DJ**r?)@GJnP$kQ*dN|YICW(be zrE}^CvKnApvP-rsS!z@@OsX6gS%?oV#O$bQcoJP^<+glT!>g_wr1uhH`JT!$U2~{eH7gH~f6xGJ)$0 zd}^y^_}i)0ZPg8bJ=7}Lvu^lu>;Eu-12}+J5&We)_;JVZ+pgRfUEFf>_(kXLosuwS>p`ghasW}j|z_V31S(}#mRD1-@5EBJBqegvKx*dV+-y?EeW z-yYDwOvBq{m-&$tShgX`-aUj<5=L)`+h};U?V^0_h-UWkL95<>;n6Z2IwJJ9CM?JG b>+!)qKgNn_<2Q1p00000NkvXXu0mjfO}P_` literal 0 HcmV?d00001 diff --git a/website/assets/images/app-icon-microsoft-teams-60x60@2x.png b/website/assets/images/app-icon-microsoft-teams-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..08c254d1018d64943cf726a55147c71e72166d5f GIT binary patch literal 1924 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 u6*yY*D0<-{0@zfz+-;^gb%i_@$|F)a|eo+7I z=Kpm=|Mv6$qk{jDY5&^I|G%jJhg|>U+W)MM|H!oMx2O4qUHf}Y|C)CHq=M03R|I0A)pm12Cdpm7?}*Dah-bY-oZS@t@s5WTsAPS> z*~d-2O9G|P@mh28jz>_;5@$&dPw+sX7&_pjA85oisAh%O4fYh5pqyn_a>50xWsT!x zw7?ChXM>B=NN+(sTbz#OxB~U8a6TF%fqDqXBSd&Y5MS`$^MYUSV<>pvmg+ENF70By zE*xBXNv;XE={44nQG<=Ub*o$rZf;y-(=;f-=D|&KCD`A&T}THm?p#NCTI#?*_w|-O z4T=@>IGb7v_Pyr@cn7)Ge@u5n?RIKUHRKNuwU>uOowYo3aT^Q6zUsLWe9H3aM;H#a z#|i>~)?VeE0a2KWGnGMTb{@NE|)a2>g>?e21rg|o=D zWxxI~Lh$ddq5O0yKopMMicHncI-;Q9oEV0R%UoLx)LN{cF*#~ZLTBVf*j0~1r4_ul*23P z8$@7sZBv&7K-JmxDZ65@&x@9ZmNHav8%3_%l-a)$N+@DiddhSKuQ1sahC|^E&m`qv@*>xU34QAJ61e$Ob zu{v*cmpgE?fP z`T0NIfbxQ0@C*JmFZdt8Pul|fyiLDfPuk}Eyp7$-_!(Q-F&T{}&)B>_?T;&{JziY! z+xdXTofhh*;^pe%f+?<;^gY$g43?=jZR=-{0Zl>Hq)#M84nbvMw&R zec&Ei;^gZ6_2uaI`{nlU`{V1D+P?6Ptor->{r&!;-|zJM{p)g=^85Gw@%8QR^6KpH z(C_#D`tn=G<4?lj`s~X{zm4QNZ|%1`e9^8uyM^K`W%k&X^7Hn&=kw<0?fBxcbI#{t z$erj`gYm^;t>f{r zKoG!jb2OKx2WF8WB3=*;N}xg#6wF%_C<%d<(#!e&ALXF5nYgv<26RKA|F`&%ALAx6 zs8RaF7~f!T-HeTiu6^y#fYAOj{&K87K*VV2xX@ef{W3ztfnFPTgcv%|drpWrJRACU zgzJ7;qSrXQJ)1XlK~ZjWnpq!V1PTjrs8{d+*Dff`UE`p-(TENx%|R@0wt*oiE-XjZ z6%I@zPjzjA5hyRhiDuvul($4f8{<7F&%v=aMHiIk;#5O~D}?w93!Cq^2}`mS)C-Pz zYMO7aR!^|MXCK8idY76Xy#{^24>6-Wx^A1)!s-DoUFtOR#ysz>d)Q;O5+ACpg;~q) z9FAE%j|M(3?HZmlt_-T)O|C=7aKZSX%H%tC410{PI^^IBZ1)b&s^3=c@YA(mKT!)) z-nC#qRtr;)v09j##fo8Sm8piQ$N6WV7N&mT9}oMp=KRax;3C*BvV?tGwtA3*5C6)& zDBoDJ@RLBL^|lSR^3(3Qk%K+fo+pO+Ezp*MS4^hjdzf5mCxB^hmnreZ@Ex~&2CeXl z$>j;)hU>>xIFp38LU_kt&RgMxNhXAC{&Lz1GueC*d}s1?JTb_^X~$;_3trd}eAyX$ zlJFiH4>Jv#;Y};-Cvxx%s)Do*yg(zouv%d#r2ocM&vhVZhBsY2tHrM(2$p?TQ$^gu zCIvu~oLkTYr_UeS+979;- z{7?iZRf?HgRx~(5^b{6&65v{Eaacf%{LHHW4(5iYE7(hWyk51JS;WraBv7R@8fsQrA!kpb>=#kmfw z3~-&SSz(#zx7Y~BisUXr8>~M#b76*|qAi&Gu141Y_w%mC%Hvxz7hr`U zTUc221T+{#nSZQyx&5N3rzEvgvoFeU*rz_u+2qEr~{FsVQI*Tb_zkHf+t!-{NR z7+Zz|!ze!v45Pg91hdo^tTvut_T&prox>8hw=UtDx}mRnhPS%Aho(Y@XFb5ijegm^ z<61!9aKt-zpRscE9Ao=03vSA^1@{TlKSeA;2QbL#flXG>q;GF`v7--cV*2{7=Cy=& zS)rI(?`FT_%bvF*uvfti4BkU9eEnG8h4DAJ-8e#yZIa~o9M%I7lHf!o#Aq^iTm-v a32JY3GOP4=##{LS0000 literal 0 HcmV?d00001 diff --git a/website/assets/images/app-icon-teamviewer-60x60@2x.png b/website/assets/images/app-icon-teamviewer-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..298113cfb141405a333fc521681fba39f2ad301a GIT binary patch literal 2338 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 literal 0 HcmV?d00001 diff --git a/website/assets/images/app-icon-visual-studio-code-60x60@2x.png b/website/assets/images/app-icon-visual-studio-code-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b9fe5290acd83691b0eff744c100e9956f8b73b8 GIT binary patch literal 1306 zcmV+#1?BpQP)*D0=;^gY#;pFP%eoy_WkyWCBm)MlyD*6H=H z)a#_nVTW$l>xvnafO*#yyL{Tc_7~uhd$c%Pow>9)ZJ~q-h9 z1y_wKmbD=SM<0V9wJLw*9?hKK%6@=`sp|^OqkArJr4{_%C|bI%Q2!N?3k(2*FQdbgA4r5P@IEzzulGNF!FB^Lfmikk z?8-j+UD;>OtNXCq&#&TBcJ@85xQdVW3!dk#v)Yky;L`UC8ppa9G>*CW<4TI|u$w=x zqhKDYe#%sCzU$KxONn`u6ohxU$|Npw_afR@*D0<;^*q$-|yn(>f+?;;pFP%S*u)yd-3q~Ntp+H0Mg@HPMd01k9gPE!Ei?~ou6pJ2~0AP~=>KH47;000Fg zNklKS$WzmQwok+;Va=@z>|4H^_E3Fn3Mq`9N;2!Hgyux9f6(gg&+2CqKRoBKhV(XdU zW?)AZEmS!`pXPwM@pC2{UGvFcUiX}=v)Ym_qhtW*at+JvG{z&N!OlySEa~2GCxIJq zv%<~v5NV45`vVt@;KYDjp}{Q3#gi2h%mjiP%S*v=U>o7$Y<4KHyyO*dk>nDbjR0T9 z+mWeK;Ou;`#?>$4B>;{FNQ@>Rt2M#}<}+M86AWWzfqYZ=FzY-p7~F*N+~A#E@;tDF zN(wblmS4?aat0VuT6%+lw;)Z<0;^DF2>_QIZ?Z~*A(*G3!JrCnvdICLfmpgexF%?m zOP&UI&}03I$mEAm4O&T=WRfm-z%2!1gH$tt2?7@!FyCw?&m z&MH&jD=Tq_Oe}v|<8d&%QZNcxN`AJoJaFa39ydAp>CxtaCAn(yX&P8z!Tu6NEeqxX zo7Z@7IE9qA;YRh$77zBFZKh;3zK;FOE-b!|Ndhb)%Ns7Pdb^U>0&g-qR}IhkVx`xy z!Jn4JdbLS`ZFG^LdaK;Z%iIu}7n!qD71^Hx0yslQ7hO5V|-Rp3MAgZd@@^7w8K$Nr1sORqVq`R4m}1CJk2U?!@XCw+#7D zA@)ux*$2aN0Z$8(0dBmuh;z3LbOnppAHz}uXS4`yecB8##N{;UH!u}2xF%WP4$>2k z)**vs9+(J7>?B$YGkSoq;Qc(X#C%Xxz%DfpzDDMPiNGyoRppFvjpXs~gK01%Hkly@ z24{oYyddY5TTSx8u$!K&4q7(?Y~X(;sx6_u^1&RkVm8;1Y%sXV&gTk-0o$3s3&;x> znl!3&@#*;ts|f^{%_=5l`i&a}mfmn#<#H>lD*Id<*bU;tD*N%SI;Xaj@g^z+xV#h- zm;KqtBBn`?i&%lw%_kJlDrIdfuhMI-h(yzdsag!5YY7@$7jX$Ihi`4AEl1B7PS9Xh z3N`O#0)CX%xI<#C8yiK|olFm-@g~1N4g8PPitP z4PAf}Is*HPwq$X=(udXWZ#w4ueFwXDo9{TvPIvFO6^>+K9qIn#y24`V1qR=J=LL<` zZoRCtU+DVratY^UF`N03uJ8KSjuuMo_Vd~iT(mkafIb{4*xSEa_Ig8)^Y;JMvd8gd ox;?$UHd`t<807*qoM6N<$f&=#Z=Kufz literal 0 HcmV?d00001 diff --git a/website/assets/images/app-icon-zoom-60x60@2x.png b/website/assets/images/app-icon-zoom-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..069259362d4fcd57072227e0c96ad1ee2f8c736f GIT binary patch literal 3531 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 FV1iGN>zM!m literal 0 HcmV?d00001 diff --git a/website/assets/js/pages/app-details.page.js b/website/assets/js/pages/app-details.page.js new file mode 100644 index 0000000000..bba7bb6ac1 --- /dev/null +++ b/website/assets/js/pages/app-details.page.js @@ -0,0 +1,47 @@ +parasails.registerPage('app-details', { + // ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗ + // ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣ + // ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝ + data: { + //… + }, + + // ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗ + // ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣ + // ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝ + beforeMount: function() { + //… + }, + mounted: async function() { + + if(this.algoliaPublicKey) { // Note: Docsearch will only be enabled if sails.config.custom.algoliaPublicKey is set. If the value is undefined, the documentation search will be disabled. + docsearch({ + appId: 'NZXAYZXDGH', + apiKey: this.algoliaPublicKey, + indexName: 'fleetdm', + container: '#docsearch-query', + placeholder: 'Search', + debug: false, + searchParameters: { + 'facetFilters': ['section:queries'] + }, + }); + } + + $('[purpose="copy-button"]').on('click', async function() { + let code = $(this).siblings('pre').find('code').text(); + $(this).addClass('copied'); + await setTimeout(()=>{ + $(this).removeClass('copied'); + }, 2000); + navigator.clipboard.writeText(code); + }); + }, + + // ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗ + // ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗ + // ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝ + methods: { + //… + } +}); diff --git a/website/assets/js/pages/app-library.page.js b/website/assets/js/pages/app-library.page.js new file mode 100644 index 0000000000..595a8ad10e --- /dev/null +++ b/website/assets/js/pages/app-library.page.js @@ -0,0 +1,25 @@ +parasails.registerPage('app-library', { + // ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗ + // ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣ + // ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝ + data: { + //… + }, + + // ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗ + // ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣ + // ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝ + beforeMount: function() { + //… + }, + mounted: async function() { + //… + }, + + // ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗ + // ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗ + // ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝ + methods: { + //… + } +}); diff --git a/website/assets/styles/importer.less b/website/assets/styles/importer.less index 33420eb3ae..8fc197e4c2 100644 --- a/website/assets/styles/importer.less +++ b/website/assets/styles/importer.less @@ -78,4 +78,6 @@ @import 'pages/start.less'; @import 'pages/deals.less'; @import 'pages/testimonials.less'; +@import 'pages/app-library.less'; +@import 'pages/app-details.less'; diff --git a/website/assets/styles/pages/app-details.less b/website/assets/styles/pages/app-details.less new file mode 100644 index 0000000000..b4e52a0343 --- /dev/null +++ b/website/assets/styles/pages/app-details.less @@ -0,0 +1,418 @@ +#app-details { + + h3 { + padding-top: 32px; + color: #192147; + font-size: 24px; + font-weight: 800; + line-height: 120%; + margin-bottom: 0px; + } + + p { + color: #515774; + font-size: 16px; + font-weight: 400; + line-height: 150%; + } + + [purpose='page-container'] { + padding: 64px 64px 32px 64px; + } + [purpose='page-content'] { + margin-left: auto; + margin-right: auto; + max-width: 1072px; + } + + [purpose='breadcrumbs-and-search'] { + margin-bottom: 64px; + max-width: 1072px; + font-size: 14px; + [purpose='breadcrumbs'] { + margin-right: 24px; + } + [purpose='search'] { + // Note: We're using classes here to override the default Docsearch styles; + button { + width: 100%; + cursor: text; + margin: 0; + } + .DocSearch-Button { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + border: 1px solid @core-fleet-black-25; + background-color: #FFF; + padding: 6px; + height: 36px; + margin: 0; + width: 256px; + } + .DocSearch-Button:hover { + box-shadow: none; + border: 1px solid @core-fleet-black-25; + color: @core-fleet-black-50; + } + .DocSearch-Search-Icon { + margin-left: 10px; + height: 16px; + width: 16px; + color: @core-fleet-black-50; + stroke-width: 3px; + } + .DocSearch-Button-Keys { + display: none; + } + .input-group:focus-within { + border: 1px solid @core-vibrant-blue; + } + .DocSearch-Button-Placeholder { + font-size: 16px; + font-weight: 400; + padding-left: 12px; + } + [purpose='disabled-search'] { + input { + padding-top: 6px; + padding-bottom: 6px; + border: none; + } &::placeholder { + font-size: 16px; + line-height: 24px; + color: #8B8FA2; + } + .input-group { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + border: 1px solid @core-fleet-black-25; + background: #FFF; + } + .input-group:focus-within { + border: 1px solid @core-vibrant-blue; + } + .form-control { + border-radius: 6px; + padding: 6px; + height: 36px; + margin: 0; + width: 212px; + } + .docsearch-input:focus-visible { + outline: none; + } + .ds-input:focus { + outline: rgba(0, 0, 0, 0); + } + .input-group-text { + color: @core-fleet-black-50; + } + .form-control { + height: 36px; + padding: 0px; + font-size: 16px; + } &:focus { + border: none; + } + } + } + + [purpose='breadcrumbs-category'] { + color: #8B8FA2; + margin-right: 8px; + font-size: 14px; + font-weight: 400; + line-height: 150%; /* */ + &:hover { + color: #192147; + text-decoration: none; + } + } + [purpose='breadcrumbs-title'] { + margin-left: 8px; + } + } + [purpose='icon-and-name'] { + flex-direction: row; + align-items: center; + + } + [purpose='app-icon'] { + img { + height: 80px; + } + margin-right: 24px; + } + [purpose='app-name'] { + color: #192147; + font-size: 32px; + font-style: normal; + font-weight: 800; + line-height: 150%; + margin-bottom: 0px; + } + + [purpose='app-description'] { + color: #515774; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 150%; + margin-bottom: 0px; + padding: 16px 0px 32px 0px; + } + [purpose='app-details'] { + padding-right: 64px; + max-width: 800px; + width: 100%; + p { + margin-bottom: 24px; + margin-top: 16px; + color: #515774; + font-size: 16px; + font-weight: 400; + line-height: 150%; + } + a { + color: #515774; + text-decoration: underline; + text-underline-offset: 1px; + &:hover { + color: #515774; + } + } + [purpose='platform-and-version'] { + p { + color: #8B8FA2; + font-size: 16px; + font-weight: 400; + line-height: 150%; + margin-bottom: 0px; + margin-top: 0px; + } + } + + } + [purpose='app-uninstall'] { + pre { + height: 141px; + } + } + [purpose='app-install'] { + ol { + counter-reset: custom-counter; + list-style-type: none; + padding-inline-start: 0px; + padding: 0; + margin-top: 16px; + margin-bottom: 32px; + ul > li { + text-indent: 0px; + margin-left: 0px; + } + > li { + counter-increment: custom-counter; + margin-left: 36px; + text-indent: -36px; + padding-left: 0px; + margin-bottom: 16px; + code:not(.nohighlight):not(.mermaid) { + display: inline; + } + p { + display: inline; + margin-bottom: 0px; + } + blockquote { + text-indent: 0px; + } + } + > li::before { + content: counter(custom-counter); + background-color: #E2E4EA; + width: 24px; + font-size: 13px; + display: inline-block; + border-radius: 50%; + margin-right: 10px; + padding: 2px 4px; + text-align: center; + line-height: 20px; + text-indent: 0px; + } + } + } + [purpose='app-check'] { + padding-bottom: 24px; + } + + [purpose='right-sidebar'] { + width: 256px; + margin-left: 16px; + font-size: 14px; + transition-property: transform; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 500ms; + a:not([purpose='edit-button']) { + margin-bottom: 8px; + display: block; + color: #515774; + &:hover { + text-decoration: none; + color: @core-fleet-black; + } + } + } + [purpose='docs-links'] { + a { + display: block; + } + } + [purpose='social-share-buttons'] { + padding-bottom: 24px; + margin-bottom: 24px; + border-bottom: 1px solid #E2E4EA; + a { + margin-right: 16px; + } + img { + height: 20px; + width: 20px; + } + } + [purpose='edit-button'] { + margin-top: 24px; + img { + width: 16px; + height: 16px; + display: inline; + margin-right: 8px; + } + padding: 6px 8px; + display: block; + color: @core-fleet-black-75; + text-decoration: none; + font-size: 14px; + line-height: 21px; + border-radius: 6px; + width: 102px; + background: rgba(25, 33, 71, 0.05); + &:hover { + background-color: rgba(25, 33, 71, 0.1); + } + &:active { + background-color: rgba(25, 33, 71, 0.1); + } + } + + [purpose='codeblock'] { + padding: 0; + position: relative; + [purpose='copy-button'] { + position: absolute; + top: 11px; + right: 10px; + border-radius: 8px; + height: 32px; + width: 32px; + background: url('/images/icon-copy-16x16@2x.png'); + background-color: #F9FAFC; + background-size: 14px 14px; + background-position: center; + background-repeat: no-repeat; + cursor: pointer; + &:hover { + background-color: #F2F2F5; + } + &.copied { + background: url('/images/icon-copy-clicked-checkmark-32x32@2x.png'); + background-size: 32px 32px; + background-repeat: no-repeat; + background-position: center; + } + } + } + + pre { + width: 100%; + max-width: 100%; + padding: 16px 44px 16px 24px; + border: 1px solid #E2E4EA; + background: #F9FAFC; + border-radius: 4px; + margin-top: 32px; + margin-bottom: 24px; + white-space: pre-wrap; + word-wrap: break-word; + overflow: auto; + code { + color: #515774; + font-family: 'Source Code Pro'; + font-size: 14px; + font-weight: 400; + line-height: 150%; + background-color: @ui-off-white; + border: none; + padding: 0; + dispaly: block; + } + } + + @media (max-width: 1200px) { + + + } + @media (max-width: 991px) { + [purpose='page-container'] { + padding: 32px; + } + [purpose='app-details'] { + padding-right: 0px; + max-width: 100%; + } + [purpose='right-sidebar'] { + width: 100%; + margin-left: 0px; + } + [purpose='breadcrumbs-and-search'] { + margin-bottom: 32px; + } + } + + @media (max-width: 768px) { + [purpose='breadcrumbs-and-search'] { + max-width: 1072px; + font-size: 14px; + [purpose='breadcrumbs'] { + margin-bottom: 24px; + } + [purpose='search'] { + width: 100%; + .DocSearch-Button { + width: 100%; + } + } + } + } + + @media (max-width: 575px) { + [purpose='page-container'] { + padding: 32px 24px; + } + + } + @media (max-width: 375px) { + [purpose='icon-and-name'] { + flex-direction: column; + align-items: flex-start; + + } + + } + + + } diff --git a/website/assets/styles/pages/app-library.less b/website/assets/styles/pages/app-library.less new file mode 100644 index 0000000000..5b2f66861d --- /dev/null +++ b/website/assets/styles/pages/app-library.less @@ -0,0 +1,205 @@ +#app-library { + + [purpose='page-container'] { + padding: 64px; + } + [purpose='page-content'] { + max-width: 1072px; + margin-left: auto; + margin-right: auto; + } + [purpose='search-and-headline'] { + margin-bottom: 64px; + } + [purpose='page-title'] { + max-width: 662px; + margin-right: 16px; + } + [purpose='app-search'] { + + // Note: We're using classes here to override the default Docsearch styles; + button { + width: 100%; + cursor: text; + margin: 0; + } + .DocSearch-Button { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + border: 1px solid @core-fleet-black-25; + background-color: #FFF; + padding: 8px 15px; + height: 36px; + margin: 0; + width: 221px; + } + .DocSearch-Button:hover { + box-shadow: none; + border: 1px solid @core-fleet-black-25; + color: @core-fleet-black-50; + } + .DocSearch-Search-Icon { + margin-left: 0px; + margin-right: 8px; + height: 16px; + width: 16px; + color: @core-fleet-black-50; + stroke-width: 3px; + } + .DocSearch-Button-Keys { + display: none; + } + .input-group:focus-within { + border: 1px solid @core-vibrant-blue; + } + .DocSearch-Button-Placeholder { + font-size: 16px; + line-height: 16px; + font-weight: 400; + padding-left: 0px; + } + [purpose='disabled-search'] { + input { + padding-top: 6px; + padding-bottom: 6px; + border: none; + } &::placeholder { + font-size: 16px; + line-height: 24px; + color: #8B8FA2; + } + .input-group { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + border: 1px solid @core-fleet-black-25; + background: #FFF; + } + .input-group:focus-within { + border: 1px solid @core-vibrant-blue; + } + .form-control { + border-radius: 6px; + padding: 6px; + height: 36px; + margin: 0; + width: 212px; + } + .docsearch-input:focus-visible { + outline: none; + } + .ds-input:focus { + outline: rgba(0, 0, 0, 0); + } + .input-group-text { + color: @core-fleet-black-50; + } + .form-control { + height: 36px; + padding: 0px; + font-size: 16px; + } &:focus { + border: none; + } + } + img { + height: 16px; + margin-right: 8px; + } + background: #FFF; + &::placeholder { + font-size: 16px; + color: @core-fleet-black-50; + } + } + [purpose='app-cards'] { + column-count: 3; + margin-right: -8px; + margin-left: -8px; + margin-bottom: -8px; + margin-top: -8px; + } + + [purpose='app-card'] { + margin-right: 8px; + margin-left: 8px; + margin-bottom: 8px; + margin-top: 8px; + display: flex; + height: 92px; + min-width: 30%; + padding: 16px; + align-items: flex-start; + gap: 16px; + // flex: 1 0 0; + border-radius: 8px; + border: 1px solid #E2E4EA; + background: #FFF; + box-shadow: none; + &:hover { + text-decoration: none; + color: unset; + box-shadow: 0px 0px 0px 2px rgba(0, 0, 0, 0.03); + } + h4 { + color: var(--text-text-brand, #192147); + + /* Title XS */ + font-family: Inter; + font-size: 16px; + font-style: normal; + font-weight: 800; + line-height: 19.2px; /* 120% */ + margin-bottom: 0px; + } + p { + color: var(--text-text-primary, #515774); + + /* Body SM (FKA Card text) */ + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 21px; /* 150% */ + } + } + [purpose='app-icon'] { + margin-right: 16px; + img { + height: 60px; + } + } + + @media (max-width: 991px) { + [purpose='page-container'] { + padding: 64px 32px; + } + [purpose='app-card'] { + min-width: 40%; + } + } + + @media (max-width: 768px) { + [purpose='page-container'] { + padding: 48px 24px; + } + [purpose='app-search'] { + margin-top: 32px; + width: 100%; + .input-group { + width: 100%; + } + } + } + + @media (max-width: 575px) { + [purpose='page-container'] { + padding: 32px 24px; + } + + } + +} diff --git a/website/config/policies.js b/website/config/policies.js index becc2c3931..00c0dbecbb 100644 --- a/website/config/policies.js +++ b/website/config/policies.js @@ -58,4 +58,6 @@ module.exports.policies = { 'deliver-deal-registration-submission': true, 'get-est-device-certificate': true, 'view-testimonials': true, + 'view-app-library': true, + 'view-app-details': true, }; diff --git a/website/config/routes.js b/website/config/routes.js index f02a813ae5..be8f24a69c 100644 --- a/website/config/routes.js +++ b/website/config/routes.js @@ -297,10 +297,22 @@ module.exports.routes = { action: 'view-testimonials', locals: { pageTitleForMeta: 'Customer stories', - pageDescriptionForMeta: 'See what people are saying about Fleet' + pageDescriptionForMeta: 'See what people are saying about Fleet.' } }, + 'GET /app-library': { + action: 'view-app-library', + locals: { + pageTitleForMeta: 'App library', + pageDescriptionForMeta: 'Discover what is available for install in Fleet\'s maintained app library.', + } + }, + + 'GET /app-library/:appIdentifier': { + action: 'view-app-details',// Meta title and description set in view action + }, + // ╦ ╔═╗╔═╗╔═╗╔═╗╦ ╦ ╦═╗╔═╗╔╦╗╦╦═╗╔═╗╔═╗╔╦╗╔═╗ // ║ ║╣ ║ ╦╠═╣║ ╚╦╝ ╠╦╝║╣ ║║║╠╦╝║╣ ║ ║ ╚═╗ // ╩═╝╚═╝╚═╝╩ ╩╚═╝ ╩ ╩╚═╚═╝═╩╝╩╩╚═╚═╝╚═╝ ╩ ╚═╝ diff --git a/website/scripts/build-static-content.js b/website/scripts/build-static-content.js index 75d0d64043..d14d4f8e8b 100644 --- a/website/scripts/build-static-content.js +++ b/website/scripts/build-static-content.js @@ -1116,7 +1116,48 @@ module.exports = { // Add the rituals dictionary to builtStaticContent.rituals builtStaticContent.rituals = rituals; }, + // + // █████╗ ██████╗ ██████╗ ██╗ ██╗██████╗ ██████╗ █████╗ ██████╗ ██╗ ██╗ + // ██╔══██╗██╔══██╗██╔══██╗ ██║ ██║██╔══██╗██╔══██╗██╔══██╗██╔══██╗╚██╗ ██╔╝ + // ███████║██████╔╝██████╔╝ ██║ ██║██████╔╝██████╔╝███████║██████╔╝ ╚████╔╝ + // ██╔══██║██╔═══╝ ██╔═══╝ ██║ ██║██╔══██╗██╔══██╗██╔══██║██╔══██╗ ╚██╔╝ + // ██║ ██║██║ ██║ ███████╗██║██████╔╝██║ ██║██║ ██║██║ ██║ ██║ + // ╚═╝ ╚═╝╚═╝ ╚═╝ ╚══════╝╚═╝╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ + // + async()=>{ + let appLibrary = []; + // Get app library json + let appsJsonData = await sails.helpers.fs.readJson(path.join(topLvlRepoPath, '/server/mdm/maintainedapps/apps.json')); + // Then for each item in the json, build a configuration object to add to the sails.builtStaticContent.appLibrary array. + await sails.helpers.flow.simultaneouslyForEach(appsJsonData, async(app)=>{ + let appInformation = { + identifier: app.identifier, + bundleIdentifier: app.bundle_identifier, + installerFormat: app.installer_format, + }; + // Note: This method of getting information about the apps will be out of date until the JSON files in the /server/mdm/maintainedapps/testdata/ folder are updated. + let detailedInformationAboutThisApp = await sails.helpers.fs.readJson(path.join(topLvlRepoPath, '/server/mdm/maintainedapps/testdata/'+app.identifier+'.json')) + .intercept('doesNotExist', ()=>{ + return new Error(`Could not build app library configuration from testdata folder. When attempting to read a JSON configuration file for ${app.identifier}, no file was found at ${path.join(topLvlRepoPath, '/server/mdm/maintainedapps/testdata/'+app.identifier+'.json. Was it moved?')}.`); + }); + // Grab the latest information about these apps from the Homebrew API. + // let detailedInformationAboutThisApp = await sails.helpers.http.get(`https://formulae.brew.sh/api/cask/${app.identifier}.json`) + // .intercept((error)=>{ + // return new Error(`Could not build app library configuration. When attempting to send a request to the homebrew API to get the latest information about ${app.identifier}, an error occured. Full error: ${util.inspect(error, {depth: null})}`); + // }); + // let scriptToUninstallThisApp = await sails.helpers.fs.read(path.join(topLvlRepoPath, `/server/mdm/maintainedapps/testdata/scripts/${app.identifier}_uninstall.golden.sh`)) + // .intercept('doesNotExist', ()=>{ + // return new Error(`Could not build app library configuration from testdata folder. When attempting to read an uninstall script for ${app.identifier}, no file was found at ${path.join(topLvlRepoPath, '/server/mdm/maintainedapps/testdata/scripts/'+app.identifier+'_uninstall.golden.sh. Was it moved?')}.`); + // }); + // appInformation.uninstallScript = scriptToUninstallThisApp; + appInformation.version = detailedInformationAboutThisApp.version.split(',')[0]; + appInformation.description = detailedInformationAboutThisApp.desc; + appInformation.name = detailedInformationAboutThisApp.name[0]; + appLibrary.push(appInformation); + }); + builtStaticContent.appLibrary = appLibrary; + }, ]); // ██████╗ ███████╗██████╗ ██╗ █████╗ ██████╗███████╗ ███████╗ █████╗ ██╗██╗ ███████╗██████╗ ██████╗ // ██╔══██╗██╔════╝██╔══██╗██║ ██╔══██╗██╔════╝██╔════╝ ██╔════╝██╔══██╗██║██║ ██╔════╝██╔══██╗██╔════╝██╗ diff --git a/website/views/layouts/layout.ejs b/website/views/layouts/layout.ejs index 3134e14af1..7aefb13152 100644 --- a/website/views/layouts/layout.ejs +++ b/website/views/layouts/layout.ejs @@ -467,6 +467,8 @@ + + diff --git a/website/views/pages/app-details.ejs b/website/views/pages/app-details.ejs new file mode 100644 index 0000000000..c5e384b948 --- /dev/null +++ b/website/views/pages/app-details.ejs @@ -0,0 +1,88 @@ +

+
+
+
+
+ +
+ <%- thisApp.name %> +
+
+ +
+
+
+
+
<%- thisApp.name %> icon
+
+

<%- thisApp.name %>

+
+

macOS | <%- thisApp.version %>

+
+
+
+

<%- thisApp.description %>

+
+

Self-service install

+

To install <%- thisApp.name %> on your work computer:

+
    +
  1. Navigate to the Fleet Desktop icon in the OS menu bar and select My device.
  2. +
  3. From the Self-service tab, navigate to <%- thisApp.name %> and click Install.
  4. +
+

Don’t see <%- thisApp.name %> or the Fleet Desktop icon? Send a link to this page to your IT team.

+
+ +
+

Is <%- thisApp.name %> up to date?

+

Run this query in Fleet to find old versions of 1Password across all your computers:

+
+
+
SELECT 1 FROM apps WHERE bundle_identifier = '<%= thisApp.bundleIdentifier %>';
+
+
+
+
+
+

Share

+
+ Share this article on Hacker News + Share this article on LinkedIn + Share this article on Twitter +
+
+
+ Docs + REST API + Guides + +
+
+
+
+
+
+<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %> diff --git a/website/views/pages/app-library.ejs b/website/views/pages/app-library.ejs new file mode 100644 index 0000000000..8ea942f115 --- /dev/null +++ b/website/views/pages/app-library.ejs @@ -0,0 +1,64 @@ +
+
+
+
+
+

App library

+ +
+ +
+
+ <% for(let app of allApps) { %> + + +
+
+ <%- app.name %> icon +
+
+

<%- app.name %>

+

+ <%- app.version %> +

+
+
+
+ <% } %> + + + + +
+
+
+
+<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %> From a5d06f70a9246ae6d469550ad8568e8f44c58294 Mon Sep 17 00:00:00 2001 From: RachelElysia <71795832+RachelElysia@users.noreply.github.com> Date: Wed, 27 Nov 2024 15:39:55 -0500 Subject: [PATCH 57/92] Fleet API: Update resending configuration profiles API URL (#24211) --- changes/21795-resend-config-profile-api | 1 + frontend/utilities/endpoints.ts | 2 +- server/service/handler.go | 3 ++ .../service/integration_mdm_profiles_test.go | 30 ++++++++++--------- 4 files changed, 21 insertions(+), 15 deletions(-) create mode 100644 changes/21795-resend-config-profile-api diff --git a/changes/21795-resend-config-profile-api b/changes/21795-resend-config-profile-api new file mode 100644 index 0000000000..0612554c37 --- /dev/null +++ b/changes/21795-resend-config-profile-api @@ -0,0 +1 @@ +* Update resend config profile API from hosts/[hostid}/configuration_profiles/resend/{uuid} to hosts/{hostid}/configuration_profiles/{uuid}/resend \ No newline at end of file diff --git a/frontend/utilities/endpoints.ts b/frontend/utilities/endpoints.ts index a1acd94ade..312b8b110d 100644 --- a/frontend/utilities/endpoints.ts +++ b/frontend/utilities/endpoints.ts @@ -52,7 +52,7 @@ export default { HOST_UNLOCK: (id: number) => `/${API_VERSION}/fleet/hosts/${id}/unlock`, HOST_WIPE: (id: number) => `/${API_VERSION}/fleet/hosts/${id}/wipe`, HOST_RESEND_PROFILE: (hostId: number, profileUUID: string) => - `/${API_VERSION}/fleet/hosts/${hostId}/configuration_profiles/resend/${profileUUID}`, + `/${API_VERSION}/fleet/hosts/${hostId}/configuration_profiles/${profileUUID}/resend`, HOST_SOFTWARE: (id: number) => `/${API_VERSION}/fleet/hosts/${id}/software`, HOST_SOFTWARE_PACKAGE_INSTALL: (hostId: number, softwareId: number) => `/${API_VERSION}/fleet/hosts/${hostId}/software/${softwareId}/install`, diff --git a/server/service/handler.go b/server/service/handler.go index 9ea8406ad2..3cb1816e12 100644 --- a/server/service/handler.go +++ b/server/service/handler.go @@ -731,7 +731,10 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC mdmAnyMW.POST("/api/_version_/fleet/mdm/profiles", newMDMConfigProfileEndpoint, newMDMConfigProfileRequest{}) mdmAnyMW.POST("/api/_version_/fleet/configuration_profiles", newMDMConfigProfileEndpoint, newMDMConfigProfileRequest{}) + // Deprecated: POST /hosts/{host_id:[0-9]+}/configuration_profiles/resend/{profile_uuid} is now deprecated, replaced by the + // POST /hosts/{host_id:[0-9]+}/configuration_profiles/{profile_uuid}/resend endpoint. mdmAnyMW.POST("/api/_version_/fleet/hosts/{host_id:[0-9]+}/configuration_profiles/resend/{profile_uuid}", resendHostMDMProfileEndpoint, resendHostMDMProfileRequest{}) + mdmAnyMW.POST("/api/_version_/fleet/hosts/{host_id:[0-9]+}/configuration_profiles/{profile_uuid}/resend", resendHostMDMProfileEndpoint, resendHostMDMProfileRequest{}) // Deprecated: PATCH /mdm/apple/settings is deprecated, replaced by POST /disk_encryption. // It was only used to set disk encryption. diff --git a/server/service/integration_mdm_profiles_test.go b/server/service/integration_mdm_profiles_test.go index f1e2794fcf..f9e73e1eee 100644 --- a/server/service/integration_mdm_profiles_test.go +++ b/server/service/integration_mdm_profiles_test.go @@ -224,7 +224,7 @@ func (s *integrationMDMTestSuite) TestAppleProfileManagement() { s.checkMDMProfilesSummaries(t, &tm.ID, fleet.MDMProfilesSummary{Verifying: 1}, nil) // can't resend profile while verifying - res := s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/resend/%s", host.ID, mcUUID), nil, http.StatusConflict) + res := s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/%s/resend", host.ID, mcUUID), nil, http.StatusConflict) errMsg := extractServerErrorText(res.Body) require.Contains(t, errMsg, "Couldn’t resend. Configuration profiles with “pending” or “verifying” status can’t be resent.") @@ -235,7 +235,7 @@ func (s *integrationMDMTestSuite) TestAppleProfileManagement() { return err }) s.checkMDMProfilesSummaries(t, &tm.ID, fleet.MDMProfilesSummary{Pending: 1}, nil) - res = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/resend/%s", host.ID, mcUUID), nil, http.StatusConflict) + res = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/%s/resend", host.ID, mcUUID), nil, http.StatusConflict) errMsg = extractServerErrorText(res.Body) require.Contains(t, errMsg, "Couldn’t resend. Configuration profiles with “pending” or “verifying” status can’t be resent.") @@ -246,7 +246,7 @@ func (s *integrationMDMTestSuite) TestAppleProfileManagement() { return err }) s.checkMDMProfilesSummaries(t, &tm.ID, fleet.MDMProfilesSummary{Failed: 1}, nil) - _ = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/resend/%s", host.ID, mcUUID), nil, http.StatusAccepted) + _ = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/%s/resend", host.ID, mcUUID), nil, http.StatusAccepted) s.awaitTriggerProfileSchedule(t) installs, removes = checkNextPayloads(t, mdmDevice, false) require.Len(t, installs, 1) @@ -255,7 +255,7 @@ func (s *integrationMDMTestSuite) TestAppleProfileManagement() { s.checkMDMProfilesSummaries(t, &tm.ID, fleet.MDMProfilesSummary{Verifying: 1}, nil) // can't resend profile while verifying - res = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/resend/%s", host.ID, mcUUID), nil, http.StatusConflict) + res = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/%s/resend", host.ID, mcUUID), nil, http.StatusConflict) errMsg = extractServerErrorText(res.Body) require.Contains(t, errMsg, "Couldn’t resend. Configuration profiles with “pending” or “verifying” status can’t be resent.") @@ -265,7 +265,7 @@ func (s *integrationMDMTestSuite) TestAppleProfileManagement() { _, err := q.ExecContext(context.Background(), stmt, fleet.MDMDeliveryVerified, mcUUID, host.UUID) return err }) - _ = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/resend/%s", host.ID, mcUUID), nil, http.StatusAccepted) + _ = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/%s/resend", host.ID, mcUUID), nil, http.StatusAccepted) s.awaitTriggerProfileSchedule(t) installs, removes = checkNextPayloads(t, mdmDevice, false) require.Len(t, installs, 1) @@ -309,7 +309,7 @@ func (s *integrationMDMTestSuite) TestAppleProfileManagement() { s.checkMDMProfilesSummaries(t, &tm.ID, fleet.MDMProfilesSummary{Verifying: 1}, nil) // can't resend declaration while verifying - res = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/resend/%s", host.ID, declUUID), nil, http.StatusConflict) + res = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/%s/resend", host.ID, declUUID), nil, http.StatusConflict) errMsg = extractServerErrorText(res.Body) require.Contains(t, errMsg, "Couldn’t resend. Configuration profiles with “pending” or “verifying” status can’t be resent.") @@ -319,7 +319,7 @@ func (s *integrationMDMTestSuite) TestAppleProfileManagement() { _, err := q.ExecContext(context.Background(), stmt, fleet.MDMDeliveryVerified, declUUID, host.UUID) return err }) - _ = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/resend/%s", host.ID, declUUID), nil, http.StatusAccepted) + _ = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/%s/resend", host.ID, declUUID), nil, http.StatusAccepted) checkDDMSync(mdmDevice) s.checkMDMProfilesSummaries(t, &tm.ID, fleet.MDMProfilesSummary{Verifying: 1}, nil) s.lastActivityMatches( @@ -342,18 +342,18 @@ func (s *integrationMDMTestSuite) TestAppleProfileManagement() { s.checkMDMProfilesSummaries(t, &tm.ID, expectedTeamSummary, &expectedTeamSummary) // can't resend profile from another team - res = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/resend/%s", host.ID, mcUUID), nil, http.StatusNotFound) + res = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/%s/resend", host.ID, mcUUID), nil, http.StatusNotFound) errMsg = extractServerErrorText(res.Body) require.Contains(t, errMsg, "Unable to match profile to host") // add a Windows profile, resend not supported when host is macOS wpUUID := mysql.InsertWindowsProfileForTest(t, s.ds, 0) - res = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/resend/%s", host.ID, wpUUID), nil, http.StatusUnprocessableEntity) + res = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/%s/resend", host.ID, wpUUID), nil, http.StatusUnprocessableEntity) errMsg = extractServerErrorText(res.Body) require.Contains(t, errMsg, "Profile is not compatible with host platform") // invalid profile UUID prefix should return 404 - res = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/resend/%s", host.ID, "z"+uuid.NewString()), nil, http.StatusNotFound) + res = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/%s/resend", host.ID, "z"+uuid.NewString()), nil, http.StatusNotFound) errMsg = extractServerErrorText(res.Body) require.Contains(t, errMsg, "Invalid profile UUID prefix") @@ -3583,7 +3583,7 @@ func (s *integrationMDMTestSuite) TestWindowsProfileManagement() { checkHostDetails(t, host, globalProfiles, fleet.MDMDeliveryVerifying) // can't resend a profile while it is verifying - res := s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/resend/%s", host.ID, globalProfiles[0]), nil, http.StatusConflict) + res := s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/%s/resend", host.ID, globalProfiles[0]), nil, http.StatusConflict) errMsg := extractServerErrorText(res.Body) require.Contains(t, errMsg, "Couldn’t resend. Configuration profiles with “pending” or “verifying” status can’t be resent.") @@ -3650,6 +3650,7 @@ func (s *integrationMDMTestSuite) TestWindowsProfileManagement() { }, nil) // can resend a profile after it has failed + // purposefully using deprecated path for backwards compatibility _ = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/resend/%s", host.ID, globalProfiles[0]), nil, http.StatusAccepted) verifyProfiles(mdmDevice, 1, false) // trigger a profile sync, device gets the profile resent @@ -3683,11 +3684,12 @@ func (s *integrationMDMTestSuite) TestWindowsProfileManagement() { checkHostDetails(t, host, teamProfiles, fleet.MDMDeliveryVerifying) // can't resend a profile while it is verifying - res = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/resend/%s", host.ID, teamProfiles[0]), nil, http.StatusConflict) + res = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/%s/resend", host.ID, teamProfiles[0]), nil, http.StatusConflict) errMsg = extractServerErrorText(res.Body) require.Contains(t, errMsg, "Couldn’t resend. Configuration profiles with “pending” or “verifying” status can’t be resent.") // can't resend a profile from the wrong team + // purposefully using deprecated path for backwards compatibility res = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/resend/%s", host.ID, globalProfiles[0]), nil, http.StatusNotFound) errMsg = extractServerErrorText(res.Body) require.Contains(t, errMsg, "Unable to match profile to host.") @@ -3761,7 +3763,7 @@ func (s *integrationMDMTestSuite) TestWindowsProfileManagement() { }, nil) // can resend a profile after it has failed - _ = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/resend/%s", host.ID, teamProfiles[0]), nil, + _ = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/%s/resend", host.ID, teamProfiles[0]), nil, http.StatusAccepted) verifyProfiles(mdmDevice, 1, false) // trigger a profile sync, device gets the profile resent checkHostProfileStatus(t, host.UUID, teamProfiles[0], fleet.MDMDeliveryVerifying) // profile was resent, so back to verifying @@ -3783,7 +3785,7 @@ func (s *integrationMDMTestSuite) TestWindowsProfileManagement() { verifyProfiles(mdmDevice, 0, false) // can't resend a macOS profile to a Windows host - res = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/resend/%s", host.ID, mcUUID), nil, http.StatusUnprocessableEntity) + res = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/%s/resend", host.ID, mcUUID), nil, http.StatusUnprocessableEntity) errMsg = extractServerErrorText(res.Body) require.Contains(t, errMsg, "Profile is not compatible with host platform") } From 6fd3ebf4cf347eda851ad98bd953e0456684edd8 Mon Sep 17 00:00:00 2001 From: Rachael Shaw Date: Wed, 27 Nov 2024 15:21:16 -0600 Subject: [PATCH 58/92] Docs v4.60.0 (#24197) --- articles/enforce-disk-encryption.md | 40 ++++-- articles/linux-disk-encryption-end-user.md | 56 +++++++++ docs/REST API/rest-api.md | 114 +++++------------- handbook/company/pricing-features-table.yml | 2 +- .../images/articles/fedora-1-1200x675@2x.png | Bin 0 -> 250115 bytes .../images/articles/fedora-2-1200x675@2x.png | Bin 0 -> 191468 bytes .../images/articles/ubuntu-1-1200x675@2x.png | Bin 0 -> 166284 bytes .../images/articles/ubuntu-2-1200x675@2x.png | Bin 0 -> 178774 bytes website/config/routes.js | 1 + 9 files changed, 120 insertions(+), 93 deletions(-) create mode 100644 articles/linux-disk-encryption-end-user.md create mode 100644 website/assets/images/articles/fedora-1-1200x675@2x.png create mode 100644 website/assets/images/articles/fedora-2-1200x675@2x.png create mode 100644 website/assets/images/articles/ubuntu-1-1200x675@2x.png create mode 100644 website/assets/images/articles/ubuntu-2-1200x675@2x.png diff --git a/articles/enforce-disk-encryption.md b/articles/enforce-disk-encryption.md index 8dd2b6419f..564bf71a91 100644 --- a/articles/enforce-disk-encryption.md +++ b/articles/enforce-disk-encryption.md @@ -2,21 +2,19 @@ _Available in Fleet Premium_ -In Fleet, you can enforce disk encryption for your macOS and Windows hosts. +In Fleet, you can enforce disk encryption for your macOS and Windows hosts, and verify disk encryption for Ubuntu Linux and Fedora Linux hosts. -> Apple calls this [FileVault](https://support.apple.com/en-us/HT204837) and Microsoft calls this [BitLocker](https://learn.microsoft.com/en-us/windows/security/operating-system-security/data-protection/bitlocker/). +> Apple calls this [FileVault](https://support.apple.com/en-us/HT204837), Microsoft calls this [BitLocker](https://learn.microsoft.com/en-us/windows/security/operating-system-security/data-protection/bitlocker/), and Linux typically uses [LUKS](https://en.wikipedia.org/wiki/Linux_Unified_Key_Setup) (Linux Unified Key Setup). -When disk encryption is enforced, hosts’ disk encryption keys will be stored in Fleet. +When disk encryption is enforced, hosts' disk encryption keys will be stored in Fleet. -For macOS hosts that automatically enroll, disk encryption is enforced during Setup Assistant. - -For Windows, disk encryption is enforced on the C: volume (default system/OS drive). +For macOS hosts that automatically enroll, disk encryption is enforced during Setup Assistant. For Windows, disk encryption is enforced on the C: volume (default system/OS drive). On Linux, encryption requires user interaction to encrypt the device with LUKS. ## Enforce disk encryption You can enforce disk encryption using the Fleet UI, Fleet API, or [Fleet's GitOps workflow](https://github.com/fleetdm/fleet-gitops). -Fleet UI: +#### Fleet UI: 1. In Fleet, head to the **Controls > OS settings > Disk encryption** page. @@ -24,7 +22,9 @@ Fleet UI: 3. Check the box next to **Turn on** and select **Save**. -Fleet API: API documentation is [here](https://fleetdm.com/docs/rest-api/rest-api#update-disk-encryption-enforcement). +#### Fleet API: + +API documentation is [here](https://fleetdm.com/docs/rest-api/rest-api#update-disk-encryption-enforcement). ### Disk encryption status @@ -42,10 +42,28 @@ In the Fleet UI, head to the **Controls > OS settings > Disk encryption** tab. Y * Removing enforcement (pending): the host will receive the MDM command to remove the disk encryption profile when the host comes online. -* Failed: hosts that are failed to enforce disk encryption. +* Failed: hosts that failed to enforce disk encryption. You can click each status to view the list of hosts for that status. +## Enforce disk encryption on Linux + +To enforce disk encryption on Ubuntu Linux and Fedora Linux devices, Fleet supports Linux Unified Key Setup (LUKS) for encrypting volumes. + +1. Share [this step-by-step guide](https://fleetdm.com/learn-more-about/encrypt-linux-device) with end users setting up a work computer running Ubuntu Linux or Fedora Linux. + +> Note that full disk encryption can only enabled during operating system setup. If the operating system has already been installed, the end user will be required to re-install the OS to enable disk encryption. + +2. Once the user encrypts the disk, Fleet will initiate a key escrow process through Fleet Desktop: + * Fleet Desktop prompts the user to enter their current encryption passphrase. + * A new encryption passphrase is generated and added as a LUKS keyslot for the encrypted volume. + * The new passphrase is securely stored in Fleet. + +3. Fleet verifies that the encryption is complete, and the key has been escrowed. Once successful, the host's status will be updated to "Verified" in the disk encryption status table. + +> Note: LUKS allows multiple passphrases for decrypting the volume. The original passphrase remains active along with the escrowed passphrase created by Fleet. + + ## View disk encryption key How to view the disk encryption key: @@ -54,6 +72,8 @@ How to view the disk encryption key: 2. On the **Host details** page, select **Actions > Show disk encryption key**. +> This action is logged in the activity log for security auditing purposes. + ## Migrate macOS hosts When migrating macOS hosts from another MDM solution, in order to complete the process of encrypting the hard drive and escrowing the key in Fleet, your end users must log out or restart their device. @@ -65,4 +85,4 @@ Share [these guided instructions](https://fleetdm.com/guides/mdm-migration#how-t - + diff --git a/articles/linux-disk-encryption-end-user.md b/articles/linux-disk-encryption-end-user.md new file mode 100644 index 0000000000..03afad69e8 --- /dev/null +++ b/articles/linux-disk-encryption-end-user.md @@ -0,0 +1,56 @@ +# Encrypt your Fleet-managed Linux device + +> This guide is intended for new device setup. If the operating system has already been installed without enabling disk encryption, you will need to re-install in order to turn on full disk encryption. + + +LUKS (Linux Unified Key Setup) is a standard tool for encrypting Linux disks. It uses a "volume key" to encrypt your data, and this key is protected by passphrases. LUKS supports multiple passphrases, allowing you to securely share access or recover encrypted data. Fleet uses LUKS to ensure that only authorized users can access the data on your work computer. + +Fleet securely stores a passphrase to ensure that the data on your work computer is always recoverable. To get your computer set up for key escrow, you will first need to enable disk encryption on your end, then provide your encryption passphrase to Fleet. + +Follow the steps below to get set up. + + +## 1. Enable encryption during installation + + #### Ubuntu Linux + + - When installing Ubuntu, choose the option to "Use LVM with encryption." + - Set a strong passphrase when prompted. This passphrase will be used to encrypt your disk and is separate from your login password. + + ![Ubuntu setup "How do you want to install Ubuntu?" screen](../website/assets/images/articles/ubuntu-1-1200x675@2x.png) + + ![Ubuntu setup: Advanced features > Use LVM and encryption](../website/assets/images/articles/ubuntu-2-1200x675@2x.png) + + #### Fedora Linux + + - During Fedora installation, under **Installation destination** > **Encryption** select the "Encrypt my data" checkbox. + - Enter a secure passphrase when prompted. + + ![Fedora setup "Installation summary" screen](../website/assets/images/articles/fedora-1-1200x675@2x.png) + ![Fedora setup: Installation destination > Encryption > Encrypt my data](../website/assets/images/articles/fedora-2-1200x675@2x.png) + +## 2. Verify encryption + + - Once installation is complete, verify that your disk is encrypted by running: + ```bash + lsblk -o NAME,MOUNTPOINT,TYPE,SIZE,FSUSED,FSTYPE,ENCRYPTED + ``` + - **Ubuntu Linux**: Look for the root (`/`) partition, and confirm it is marked as encrypted. + - **Fedora Linux**: Ensure the `/` (root) and `/home` partitions are encrypted. + +## 3. Escrow your key with Fleet + + - Open Fleet Desktop. If your device is encrypted, you'll see a banner prompting you to escrow the key. + - Click **Create key**. Enter your existing encryption passphrase when prompted. + - Fleet will generate and securely store a new passphrase for recovery. + +Now, your encryption status will update to "verified" in Fleet Desktop, meaning that your recovery key has been successfully stored. + + + + + + + + + \ No newline at end of file diff --git a/docs/REST API/rest-api.md b/docs/REST API/rest-api.md index 579dd9772d..0f55c9c135 100644 --- a/docs/REST API/rest-api.md +++ b/docs/REST API/rest-api.md @@ -500,6 +500,19 @@ for pagination. For a comprehensive list of activity types and detailed informat "status": "failed_install" } }, + { + "created_at": "2021-07-29T14:40:27Z", + "id": 21, + "actor_full_name": "name", + "actor_id": 1, + "actor_gravatar": "", + "actor_email": "name@example.com", + "type": "created_team", + "details": { + "team_id": 2, + "team_name": "Apples" + } + }, { "created_at": "2021-07-30T13:41:07Z", "id": 24, @@ -541,80 +554,6 @@ for pagination. For a comprehensive list of activity types and detailed informat "team_name": "Oranges" } }, - { - "created_at": "2021-07-29T14:40:27Z", - "id": 21, - "actor_full_name": "name", - "actor_id": 1, - "actor_gravatar": "", - "actor_email": "name@example.com", - "type": "created_team", - "details": { - "team_id": 2, - "team_name": "Apples" - } - }, - { - "created_at": "2021-07-27T14:35:08Z", - "id": 20, - "actor_full_name": "name", - "actor_id": 1, - "actor_gravatar": "", - "actor_email": "name@example.com", - "type": "created_pack", - "details": { - "pack_id": 2, - "pack_name": "New pack" - } - }, - { - "created_at": "2021-07-27T13:25:21Z", - "id": 19, - "actor_full_name": "name", - "actor_id": 1, - "actor_gravatar": "", - "actor_email": "name@example.com", - "type": "live_query", - "details": { - "targets_count": 14 - } - }, - { - "created_at": "2021-07-27T13:25:14Z", - "id": 18, - "actor_full_name": "name", - "actor_id": 1, - "actor_gravatar": "", - "actor_email": "name@example.com", - "type": "live_query", - "details": { - "targets_count": 14 - } - }, - { - "created_at": "2021-07-26T19:28:24Z", - "id": 17, - "actor_full_name": "name", - "actor_id": 1, - "actor_gravatar": "", - "actor_email": "name@example.com", - "type": "live_query", - "details": { - "target_counts": 1 - } - }, - { - "created_at": "2021-07-26T17:27:37Z", - "id": 16, - "actor_full_name": "name", - "actor_id": 1, - "actor_gravatar": "", - "actor_email": "name@example.com", - "type": "live_query", - "details": { - "target_counts": 14 - } - }, { "created_at": "2021-07-26T17:27:08Z", "id": 15, @@ -2543,11 +2482,13 @@ the `software` table. | bootstrap_package | string | query | _Available in Fleet Premium_. Filters the hosts by the status of the MDM bootstrap package on the host. Valid options are 'installed', 'pending', or 'failed'. | | os_settings | string | query | Filters the hosts by the status of the operating system settings applied to the hosts. Valid options are 'verified', 'verifying', 'pending', or 'failed'. **Note: If this filter is used in Fleet Premium without a team ID filter, the results include only hosts that are not assigned to any team.** | | os_settings_disk_encryption | string | query | Filters the hosts by the status of the disk encryption setting applied to the hosts. Valid options are 'verified', 'verifying', 'action_required', 'enforcing', 'failed', or 'removing_enforcement'. **Note: If this filter is used in Fleet Premium without a team ID filter, the results include only hosts that are not assigned to any team.** | -| populate_software | boolean | query | If `true`, the response will include a list of installed software for each host, including vulnerability data. (Note that software lists can be large, so this may cause significant CPU and RAM usage depending on page size and request concurrency.) | +| populate_software | string | query | If `false` (or omitted), omits installed software details for each host. If `"without_vulnerability_details"`, include a list of installed software for each host, including which CVEs apply to the installed software versions. `true` adds vulnerability description, CVSS score, and other details when using Fleet Premium. See notes below on performance. | | populate_policies | boolean | query | If `true`, the response will include policy data for each host. | > `software_id` is deprecated as of Fleet 4.42. It is maintained for backwards compatibility. Please use the `software_version_id` instead. +> `populate_software` returns a lot of data per host when set, and drastically more data when set to `true` on Fleet Premium. If you need vulnerability details for a large number of hosts, consider setting `populate_software` to `without_vulnerability_details` and pulling vulnerability details from the [Get vulnerability](#get-vulnerability) endpoint, as this returns details once per vulnerability rather than once per vulnerability per host. + If `software_title_id` is specified, an additional top-level key `"software_title"` is returned with the software title object corresponding to the `software_title_id`. See [List software](#list-software) response payload for details about this object. If `software_version_id` is specified, an additional top-level key `"software"` is returned with the software object corresponding to the `software_version_id`. See [List software versions](#list-software-versions) response payload for details about this object. @@ -5729,12 +5670,12 @@ Get aggregate disk encryption status counts of macOS and Windows hosts enrolled ```json { - "verified": {"macos": 123, "windows": 123}, - "verifying": {"macos": 123, "windows": 0}, - "action_required": {"macos": 123, "windows": 0}, - "enforcing": {"macos": 123, "windows": 123}, - "failed": {"macos": 123, "windows": 123}, - "removing_enforcement": {"macos": 123, "windows": 0}, + "verified": {"macos": 123, "windows": 123, "linux": 13}, + "verifying": {"macos": 123, "windows": 0, "linux": 0}, + "action_required": {"macos": 123, "windows": 0, "linux": 37}, + "enforcing": {"macos": 123, "windows": 123, "linux": 0}, + "failed": {"macos": 123, "windows": 123, "linux": 0}, + "removing_enforcement": {"macos": 123, "windows": 0, "linux": 0} } ``` @@ -7656,6 +7597,9 @@ Returns a list of global queries or team queries. | team_id | integer | query | _Available in Fleet Premium_. The ID of the parent team for the queries to be listed. When omitted, returns global queries. | | query | string | query | Search query keywords. Searchable fields include `name`. | | merge_inherited | boolean | query | _Available in Fleet Premium_. If `true`, will include global queries in addition to team queries when filtering by `team_id`. (If no `team_id` is provided, this parameter is ignored.) | +| compatible_platform | string | query | Return queries that only reference tables compatible with this platform (not a strict compatibility check). One of: `"macos"`, `"windows"`, `"linux"`, `"chrome"` (case-insensitive). | +| page | integer | query | Page number of the results to fetch. | +| per_page | integer | query | Results per page. | #### Example @@ -7744,7 +7688,12 @@ Returns a list of global queries or team queries. "total_executions": null } } - ] + ], + "meta": { + "has_next_results": true, + "has_previous_results": false + }, + "count": 200 } ``` @@ -9367,6 +9316,7 @@ Returns information about the specified software. By default, `versions` are sor } }, "app_store_app": null, + "counts_updated_at": "2024-11-03T22:39:36Z", "source": "apps", "browser": "", "hosts_count": 48, diff --git a/handbook/company/pricing-features-table.yml b/handbook/company/pricing-features-table.yml index 42b3e306f6..4425471881 100644 --- a/handbook/company/pricing-features-table.yml +++ b/handbook/company/pricing-features-table.yml @@ -377,7 +377,7 @@ # ║╣ ║║║╠╣ ║ ║╠╦╝║ ║╣ ║║║╚═╗╠╩╗ ║╣ ║║║║ ╠╦╝╚╦╝╠═╝ ║ ║║ ║║║║ # ╚═╝╝╚╝╚ ╚═╝╩╚═╚═╝╚═╝ ═╩╝╩╚═╝╩ ╩ ╚═╝╝╚╝╚═╝╩╚═ ╩ ╩ ╩ ╩╚═╝╝╚╝ - industryName: Enforce disk encryption - 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). + description: Encrypt system drives on macOS, Windows, and Linux, 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] diff --git a/website/assets/images/articles/fedora-1-1200x675@2x.png b/website/assets/images/articles/fedora-1-1200x675@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ec32e32b0647a45f47617e6f2ae3b575c8292c16 GIT binary patch literal 250115 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%0Du@IN|Nx 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 +WJ4n)r{NAwQ5jeC_6E9H7et zJzG#oP8}lBTNxR?dRM0fajDyxl^jXoXRbW?Xb`J`$GKRh9$-n=8TooBwTu0Br0W5O zu-cua-hE`yUE>rqT<~`GN2#%4KF^6-cCN4a$b<=@E9-s+g$SOci{@cj8|ANGOJjr(6dkFK1UgWs>oz~{fsSs1?sexw zo(<^BTC}{t@uC_faPv@!NQVnI@l55^J%ueNbW=2yXC(kT;?Y@Ct6ly^R4i_!it&WXj9>8>Z zP0pw|MGmRP&I|CmlI)Gtge~dX?qZ9AY6~9axLgGI|5-k@l+a)|AIQ3ei`+*#+|YZ{ zm+-(=5=68==MOD=|Jd6lZ1uA)9wqh^Q8LNkPHcjKTT3}`Ct|`8#(a%7;+KRR*Bnl) zg6qP7rXr54vT~Qic6X0fHjhTLdwx;P->4$+n70BbmHOPVAWwL^%*uW9A$pU=W8xBG~pLCc+Y+R5*+eLk$QZsFvpiRhiH@hXvr|6<4F`YGShZ)0`u9Zj2@a))bZ&1WA1xDLtI|yE$(kTokw>L=dk~tzG_IhD0G6WkiyGcd;De?^ zk|;&p2|u`)#BM6S%=*b3>Sz| zYC@Is2e#MagrHxq4*%YwOv;DzfBj&R2u-Z7nJfP+ym~jp3ZA~xIjTS=y~j%JrZ*{M z+6wCW78{=PCaN6=J!l^)z^M^(z9=%Dc5S`L^7d$L70xNCelQD4_|F6DE4jNFrDOtE z;tF1eqSA~Mja^L}P-IrymRxX3l{&>67MVvb%P|fWxh1uO?@n)h0`JBmW@JzH0Gfr% zX=h#A%+I7*oyKBf#<+|X*t^J5-xn6xZq1D;4fk-pg+?EJI>OxP7Il1;NL&^ZR}YJ1 zh!M!%>5#C3&+9s)0U$qAk4!y&5oa{C<9ExMMa4-Ttuy?`W7k0℘(pXrY9&=pA;a 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

-+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 tkM=VE`#SyCCofa@A5lsYyX1{|hqnC~yD( literal 0 HcmV?d00001 diff --git a/website/assets/images/articles/fedora-2-1200x675@2x.png b/website/assets/images/articles/fedora-2-1200x675@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..1d533640c2b93d3f87662e28c36b5836f9860f2e GIT binary patch literal 191468 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 x<=@-$UHYF)@_{dMcZ|sxE$0^CL0daw!EIj7} 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 V%zZYmC-^XS^7!dvB$r>V{tfWrJ~{vZ literal 0 HcmV?d00001 diff --git a/website/assets/images/articles/ubuntu-1-1200x675@2x.png b/website/assets/images/articles/ubuntu-1-1200x675@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e6c1f3edf89b5bc3911d567d22587b740dc56353 GIT binary patch literal 166284 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

IT+ooBo>>LShZO$#nkLfp`=25kF zVMYqRSRSrM6$g$p?Eu^*(8EbQWL4ZyF&|hNr;mEmNJIXFQmM?sUVckJj8q`>h&Ct^ zXrZ^$oBPEf>DW_Y&}pD7?Jozy+qX)4-RJ72OQHKCU@S%xh2yH>x+O`vRa7&hpUk(y zQ+UWB(2%Be=zw*Ie-ZhZdn38kXe)S=^A{Thh~H~|DP+z?KD||dhS4Cgg zbIVfQT#`N^`;6Vv_r_+XN#4f3Nz!~&@l+uHCgNdfMNL>5e}nt1;`a8olvf)P@pW*J z?tD(9?7WE@19TIlDe)ChHi$M3+27If%$Nb^K^u?R%)4*cZ>IkLQ1zBkQN8c?unI~^ zr!)*HIf8T!F?35K0@7VdBQb=uLrO^sNOuUx(A|w7-QE2hzWVvE=l71q;sp%nT=%~B zwFAMqrS1!F(#@m_EyuA18?Dq_tfRO_f{6au1&bAwlyF>KU#DZ7uJ8nDQ_nKw;Y(_> zrtF7stgG*h#(4`Bk}unx_ir?~f;v2krS}HLH#|nj`b{K;_46v5uc}GNs1%D0V4DyC zp?N^m3(0|5l^8idP9dU ztd;e!EoV2pwDTNu-Sr?va25g8M*1MD80}t}oj?{W;|L&Qp}jwucXUB0Q52}OMVFUx zLjWaI^hVDXI1-qGWEB`VaZL`1Y9t7nNXCf$85ndmKF03K37U4lrb}iI4Xl*mt4!Io zYZObhpDtg$M4lZQQgKRBPb3BGka0*9)Zr3cFdTgG_<_r$ZNs%MjxQwEIZq|63DJ`2yuJ+CzK&g27av?X#)-|wJ;&iU9^Q*@*exPvlJAK= zPEejJ=1b5{{G@&>f0KLE@fe#X9wIx?aWUp{GS@_bZRZNT@9zbBU+Z!U|0KMAMk(ES z;ouVg-9)`uV^h_6m?X#IWdg0Cj?k)k__Pd@hW$k95MZZy(lK@-F{f8HBnn>pe?Y$A zA9e|oD0`*KbHevIYERnT>eW{L2k7WFm88#lQ`e%6!@jhF&)F!G&|XBn8qLhXM0MS%6(NP}7>ywCIJ#<&VUj;(fwG2{uY8MlrAgjR>HKX~ z&q;v~YCSxdZ``PJ0%eAFQLA|<0{?M z2)d2<@XcspIIrYJla%tanq$GyM=euG5&H!C*XNQU;(Q382!fU^ib_-o5)P=nJFoYf z)7u_)aRr4%MYdAJrBE=lzJ8v1n^K62L$2{K*^yH|o;*Llx-gAmJ-1Q+ zcgR-%>d^Z&Y11NQwBrJbax>5=&7nr$_@pS#x!tMcd6Kfp6KEkyjmfNvAvvWd9!1=p zBfb$DOPFv!0Ku6_gMkO!s?{6*A4Ey|hrBtIi;qN+by5v*QH!?7R9SD%fPGWUY_;G~ z>TDJ_f7(RUYXS)7dIqr9ul2q6acZvzE;o24kDn9*ZHM&GLZBszu53UK7Hk4}G?`|j z{kr0UNhtLW2t56eDf=R{BHs9m^b_jF)WJ+nB@ev?$j=;88g@2rzkCR|LmHadyl*7* z)0KySJN zH~%XuqLxQbzQijk-@je;XARW_I^Np^0135ztpg2=!N$%mG5{zq1K51LU@VHy0At7i zY=i|^2<4#0toi^qlfTAWoj@e1Qu*{-BU4}o;)NQ@F|M@fwx!4}l;&tW%_~rjIs{t9hX#eN}zLmAjxi85QrB!lb)LcSBDRZR{2tmZt)6>Mph82*UkOD=K zA;6I61h!>aKYp;FnP_?dAOK$^irXypYu`u=p+I+$94(WiYDf+NL?xYzEt*hMRzIh? zB2s}o%)8>x=Qo(w645YuQvb3z0(3i#ynx}u!wt_1&MVLT_T{u>u+ylkbB7(5CfqwuhClJ0rD-o=ht2YdR0@1Xf7YCB_E4gm<7SLd{g;=tQgyxow zNi(U)I+Z57!R@TVPK?PURAKXgE20C&b$GEho>=cJ%!54_C5*3ZY;E+Cv=^3FqLJR% z```Qdzim|F>3@#ML9wTqzL(F|mz;?1jEgXK}Gt}Prm_ZI5# zvF-Ik5A`Oq+}3qnfC0Cb0WEX4;uh$IS@g$Id<6#Q|L@n*>HK$EU`C__I<$KLJ|;Zl zhYIfchKAAokP?SB0mmiDotY{YpnNqJnf-_26eI)!sDm8H3mjb4*g|weOvb|mN@;pB zZA-gsds*32?A1qMOiIe_LvX)Z=O2Q0hC)={>!K04O|xazUewiu9a$9`Awf*gdE>vq zmjC@aX21WDFFf2KWe)B24vP@|CNF*<#p}zjr8up(p$~5Zv=F!j(SeDSbbcrfWtO_F z1VqD#Op5F@X)Eb>pR88~1qCgj=UWg3=gVE5iR^@-0FZjPp9Snw!YFrc*ReOhw~n>6ZU=nYVaUzvS`b*S%KcAB)XMjyWeXSXbqG zYC9n=z_~9jIuOQrvR#Wmny_5lIDF;YNY~+vp4Om3)ZgE~7xll(PhU{mU+`x`;>KAZ zTOyyp-u^w^hqT7-w;Z7-#4(BuWQmc%3Qi_P4oWNW1;UF^8UCwhgZ=4F(O zD={d8DyGDDSD6-Nw3r7UdKiFU`kIorpZ&F4~`=Mt|Z(w&cL)GPq^B6$k6zyEHZ6G zu*~pQz($?rF>SI=5${x|lVGZ1A+Nc~qpm4C0@9L)eUF{O&-6p<0RYZ(!c?CtZQ|@M zpzDmKXaV>w?hcH90o8@@M416?g+F@HRh%Tt0A6kjrt%?~1xn8+-+Hn|Y`#G1e=Yr< zT=DY!?5qRMs65h(6)SrB%`;%tg{?PI%kTP296`_vlQk4?6mK*H>s17>I3#qp{A@yt zo;CQ5WAygOi89@s05zh-pn<5UsAyB?pPUwK2Kwg%onS+s>_BSC$jGP#-jj#aVY|87 zm?rb`pwhsSpGR^JWK*N8vKxd_yO=)l(@0>y2NYjbK>-Yw-at}R>*rCGB8#R*;|0SG zfEZg)NCiQ?LmgFDiK!Zue{_l32M`D}?0hWk0=&FYB`FcPC^g304u&?#B%cSC-g7U8 z#8n_A0bI*R5_Qh_KlME%E2izQ6#gtz`;&yoZUYEH@cAi*m^V()4P03y6gYx&%7jfC zr=wYjIBajOCYzPc(RPqio@w(yMducB59)ki*Y?gE#q;o)Jh zx$ocPiawA3wAp%Y)ICUI44T>wouuUC3fNFIS%9Sn%+^hAGPM(d+TT!i$sTV7cXQ4k znHr1t>Kn7RAUP_^4e@AvAZsyFSdu*Y;UJXuJ}J3-6JiRfAAB$LoMENGq2LZ1a=LkUV4jkT`Us)<;#@Z8WyPf^l$+pTloMf@koClP z7}YiF+x&4p9HhcsZjXEL#6|&P3;y>s5ck@taPGxhf$+o_n5%aP$I5G*eCf>pe1PZ0uaxtQJr$! zwKqRN7Dgkb*K^p5)cqI=DWV}YwF~9c+wEOvge>Ff>i6lsckWxQZfD}@)hQby2Io6X z#s33&i4p%@GPO8WKLA&STCYnd4-iSRS0EDr+~kY_eGBW)%U-0-sw)STNa(h(Mn;>q z0=k9qAJY`{0xDE5&? zMYYF|xY+a%vH@pRBIYxHU*x-RF2^rMMu_CYZlJ6$jiJd985t+swy!q>ybM8pG{*}} zq-+o1CA9x}dp3L6f)XPK51cCiP_;0?mL+YFVW#A*$4HsuwAW--n&~# z_?|$0W65u=io(Kh!&0uinoFx=j%hi#hJA+%@Akf;a$!Q_)B6Q}SRFSPMefHzsUud^;UW6R=vw0#-jzJ<0(4o?+Qd27SSwQC61 z-=3DT%8@WH)MUc!F)et7_F6!Qpr*cIxh3Mq@W;*Gkn7)N?iZ>qiqAXZfsylOqIfW_ zRUI9`c!y$z4uC<0?us$?_XAQ!LPF}+kv2Lq!|&(pc;}yT_|in!g;ndEEy?c^G7mDLt@!1%{;4Bzg`yFA+5kG%Q3g%B4^M# z6{^^!4zYi;AU6D z7O;hVAxGIaWXc}F?W1IfpxHLhth6i@#_X4m6W!IJTR)TAC1}ZKux^P4hP@`o$4?1y z^JZ)(#+fj7{XN!GvP3S)T+*ctGzEd=cAP44Sh2bwHCrV(ep-KHN@-W-HtC8kQrNI1 zJJ5Vfu-qH8xjxr*R@Q^GT^12pa;&vJ8-{8%{;@*&ly@cc7?Po8oq_Y|#C1pF7H#$m zWsf8+(^(4`C$+#g?iZXVy0NBsuX()2@oj~nh`AFb-K{`pT*8j4jAbR$U#**s#=lYY zH5lbfApQFu$cJ1WxY_CVO{`TX0W7>73$d*%%L0S*W9 zZga`{0N-)(o{$t;#%aUnbD*gf|0K}`W*B}7KMG-$!OmodsNEg}iz+OWzEygshd3+~ zwwW%c2mBH7r5Rtu^?kUhMR6O zOS**}s7c3VI7NPNQ&W=*_TjeSy}&9liLcN7^+MD`&v&!evz7gqzN0tPiV7yxCF%bi`?A zsU5O0or;|!hpA}B7MsF8oBp z&OH~J0o(I;coZ$T@HxCErBrn&VE4#xpOyJLWrQ5A&vxlPiPe6ZVqobqR->XJF(wY= z4L4nhmC{WLG`9&q2)*qI#lU&N`67xF$}X3UJB;MddPZME08~m%@us8Jclq-8#-f>i zQVF;L^L*EjRf=pCAO!*xC*txx`@$L8$O<`Hh>ah$VgIV$f#3R`3`6sG=vQbByl8Er zxBI=`rRz+R|JGA^Te})f=>z8?a`k)nc=7QQh6zS*Cm4-)h?Bv!+2zqXu{Q|ofWnSfQnT7oVc5V+Xs`v5#;SE zUx&brUUAt?e`9>$o5I2RN*(^q1N#|*C88?B8OB*vvoXT$FIpo5b(vm??a_JlKbSe)^^ah99l)S?!XOBLq7Z)YDKR)f=+PRcl73m@*t?_P;7=8>D4p%X zbJ^#OIgMlXW97axq4B4r)ec!%p`fd16zr>|o*ZWYMZyw)dV&Nfe}DD{MP*uB0m5IH z1+|coc1RTg!80o8V#j4ym2D&-@CDX*y8I}|gYBC6hAbKkh1Z^da1>89A?m6~5Co8- z#RD~^9f_+j@Z)7A{kIonsu=idfvp|gf!&S_*{fphZMD(SD7^6d)8;#^uO#LvpB-St zsEOHTwE9bw{5r3Eq)yr#eSlW$T;S)ID>#9dm89iKrxd}rQpef}>J)@<&@H$u@|&N~ z%|jCYOr#g5re@wtNg{=x6?^+Yd2}amQ5jwEio)*&?v9B*&_FomAX7pv)%``MEbAk3 zV!!B)H>i(Dz8~}ApT%~j-%h9JIORNVK5yL5ae)&fKjwpgTkfguT76|Ww7Xt}Edv$G z_pW%6G(ulwB5ZM=MNbrtZhEydte~}^BM5u+Gk)f=g#N)%zNrD`_7^vQ6JG#ed$YgH z^m34wU;-4`>*N&40xiOyO=N{2NioDTkqC(;JBcKkly4NJDg%56rH#mpsFpH65W%uP zUyr)+U+YylCy)o>-VFO#;G;8#cqT~t&pF?o-`_2$>oM$C8X{Ydp}F55i{2m8sa-4I zZ;IwD;FLKkQ4=hajGl>ER`!Q2V~Lq^N6pouSyFa6%dW(=;z?< z3{lAn|8zOh1weEPTq0M-43JZrR)sKZrrXJx;=*~oo%dh?C}ov-5>0cx*m7g z1$o0nSIn#(9ETX5qw@(p`*xrL_8y$-yG4x4HL^Zjy&1OV7uU%(9PNrtVz5$$3t<_C6yrAHSF}7XLu@ zwwoqKghaxJh|o6HQMm64?7ze}$(C zkZ_f|yqZplGG0h!!W4--T5((X`W4jd4qe@TraVJxqKV8<_`>%UxF4AZe>ME|3tv1t z4Dm-xq_9DW_q3{yAY_3^s4qwwXb}*NUmrpqPw^@-+w>yy%v!SgABZ8Iow1-0*9E=u zpJM4!BKU>eaQH_^GQJEo=3U z4|Z@*TvPcMU#NOCo?yd_K%!1ZF+)+O7VD^{IJHA}%%F)kQvz+Qs4jWtfwVTAjijA% z_{6K`>nSfnkB?SxWg5BKh_+kAY-t&{+nB>_OLe z#KK|2-solk-}k9V=isxzgXrp)P?js?K(X&isUCa3o>fxJ&_h_xS}xl!)mc(rZe~-A z1dps)&Spy}eQ+uot#*>k7IyY=@;ZtbQnOr6Jb|4+Q#xbST|>sLWRSe6W@CItKK)TE zR*xmSU0Ar=UC3Q_5<)BIOX%XbmC-*%bFy)6k&Z=6r3+KXATMVJ^oYTfiPYy;ir6h&p7lPUn+u?vG3#MWJNY_ChZaK}1AZF;t zWu$%=J%mmZ3(*8y4!&F zSmS2^tOtIw9>|oaAP0AZI|CVy@*Z2ZpDnIEI5f%ux~B|8;wGkXF9N}40WdW0qqc=u zdZ3jy7KGqIi;-b8M$xx!^LqNoMwvg_tc{rUiqhz@>!3GkGpHC+`RPC`letY}1aX z&LonN+JvoYDxt`UCt)ehl?}T7tiP3=y!d5eII5rnKWwhF6MjZ2j0+zV2fgMJL3jou6)z7U=MH{p1kqBVqc?sVZR0WLrm$ zhoCU}ItwA@^qX1~&w9(_(?Qs2t@JRo6pNPisIMuXY^3c}#Z+dxoZXvj^X=gIzI-w36Q2Y6A+yb5halBJ{`fQIDAvI~>H{V|3vTs|Ky_z2(8 zBf43`n6^=;uRP75oabxnkc%nA$ijl}8Rcm^+_NTgzhj)F*^!@gUq9+>F?je*N(fsH zgnRuZoW&>}*yEavxqgtcT)$(Ac+Oy?xp-HUxK~F$Jf+4@6Q)|@t~a*t1^CLe7(Ra0 zW9xX;QHUCjlK4Dnk03bm0_E`& z`~{tl_+o8{JCbzHvWSCEyDxrlGML>U1i}NRDH9dU%k`H~8Lp=(TX3;hvSk-NPrZpo zqEj~*i+0jwKX$Cx1dxPtAMkGeakGKyu428xm`JF2zVvjY@1vJot<;Mz%5B)_AEhs; zN&oO3^$I`gRNG+v_VZ0poek=oI|9g#R#Z(x4-S!eY(!*4S4SK(6|sOD6s_4K5y{!Z z)y$%w(u3uPbiJ}r3F1dH%_pj0%`FvSi5HK~boe}d%{-z+Mw?nGs*iBs$P(#7+TNa1 z;gdU3x`u|gAnapbV98bDZ$+GC@R-F@&Jdo;B{}a61*@j>H4NX&Q0rO??zPfA&)maE7zO7*55$)xBFm0M&o$N1h2-b8Vs8g$%9-+=R-TPm|=`{DSeTvy6gFAI_vTBuh- zVO~W~uNfk;Q-y{YETe>=--^DR+1yQIUuTtM-omK{l+g@UZ-P-3ZPT0$jf;|osshWu zm(WJx?e|T-cg|`C&)l-7AlSDK#Rl(}b-x4C}q6_zCyU5(%-LF7tS$(qXm{HPN zRX99kBY`Rd?}TD(9dnr+=qXhGkHGb+IPFHiuA;*R=C1Cdh$c(+$HwM2N~wqM(mfUz zOOF~zT(g4_I>bDbMS_7%YbLpCU3t59FD4wKplb^xlwC(jFShJ50m~F)U&9q$Ua8U0nk=7Azr0y*>Lc6K zI7}yHmJb1FKr|5x_VN>k0$f^}0wb@UsV``*MDGd_iLu?5$jW#I%$0jkPB#o%w zEAQW6bkv40k|alC!CK*eKBL)78aqW936IEXzhan7l7XuHA^{r3E(dDmj1JAdQkPI- z#F!yJ3SpcIa)fs%qkDGlEA$MW`AgEEb)X7gG8|UA(JO6kSz8COxML zOqc~(InU2?ZmshN1YO1FE%sv{N4_0DZ3nifm7+b*7{Oc5afxjYAHe_UExD3Ftrg9a zOm^PS6^B-zt}=_f0YI*p3AsW;mc^TTQ}V;pX)C$9pa1Rly!xKpj-EGV`uI6{)l4bE z<0zll#3C5)eK~db627?3*D8+@HR_$wl;DTU|qIQ?GeRJ2y~urZ~2 zyn32<_p1nT2FdPQBNA7yk-YsiXJ7iqv4KU|db6^zQtogpnE(3)^o%@frK*cfDH?3l z?kQexmTN}at=Uf|&XMxr(DcHe>g;%9;~vAdxgz6#$%%-deUFF5V@1Iqae8cnq#;On z`+H36YqxjZ?}_bCkA^gq9tjboSc(e0TM>VmE;KhL@s{$P)sY`cgMy0Xm(buc>vn#5 z|H4z&(qV~rji(CU>gF8V@i|xD6%T%;p0%ti;AdK=7PcZh>^Ow>s7l667Ftmp?=?RP z)y?9AYV0KE>WK6xe~0$tx0cm$z9>=+?}=5?p09WX)XZl(tV=71)>7p7p~STMt&TO| zVn!&>ny>o8I?uDXr-)q}u%%mD@BxR`SHufVO)NlO$4(+aOB*AI$If=c7R++R$}p8w zIqCMa42|~XkekFFcU2o>g`y_cgwzzG3>TM^$OqUvj9kQrItM;inB&sH3irjmzuAC_ z%9)Tc49Q|ITSN|u;1IEnVQSbh?gTWy?k*;#JOR3qmY+|U>kn23)`4uZQ*X`M#j=$b zXCm?YWN21CpCArFtf(jkslw#gun1g@>e45<>NHm0Cy9kmmFx* z7QV6F4_p{TX(X4T+QD7LLTT&EwdD-mssB5y7Wl+)T~0#s3GJxDd6y0^9sesfIz@r> zaUa(KN1+*%^NkcOr;%QCqRm-8Mx7GPT1X4Hi+52;C!t!#x4%x7M0|$bo8=a3gP#40 z8Cj*$8m8I*xm+X7s=q8u5jGTpRN%<0&Tp6MmYPf1wtIZLe4}_Ydt}`LtipUze z8bbI-?}uw-Lu7rJCUuSZ#nc!W3p^JZ5AKLQJcERB&Pi8$jK|+UVWi z)@OrF>ps*iVaiZK*EJ#`QTOh!rtrc&q)R}7uT+`xs`$fqR^gv%)=SpH--0KFICG0D zHx|9CtTl^j$||t;POI6kvkba&-ZhGHz8&;0RLj}QeYwuzEprb%bL&?*8;vgPxqGhA zRwN0o_MwLZ<4h4)(AyULVYd?(z^WD3rVS?+ z4MP;MfW4c)JG~{!Dz?@Fgjtg8hQyk~FHGX`J7N+z43(DE8H#W@4pJeUXs|QmK7Z6; zM-2D{srk!3e3M9BqfIk^Tijm<^1@F8rGgHa#(p`bD=KPvy8WvmPS8uzfFDHFC3cGFfwyoEUz`K3(j@@lw&M zM5&evkxZgdxVl)5S_WER4RJ*^8omw?6{y%MC>e}jyX(lX7(b6M0fN#G8=9{h@*7Q$ zi?3Niom{A3pO!!RtbbIa01?nh&I1aCuLGc&0z|56wb|t#p)Ub1o~R?sq9-K~zTUiv z7c0=zR*cRfxTTzZ->TZQaZ6|$;xZzFb9Va&MO0>eT){}VstEV5r0SE<-e7@ zCOM`45oc_0ixAH0V`1zycjp9@T!ks>o+ZBxn?=-5p{NMIKgI15CJY5`CU0m^xm$qH zaeg{S1qKwi=sqM_Mg1bP6KWUG&o{+2MHTj2y`g^e*VH1{oNw(vSjRN@&y8?j*5J<1 zzwy+OH{xIKT{+RvZ*Ua0Gssu?y6}_S@!Z4ZWFa&%hH)7Dnz9H$k5KT|gnV_cvXqWB zkKk#X^M~0zy7C_0H&<)@1~J=HM3)`81o5SSe|xyp+BY^RUC6cH%1S&*=TZ<*I_MbL zbY%`}8gJcF8TkE&LEui2>E(!l1J&sKq0xEUMaci82HC+KjcxWH7J| z6(DtkLL!<8(aVTP8$dy{w_@(4wy)(E$$O_{7yEG9Xp9%rkTbe$EOVRku-jY;qnOY- zIbdfJ?{kO3oG?f4=%kZa^l2_v=VdYmr_43EkIU`q$B0i`8992Q8DUJx%!vps}PN@JI z6rsp0C_gW0#Wm07mZwgPViV>X_y_dY8P{VV&%R)7`MpZpO^FGyu1;`Vc@Irh`?#aE zzN5dHkzzqM)_~BPK&<~;(*M&V!Tw}WVACe?QNKsAKRzbnhZ6~*N)xotmj3)b*lN^x zDXc|8aK;NL^F-V?y6JFdd*A%f6mT2rX@!i?Od?T67Jm>Y+K4#W;3n3_iU|E_ zgDMdRyBqjiyAr~nRk-sWA&p~F1cg=sTeM|3J{ZhHiiGX4JTT3%JiJ-~cE8rOq4dI; z&4n84DM00Oe4Oh}-*m3z>K9b*za};2S^&J`MS?)T^sC3SbJEZ=R*l8c>ZJWCyt=c2 zyw6T`(Xf|dBiD`gEl+&ViC3DY;RI8J^`A-6gSqYo2JDn-sORcw4%`+}%f+H^z=x30c z;ad8wwfm4?ol_~Nu6K{Nx1`8Nq5#%N9Pm7;uG@5;?F8SJ_v^FwtntcxYDZj33T9!B zekk8-9eaE__#L4{dp73%%C)dQJ3`fd3uL%L&idWalRYQE+fg~nLIKgS(-x_N(+VnqbRD+vSY3?8(Ds4R-32_@KMoInvq@k7hWe_y#b2cw1bbMDjj`HOQgnE z`dR|%QJ(+g=$S`VZqlz@1IgG;p)P8Ke%rI z*@KjYTFP|&$k;LND%}$5l!@iS_8CXfj=42luf-8a@{~5Oz-zhR7fJ=OfQx=69 zc&|7NWHPV;4jfkea5j=3w44YESXusCf;=LGvw;-1Ki6~oRa8}yWjwiP+j$@okNYwb zgtI&^dR-wZKk#NJEYx1npNnRombmMyZcaIa0s2YEtl07YV4+gq=wEX-{hr6s;OW$I`k3&>43*vpBkR7EST z?nu5)H43%s?#%w4haavQJjQaFJ0QbhD*tuCbH7mo+o?)?&M$8t5NpZ$vg{CB=8#+Q zdzUHqgz-ouiiWDt2`y6wds2)@*9}kPSrbi&d$pX0yvJ!)WsIFvI8D9n0~$p*J4Jke zFaZWI^(90yv%0ekXEU02EBFlR4Vud|P;Yi3gK$e<9tvMC-pSLl@3q|Z`d)Hu)>fw5 z`y3~4{#;JEx*YUP3k1(4+&&QD>a(=U5B0*t4TTY>wI%6PFQ6-4 z+4WHLuJc|7K{umzM$fPnGUPGF>`l17OLfyimHp~FM@E_>%F6tULrP<|>TXKUYR}Rw zPeHUqi{&xV^h#nx(Z~efg5|l`^|OJB!l^vc8IkooN%-{8d8O=t?SRBgf__dH#u78+ zC$I?aEIkN^7;~THA5>PF)LoYOqCJ~#M%J`ZmJz#pg6J6uVrD@=&7RuXfQI4vJsZ{= z%Ny5xRQZAf64h`O_|fPw2GQI_tVKK4QT5d_38jG=7 zNSx01J>R+~fae##T)iy*CGwRqZ2L{}UvI;I+P?}_8EnB`%S=yAw|DW5FLe~$NIC-q z=3lP2P-9?gyMOA+MxtR&wCN5C(Al_qo!=rMO{p;e{ghdw%g=!Q!hI^gMpvsL3uklf zu{96tf;0sCu;Oyw!jzL3{f+i+ec8mfTqq-h$$JaVAjf)up4bPqH;42t`_nwXd(<6ST>na;x9UEsH^Vwbj5;3BL$RrL$-6%uv8Lke)H zIddid48C_c)BU<@z3Higa?U8JuJA{`60yQPpvxD+858ODbn*GMIf9Ia$e5C$?M1^Y z$|A~!a_qCtw(~d^Ej3*dUT2}4(CtU~7v%W3n9JIdtUoV)0hMqFY7zRO9`s%kDA!$_ zi7HW}N{nmi$cI?$CAv10mC!O_f258}ypkBkk?s{yOX)x2x;T$Ysn=9USHs&qR-Q6p z1NrNfOwgY2?S)P%-;|z!!zS{2CW#*CVvo7RWk%tOnK~P3hTSyP6By9iP93@(3yox? zK}9@oLkof7yk}-5ca36ttcoy;N#k`VnM2bI}^0)6WrB0Mf znevhRZ)$<%g_VfquFcOp(S(zw%2h7{Ib*-?Q7|*|JC~&q^#s~#V=s0q7>)AK7A#E9 z4d;nUzK*50jZ^L_9=udcekSRR2OVb2PqVb2Yj&oJs)eo_oJoDw&A_7B{+4|@hBeIy z(MY2hio|(JJEWctybK0zaJ!M-I)rH{s zr01u9iOhB#9}@nvlx-b!i?Z$PqvBhr{FVPakL9eN2eqBR?wq*!LP`#P^=1b57VQ0{ zO;b$gA!>Sa>I57DolYyyvOSAjwfu9yvs!{1W=JoI#C!c_QYV z(e+fuPSxX4suo}2iy@?Ulv`HbY(EU9#ID>nWPRK>EES0Am>rag&@z-T37nkBQG({L z9k1m~%g?V?ndTcQi*C=27rK_Gg!oBqSJk-JdTNSz5RFJG|M_-6j;>KW5kFjVi0hq{47@0f77^klko2bq^=53?fw?n!m zKV1%~ji-4d(q;!3JAjNwC2NBkYojZm+XdN(zfIJV5H%kUOd?e6y$ zZX5oWNF(9EAtC%ly636m6VsQAq56#kx;UzY2)q%+TNx5@3OUJ)`fN<<=;%+OUTV3q z!j~X6ymr;5kki^In%g<+;f{+`?yl+JS0;jJkM}D^h&5*iG7WryYs!Y@t zn;m?Vv*3Aud#*0L;q?VXxT*l;HI59@?&U1RW)TlKrC{J?k8r4KqE0hOT;Vlo$>{Kh zj+Uqm?7;70mC?w7#Uc_~_|r+U$nO1S1T3ajNQ;(4U3dsj@5$*VMI z;IDL-rRC1n+j!|5v%GS1uSPZVD zerRtbI#*hecZWvjL?`1NaxyH|h6vUUE>Gdv$DnLQo0L;c+-u!$848Oo(B)+*IIgQ* zFJ6qjc9)jSE65&ufE&LS;**Yc*Xso*o{9D zU!jkAG5dw_EOj?*DpE*g08ed6v%~rM73ReE;)+}B1;_a(nKD~qPgo4h3D%;$Hgu)Z z#)A*YHVf(H;#ok=s`2E?or^Ql0;@vONrMble^<_C!thz|jo{6=1ktN?D8SwHjr?l8 z5xs*m zWMn9$zq*(?uQFIX*s9Gykl44lNP|t}KGh@9J9qAvLB{e%w7R{hWoh-sMdzC}n{rtD zlIr?J)?q)`1ZjN_B)#@O`tn9miXD1Cg5sNs*YbsM=Dwl{?r%bzKr;^8vz zqtJC=>*oX8xc>2Vy-%KK@f+!?t$E;a>^c4pC|sF;76E>UoW;TWHIJ)GiH8whG2gvT zTa(X48kG{MKf4BScH9Ca_~~~NcqX`4FDIwSF9&RgD_ylre@AxNFW}eRED5uYBIySz zdu)`{OuBP+p8te(2iVR(T4z%Y)F^C~IKLFpoX+GjKoZ?;XN_s0DjWMC$naXxw6`9Xzfw3rc#H&YIPE)()yaSt9)ZaA* zN783#R#BVr(EUv2z-#W~6~XateZSw1Ym&c2h$dbS6=RooeOAkCP^ZoYSaOIEKP<8U7c3wq z2ON@#r%FxiM{Kq+qHeFYK2U@}&H?S=iIl?cdeYSlzaIQbFLc8xm!sbnLU(3Bo|;r~ zF^z3a;)bo#A0rBECim#B07V9f8;R@%Mw@n^SXWa<{^>eb_b&;_joPH{uq^!f|aOTiIT~wN^3%x4^@m-lmFF*!Pc~>m>f)kTh0`zicAe z<2gr%08*;Z?jP&MTOBZhNY2T+*K+V>RZn;I`d?MB%a;c0)n)lI zIbneqk}~593+Z#^+73SfdRHhZ3YZYOh^L zCH7oc(I}`ai<{84PEDKlBo<=XD;*hcS9AGDcjOw?lw{=qt2M5 zkcO0Umk(=>%7SZCZYL{%PdFzuV3|~4i(j|(`e?D#KC7g2>*vK|E}pCmLe>E z|5GLONq;LA*_z>j?#vLMqTsxz@FwjzcyZakx)M%Un4*t6{QlW^P*{PeTg{7q8ZO4ok|v&7X&^NKOg&_S>uFrT9|B`)aY;Uvu<7 zMK8{s3qCJp70~j4n=-D3teFgbUgaXtEedAm%z?C_YA{aI4bsBJz@V5%@WI<@g@uKg zvdHu3E5pq>6{6KUlo)jsSUyYyWLw^pl_72XDDhiEJn5rKvAgz4$SY1Kpne z1uXBZX}L;4`zcpFb`BtDZvlI-`1hw7>#fFM}_t(BQ8cgASwJxtwnU zSy{4-HflbNkDDbEmw1Wgo)fR2#5hZ~tZ^dC8xu(Tr(Hva!oqJ`K_{+jtI1nm$Mab> zYS~@_K*7PhwR@64ZjoWsv3YllNfKQaNos^2r7`|wh(*%v#hj(B8Rh3{RbY3}Se30W zyR;94ibIBBqRJ__3tRdS$R6pwf?6*baqZNP4ZQaUlQ1J@bEia>a4?E_uq!Y z{S$>4;UvSaG1AICu8=kvawqjKJwv_4+OO0MKFA1WcBsgf3<0h#R-*<9rkO07m1OLSSeE6!#{QWQgWT4s>1BK(5~<0&Zr! zI{8a;b*xg-bA6n(UWg*+Qvv^@^M+5knC17ZDul^2eqyP)yR~}KLbxt;tz;c zIS?kY3k*KgL*eNG!2Xy%9(Q9aD=SkUU*F7nM{XeMY;C7v1#JNf3Xh+N(>ZG3_?JY7 z^B!6Ea=UT;0#b#%s5_B@lA|1M3P4ZDQio<}QZ6gw4JWM6Ls*B}^W1~=mo?ZUv|P<0 z2Ip)QzGiz$aPqkoBIp?7_GzK#+B72td3-FP2S(#DbWM9G(ItAiW1q!oIjTB~y zUCJAnp8#}Y%gElC`E1-jgZVD{y_zp9Kl7{0c&ZA@6sWb{*#^yiIc9$oc27az( zm7DxF`!iBiXJNW*E*P-y%9Xt2_q^$D;Wveag1;M}FPZ(E9vUJ%Nc1Mp4wI#gQ){!W zyre_tz}=P5rKE%E>2Dk3QOllf{m~u*=ayie`WWecl zCf8!jU7k4lx(iCg?yBDh+TMHfAA})`{T>>Vgb_IA+2)K&z;(UaTUkWPB}vuJm`2z6 zBxSSpKj^hRukqJL)p8nIIiFpSQ>c)kukXo@la)~yk1J)%rQv3U z@)+3XuF6RM7tF!pZfB+sJ0T(7d>?*B$3?k!b@vwQ@a5!2&r%BN_h|;&h3+_bJpgeW zOvi@GOU}(EJ%;*xLsrOn)4b@sV(aTFS2Z6&rF|HMH0-bK>P+;rkQ0$?``~70Vw{m=eOVKFN_2G&EMQ*r0gQxHPDnH2Vjdxu3 zmhiieKi|XLKr+5IeeXH1+a0P8>hX$vA@JdL=JJ81nVYw++Jn{CTRS4DNSlbOw7K zhOztlVW!{7aABWo(=Pn#*$&pSVTuisV+!PebfSWf&Qr$f+MpMioTr+io0|ORA9GsO ztaNC0Z(GJAgy!Fb6X2w-v|~Hh1?eaFZFzqASmTZJ5N>Acx;H2EaH1M#*xH$gb zzd|AT2z?&nJA{N%FI~Cg+yTOJR_J5a!^$+P3=I@RH3Yf>d8Z;-)Z5ef1&8SUp_2() z4tJ{FO|=6n)3b~GNInt3xSngSYBd&m^QxjaHugHN4*p!{vTcN@oWnKW6B8?ots)bl z*mtUHN4#y31NzgZO27QC25r%jE9DtN+>axROteR@T~lJya%H3FvVmHt@y>+KeIp_kW~AO;u_VdLTPIT^~% z&fYdS7{^?WaG!8zFg;FX^oVlSZL?V&fukI{Hdwm53;xDs4lZhey?~}9UD?BEqA(p-{k1b%=!%a z+;bG_r>CBeeFho7bD4l7&^$99$o8F5B9By}&LFuA$=`W(z(C6+#KO+n3b2QiqhfWl zOm5^Hn6x#Jo4|s*3EdU&Bq#$sU9ur_^YLItaspig4czYzqGN8rv4MH3c8QpuM*jQk zz)RQ2-RYtOKH8E(kk68Ib5&H7-Vd8XgnKn7Q~0bCD3$&{9*zY5VfYQsZ+(-)+W48lB^+c&%(|WpEO>!pT^ap5su=Fv8#J=bu%U9 zfFR}8`P9kj;tQe3v5cny?(G9cJcO{0*!nbGP? z`R>VG6(jDp&z+k6<$jO1V@>})CLqY8*_mu1z?oh1??fOpl)8JyOpf4MjKtxm>5$rp zxx0;)Pm^$!m6aiCxwFX&$F;>|ckjn%)lg|g&3YbD`03!7#zyi6<0ciWg6JolHSU`0 z5*xm%cA@aMoA=#(*>`Wbei(kF^RV3O>8gnUMoCLui(`X2S~RzNrThBH+dc6HuVWT1 z>L#_*FBj*llZR%;TE5y`%>Pca%w^;acSjOZ!YVfkv=3}YcBDc>+|N6FA*b?<{9d$g z^JD+|WW6u*H(T55@$W4hM!9GCQ@1$1vJ(pub$KELz_8SiMS08(Un6|!%Ok&7&m-g zak=fOV^AMHEJ;pLk;!-6%cc0Ot^K~Vr=>#s`l{`v9bITmQ*h6$$m=y_h9Na38R|QJ z5cJzCtdLquRG5z#=Wdn-i;A<|g>S94EUedB$HqWcj!*POl1g4m#WlFNmJQFF)ZfRH zg!>5uA9`(;$6vU#k-1OVejk29c^4TCD7%K{($7}5=_g*L1mp)g;y(}HKz3TC`P-%$ zU!gjD!D0uC3>WIQdOmUPx|Ou(QyXVkVm@ZeK>(Aa$uCL`Ok``+>;<-oEpE4?x0r{S zrgPC#7SiYmd9<{de~ z?eGlY15Um{s%p@1XU`rl8bi`>3dxtfiW$84n#-Xuz%1*Y{zs zY2fLXwK({V3epCU0vHQB@B7?sAA$zN8s2RljrntYd(6I}{PPG&_v#;`UY(uE{+AB$ zmnr$VnUx6hF225vO{{sGRtC{~3NhsC@>44LZZhm5kavX(ujDAZP6rih*vqkzJSgNq)qP?}z8?yu zx^)kd{UM>O7-B;#UhE8fc=xdjr>K=&=j6B0G5<4`wU9zk$%pLWG5FrA@CMmmK7n14 zCT#1^lL?;jE%3PZ$Gd2W9&6by-n%TfF@AIh3a6C3xwhX64^fcx-qUY4njcTB4@o-B zgwQaPYzX_|Zz@e@s1^rjYs~q5#;TKUm5j;r9o>vOjVGr+US+k+sr~0JK5}tu{UsI` z&fL=J;*uio6kt$vTCnqx2FQCvzL0fq?Vg^c9e*?r(@buZ>d_m^(vu@pPuvC}iksxZ z0M7s)Ml7*Bq1(W5LnAOz12saO4uSR#Cy8kOSavQ?3a%KwE`dUiXzL7UQhntcP!}q+ z!%T`OCv?Wk-ow#UPaQSYHatt}oXOtb<(|uD2CWs_R7|Nn9Sj`c9jd7^9`fTcqCxZO z*z$K-OF*n^3jMF?%rxTTy4_*TCXvN|81J`< zn=`hywk&LH+c2hf2>zxW8V@K4Hj&tGijL_`D|?ScPo!+WKb<{`Pt;^hGwC>Tv`Sd; z5%I}-ugOZx)J>R1u-ptUezC&!@a3mYfxJyAlkK!{6VDh6yQ1!oUt`H)Q)Qn@R99`C zN?O^@&W=9lQ&hEaqT-GA*yX&X7Zwvo#&i1qSxlI!Y65ITEv`9{E3{TDw9-)2vs`ul z&BdCh8w%^wCzmc>JOwdot=FwKYjb&XOvtCWt*C0{z)5Z3@|i)1E5@uC?ly0m0jd2} z=hDAJ8o_RvAd97ci4#28^(L zARNz6L(bi%x+45jeO1HYz)5`zY>d_e)4`=b1*2v8Qz#dT6DhSG*MgLlyO!L#CgXE| zh-WPR{Lue&Vq&5xo!g+iW))?3DG`g9pFMM%^F%!GF)+o4OQsKuJGTsma-kvyjsu)- z+JM36MzHis9^c#b(H-Z<$UImPA1Z?{H5gNLTjI+*QCk@D&Gl@fQd?UO)|JPiguQLD zw4H%?>=w<@xXcwZ-&ZQ?T^emZmj}u$Mz0Aeu~E3jz95cKMm|U;(PQCgUE2Z!JMV4m zyhxRUE}mt$81k!fDIpmJ`d5BvZ#z-CH0}-1Nm&*ZfK!O>juaF+nOE)1Q=S#$N6&+&Dnkf?sK zleO1Vl7)S4A6o9~Z8fZ_`}NQQq& zyTC#J9`OjonSx8ixkt3;Bojaw{jRyu5O_0%8L)K5@~ezcRpx$A)g#5?^jYA66JI1C z)50yPnqr$ZE+p<1<$L`WZ$i}QX}#}{Y0D=UjEGk?QuBQMeK%ew**K^}jYdbI2fJX2 z#f)6&$)%L15oaG%aR;7y&eyYO)VnUx<_)_)sQ-9R_RcrVV36v8?J#;W(zGsk_dTdU z@Sd3T&7gpQ^O<3Hx(@$Dt*ic&5lSj5^}ri5l9$6Iud-`kGnkx4}SWtbiBE!?o|xmxS(kBy0L{CV;i^ z+o~Y5MiJ<1S^J2!`B9^uGTF6d+aY1amwY3Q(TQ{wI-1QsX0kdvjlF%@vP=7E+MlYb z%Qjb`i%qBG(MN@M-uBsKH9e0vqW@g8@RXDk7qMVKFkScHm%l!@VnwxOjz}!NY8gf{rbJ^;ukLvNWsCfAsk;siyoVWNo!Or)8g= zr_vneMrV+VsMAE-qwpNhN#@-<$o46$ns7LV%5%9yEWtcAO*X;E_O z)c7FFTQL54lFrR9r@BxD>G13T9q=%m`)RJ`j;ob-Y1wvg!;An%?_UZGZ{YanAx-A! zH3X{l7_uIz1X#Pc3}|UexF81!yL|pLZ4i-xZe%V*zgtD%Me%OthZl&IITd@}%z=}t z6i_9BFkAi6Q36{R^}&~ktY`dzJ<5?nRY(W_!vkq*KjyS4OQDfgNul`noZGBSa z!6!l^F|0Op27S8B`zg~edbpqMx!G09tPq}$WegAIZEhIdBqdxgEc@Zlo)-Ps76*1KU?mY3RSvAFiIsiyCbEG=lkhiQ5_0a{}*-c<}#YmVivl=Z`Duu?}Qt=S9Qf z`HfKax5*g86mUVx+~bZg;@Rkz+F%Mm@A{aUHri}_xdoD0y@ZcP@dt5=UIAUbc} zyfFa+&=#i4u>k=ARy1|f%vxmS!D3o{Fz8AiR$%h}3iE#QnxtS7PyNr$ur$Y-a)Z{@ zo%<A|xgCA98XtSHy>C(i2I>T%1N}>s^0m)Rw`GDb(a96pzNwz!R@(uhvxoE96Kf)iRV#0dOu&1IBva?MPoZ0$dA}^(80YZX6F(FqO1DR z(NS!@R(?WeD0&o?51}tF#&HYMmlUtgtsH-(YjfmSQPk?8O^$8NW%?mI@yf{w?X-S* zBtxNX{Tm5sat5%*9{$vUI_N>RdOn#|vyiXk9UI;|wydmus*Ph^Nv?VvYPGOuO#$49 z0fg;6V2fvNuB7H|;>nc_ZcoL^lQfmTP>}x|Ywq+P+(!x49g&zXhw4`9(H788)l+SL zh7QPABjC6%^V63mxJPOT@W15ad*m$?dB;8=pR^VT5gl2D^t)7wU%tF<)f88X_<&Ae zdN%;vfYSzcRzkagh+S;+B+ghS<1t0H_%qD16uyR^`wWk|u!tHLR9S+U@) zBKyh412Q$W&Pj39?G4u6a7%u~;ygBJRBl7L+x@o1$^SkcciRc?-|;^uuAFMyEaC2f zN>SK__;7uE3MMh%L?4;sWk-jkOKgG&ci4rjO9;{vAr_GYnVAj2rep=>@LP!>#H%|3!6>xRfx^VuSd-Us=Nd&jrBL?+ELef#Suo5-(0z%f?)TDvs`rn8EZqE02D72*{7XKp;SMV_o z@hk^n4K~K(RO5T{cr{T1%#SREzTF=V$j7+)J0w>2&fn-yG(Xcm!7R|c4d$tiha9zs z|Keb0SFy6n)a^F4u-IFmNKL~8npB$ZA@Ld(_v1$s;3*n5lZA=H@zvo+oZiSPa}htW^yaagX2%xu8f~_bvotI1gS3;^qRP`e;u@t z{EhEf-!KxjEFd=-z&#o>Q**IWEV%FdEmE}U{oo)Mu|X$xgL?F3UYY&&>m(zr1zXm? zG?9Cn`fmez$YpZOz@K>#PlRlBYtnhz!f36)^!jqdgb<_Wk-bPU;w6X>=D~#o4NFA0 zrpPY|g!9}PYFSYvlc6cP6mL*q94WPu%w`xiYXi2#d zVbOwXv3n#|U(^9P!GcAYIFdT16*3gDf%*4i2|GW1FZGs#ia*#iRK*PI(;rE}MZ&TW zxtSj+;gHLyzD{R2wZvfLsW~H-zFG_>9yBzh9~DKJ`>u2#b3l?z=>9|W>nUb;d5Zs9 z8Q^Ov_D@v9B5*^0mEbO*c^g0MMpt^`5Bn05m^8Z5(X*`|G3RF#5qlv}L*MB6fF>Ey zDIMgVirZ5dP6>WmJm=Ra;?cB+_+@xw4zugv{H{<}P*K16>B=r3nd&iXT!J~uq>y*?~)30uU z>eaM6@`CRnUzb?@>!5RUe~b@BYeE4?7J50t%3zmZhuuf~=^Ucn)5A z%_l&QI!;QWo5$h8R~-@?5Pdx9$#ktJq!d-B_sW+KYU|}v!fDbE8wms`1Nmi9J3{8e zN;W`KPyK&8mhSx{NJ?CX6H(jKF#$cIaRP^6qe}5I_b{yONAIVtP&1o6BR>*J#$I3p zcYCy=oJ6ueO{x`P!;q6|raU7;WAybl zu?D~F)O1nYkHmruNyK0Z0lw8PHW4sc6yJLz*zz7i+g1V7-OApKZ55;!86YuvXsr5&P`H zK6HCRD8=3}XX63&QBAZXjnWNm^|m>b_g5d=kEbjF?k@7EW7`XU@oQwSUVw@elTNzhj;~LJl8+DY`4Ln{rzf^=zlK zxcKWrZ?ffZPM>YdGqTIL5XU(m?N>1Uu6bdt+Bu#2H(lg{ zRv*#H%oNXWiTc5K5R#CVba7s&T|7b;SI!pwoVf~^g6}ue=z>yhO?aEojWRFfw@%d? zZ4?D;*y)2J(=XTf-ZzK}kKW%q7BOOGE>rkLCrcqZl@S-VEud8Y1 zum8XFfBSR%jc77syq>B(DD~cF;cXLd1#)#D2lqPOEzzGWU|i9u~HJj!_5x#<3B*@RrDg2!vhEd%GOg1==cJ)PC$q8!QA@3T(t7R8=hk z0oL!#p0ie@$;E9PHBYgkwJ&?p+*W6|+?BZB9xS}ioLt@p)j++AO7fkYa6q56Q#2f| zJ2)!(hIwI_KCfXY*Eg~h-InZD``5VeQI~~u$28hasbF5Li%`zRviKWcK~_rMUYDLI z9x+rq&nQ_{MogQF5<4;KrLvyUsJcx44gSko|9X75ANwCQXv{K4hnom6HEIO%FpvPr ziR;RUu+{SoV4G8^-K*|y4N)%r?@+`IF=+2RBr#Yj6=w~j5u}d*z2h^eOi1~JA=6DC z7{(wFb$7tMDgqM<6As-H0^*pMb~>%*IiZilF%pRVoujA=S3-pMuVbpqtxuioWd-U{ zGGA5_I!TlD?JGv@oa=;Fa`4G+$hldY)l4o=6?22@&(ly|d zOiO^K5^yDU-SS#Jb8<%8gY$2ki=(nM^pE_eb&am9^2~Z_v)Nt)-`(_OfFPpKvWMa) zA#1=4@?A0%qSw^Q>Hu(MU?ZdTEr=Vg+iHr={D~>PGJ0YjT25zA2bZaMo1VM!&c}&l z04S_#>2jc#mX#Ssmc$yWL$XH{$n`0fxdOMNWWr4t2vn1XkL8Ih_knjOBnYG5R0n36 zw2#f-a-OM;US760jOFi-Y%&t1@ciKV)I0O5$Nd8e{{IJ0B$NCdrbvlvu=WNEk_#4w zz|JoO04_=t4*g<}!CW55R6Ib_zEgxkXH_G&wp>N8@hxy9VnG7>7kplV?1&dGbNgqm z&9@zCbF55@Y8}9uBTZPFBipJlYfC~W__gZ!7kP(v-FJTih%4@6+6RHf#l?oayKjx4 zVq#ylSTpV-0VHo_Rwy=m&aysfh4p;#(MV8W)Pe0)V0-LKV_q9am z6$=-R7Y45f-D_OzrSDffKN#;L#6$8Wkx;3J+ba^w-F%QwBpBn=l>HpS0NBK$9WyIV#P|7Ift`au^;R$h%GtSLp%;S70d$;VA~( zR8$0}q^AA_2P-7fK|b`@U9RFyg<;liHuuoom(uqk?wh_iauN9j)e1~yxmN^Wy7%;Z z%CKLXCj8AF;<1@0sv42KQ12)>MZQSMH@P^17mOrMd6rABv zrG3*B-aNu_We`qH!TNea4!{9UNcAa@CQlR-8c5wty6{6?d)x-BK-y7OzbNRsu`VAK z9W(ig=;s-$jVG@(EY?#0^n3FzoL}o-a6p*x_v*#p@-9VhL>D`J&fp~k8v&!0c6!Lv zyMjGlF0^dOc_uC?xs*su*=?(U6xf{tvus*`rGNlYk7bmPph;e?JWuhq2~CWP&1YeSur1fyKd zGX@(+;#<&zLG5KEqJeS}F#fyo7xwHFL)_&;85HJj3jHr2-_$`j-K*P&3&Qvjso$+HB!}Bo>2eay zXJ3dNb+*B(;+?7tA>{0B$8jf_h>Nyxrh`~)cd2kv)P+%$7VESj8$c>CvuIwNyHK^5 znY}+Rdx>H|f681fzc#4(kfxmrekD+^!_64aOfWJph1A-gZ+wO6eQ;MDDkl>VkZmJM zHJz5R4Kq4^p8mpfV@|x+A$8OpS=ZpcsZcR<`%`g!(Rudk?oF9%N@L&FJ40YC(-&MS z{%z@;Z~Uo5IHz1v$>vH$CP63;B|rjtZ8`234P8W6PMo-`Y~;cM=GR_dA0+^5(_3}K zEl>&Xb&uHO(6wKQF#2%sV>N%O52hs{NuXk%j-E%vz+OSCcMiZ$u&Y8k0x2k8@N3s! zC&$Nw4{kL?wKq*pPVVwBg-CGFi#RL{iP*BjW$%k9PndRwo2ZkVn3$Tp-yxZ?UIyxI zALN^@oi5Xpc9j{eqw-n6 z<_MgYLxzwC`q3&qKVY(8{g;b$ruri|>n1-U^D+9ctVP{2k2yk}bnhh7R*2L}fQ@s%^+iLPDSL8=o!C5LPLlzgWf9=?{vrxxEmZex!! zIvWTM>rG+9lyScOl#{a>ysnpJmY)%Z%b{yS~Gv%KTiz9 z-Wbs~)nkWBBm-1#Y3MUToZvdhci3 zJoFk~8@*}zIemOwmkF{HhCr;{+=kx`7)PiV1<%`%X$inn(lN^xj=lNxJURhn7>3s% z>aC;`JWeibk6X|`bDQxa5mc++pYydt;TjNep7t&%8lHeH^%HD;r3h_2vC;Xqv|3W% znh5he69b+|c>OC|Pp?lcbt^9xYLiWTex5ri*QF6vQ2sRnyYV57nXv78O}=@#|J#dm znBoFi4u>wZ$Re)!p$XiO9d2*Oo>=W{Yv?*6J7Lr zlbd^(*OT64fvEih8og(IyFBo)!$;I>YaYRfcQ=e^@_cF3ol`riGDYR378f9*Zg1C* zheMQ#IEyqn1E(+iD^m|!iTMLLNdXxqERJ6+)&22!1w=%hEEGni`ntAOTGT*TMC2Iu z9O0WYX7J{-n7sVj5{6aTc{Gn&>n=<>-0F8a`ouzY%yU0K$+n{O?K^FqZwlpd-1!1c zF#gC>KryY%z$*I$4PIt&6Ywr61K^z4GGv+xDocX{1cz8_c)R@O=Zoj zXbl$ZK>>x1LtuE)bM;sbZ|;@pM&HAqI|EQTxmDG?bq^D7#i~#z>*b~=8tZkADPtW9 zn5Ui;Tmr(Wq%9lzH94PDSbOL?$C7cCDYqmP6xL6Ud91I2(Lhk?!y45Y&{>WwN>52w zIUh-6`Z#3rI-&HZWUT1r@_DHLhQ#&jHlF?*x}1KImFm=kX}(dzBRtLeH3n z^lf*P7Ratz3RiN4NZV~uHm+AP?}>#P+S61koU%VrIAUm34g%FZzR|fIy!>K^mvq1; zqql)dxUyemP>Vz>6gKo4Sjg5v6h(!(iw2t0+PH9lHI0gBhGqkps}DW>8q7%!qCI=D zif$*@ebj}zcDW#TW%DVi_XkZ=_a0waP{<1vpk35j<@?{*BA90M>L0{6D}@q3wN~dw zjbsLq<AU7dz)Cpv>)xnjA@rUT+bNEz$b0BpyjIV*;>!17fCP|;2jqNz!Em6Y@ z0j}6&`Lg}9y7Ct`%-qa#Qrci*Tm|cNSxc7_y>sG1JD{j+^O*IbdOb@!H~FsGe2kaZ zU3Ts^XBxDSj_r9AB>1x9;>jaJ*4ss&LYw2Elka|!IS(K*mk-Uw697EG_RC5>10kj4 zNe#)WxD0&6KbO}wz{%qv&S>Jad~M-1*UKMWcZ+VdIF|Y@hb6u|$@3i;`1r9_2T`$o zkj)h67%OVQ!g-5!qjt^lEti|sV|gXrY2oDcVzYnUV9k|3byam7ej-#zTiXp7a zJ{UwU4e);~=W9Jym=>3owpPE~EG{lS+${zM=6HdB`xyo4NH_f_n$a34oIvq)oIeQk zvg&#=ZB^zR-9gC71G*P$rOliX8+Fjv8iKvMp*43AsEo0^s7Jjp=w$#O$x7{_^8>5e`MaurDN5 zxo2}w;v3OHV>4xdB)l+ko~V=;x@en1(IK?c)mTEpDhEU_*A@wL1!}rvLEjP&SJ&kc zH7kx@gVzhu=f7M_2*ZRs3&?m{IV8W4vq8`gW&Yaozc{oA8<%}y*@osZ^6~1>GSPRU zUjv+i;FKbe<16Si_3(W4NL!P)d}(HNIOq2CrnNiK$14oVDZ^3z$o&ga9lLm6z9u)zV z#;aOB=puPdS#!bO*|pVGFPqR(L_Zp(jGxUlZyWX!U?vFv93O8nt_tCyPplt{d2<7U z8fQ){1tfL21QEdt0A(j>*aw&k-U!bpPZ}#Q7{LJ^3_w}38Tphppjiy z4`Y=4(2gp`XpRY8<(3o+jGxti>ZGlMu=aX180nd;1+S9=#J8cu%V`YzeLEjr`}lbC zoPPOwfkj)WZy5Fd?8h<_XR()XlVUCfx6!*!6jCzo2Un++O%-nTTG3_mWAbARzM(V2 z+HpdeIgArgezLdw04bjqw6Q7mPS5c0bFP>=_3*u_RQ|lIn%BJu2Xt`!SER$<{Nu97 z5f#wX=ANs_Fj)NeTX2TaR^Wc+%}KPz<^2u^3&HX}yIqC%^x13@Mu4rCeshW8^cqTi z^W-{?B%|$~=AU8DgPkP5e9(M_k`|}WF2-difmb&+4vf|^cyK2wp7Kbw*w{H&JS zF3D9zV*A1;{0!1)PBM6y4}xIK)gSgV)qc);)~q%+D;OCY5q7s(nVOF1d3i_23jqqtxdhuB4-o`8Nji9D?>k{o4m0#*$&X z3$#=60(XlK8QzkbSvH{`8qbfj?C$bFsJ&3d20eQJ6>0>PDW!VO(+SPmFsfaE#WXFuPtGam)xrl}_sfr4jb?i5GpcO6U7j{v%&x^EXR zTqZ1=JtM}Mp1Unz;Nc~OlR1jXCXbb*nga>&x{URb zE%FlrztLdMV}zimb**2}qU-o@z2b=kZ8@b)Pi5t&P(u$IC0(kuwLP8imeVUOtw_P{ z9(s4X;*4n>KUentMq- zbj+ZxQ8Tt+<^CNBXvoH6+4M{&Yg49JVbwQ4TY)jr4B4ewfQP<}>^8xD2SqgMTzOr) zFOS`3aVQ@k?94&SHW~6iOB<${fjHK3yS9VF;u6C5BA6~XUbcsDM>4xI^}33B68Ra_ z3Lo7+W&+w@8cBhvl)>oiN4%Q!Y@VV7Lx}%}(M^~pyrJp9b7>V#TDN%zUi13O>3)M-?O3q~Kk))*LDb)JcjuhX?Clx6UfB*=3{L=8o*89p| z?}cY3-`Xx88T4UK`Kv4y)EW-iPxm~c)U+wHqAQXcN*1BAE-6<%=Ph5{v1!%baiwN5 z;4fYflIl9hE|hr|K2g23+z1S3?~Lr6-R6f|unUBaOLy&j?zw6CS|KY~RSBnIAS_wy zz?BpCSw>~h;P&7PSrMz6&{u@W!|z#%M(qv(oi~$|x*V9!`_FnZSqsZ0+kNCE zmx5Mf?Q0X$+M3&kgm<;E(9h5HQ@Lwu)M1#Jn4u@TP^P96^3Va!9AabGatG6ElB*0k zymqJznqleU+q@^G;WQ3PS9F8eExAJP?sAFj*=~yL@f*i zR!o#4+mmTlapXVs4$8jy&&%^5YP&67+6;dAc~#TucXi=ESL)jM50fb*iMnFgW8Z~b z1;6Uf#u|~JT4cCQ-_Ao{1Y&}&g$FW*@R3_VZ0-a^b|V;I32SBDXWHNu3qap(>T9X- z3LV(lQ6u6IT@hrq$Br>^3g~^?P+pW71##=azB*dtcllvVXuQeu5o;#9dsVi9%drQXOw0V-Cm^*o-I$K!Bb5`w|)Q zzRwGFGv}Ivep8t`+uDM4d(zUgH6qy*@)+@pW}e{~8~A)D;Y9o#G&< zbW)N2O_&C9m1JG<#QxEv!2|A-=x*C|5D}^x$$#Z7<=l%)Z}?o82t117X}(Rn^OLQm zgrieQQoI$Hv03NK<15Fe+ViwPLYhF>+%be$a_>pC!N-D@lsG7qM1Q1SKmrAsw3LoU zUB&jcc|Of`FTq|<%O{ZT4<+{W1jJc`Aq=0)$lJD_(P~)S^>Ro5F|(2jhYca(S&mWp zIz3^0y(=_HDf4!_0oV6#54s|g3!8aZnGLfMhk9WJor52w#R2t&o$F`U-!|llN8%hF zk14Ji)SL9g4Ku+ly8nHXihphrqk-q*!xI6xUkBkq49=HqUeIpHdSr*$0?5}5QMZ6{ zK=0OXAE#%o%jB!gL_%%*cA1}#8j_=T@90;2LvqR zH}o3@&qyo%35zaA+ly-*RD;F5RI=R`=R=5&r4z9Vc@fx`cK?qJL(cs-6>s!{COv~6 z2&or{?p7+etKa97yd7W;w3#B@b8f4!uKeajbO2Pe_PIG8h0|Swck8pWHkpTZ69NJP z`oL7VL?Y-Oz-DW`i|9jhrhV@?%`QeSkk@|;TqdLgE|t(CmTBTQ6FmrxhZ}l1IG5c%!M$YO=VQ6N~4eyeYWR8(PEc&02|Vc>B#Q zXnjTMA@8^g&nqWh=TEfP*Lk}ZX^|k?8%*>BP&AnKukNLPWOR!X2viIqRY@<&3Or{6&e|e&b|z9O%iDId#jTY?tZmQ9cwp) z^kgU%kQ6o30RU%<-;gdt_kY~OZ$}aKCgKmwDLairVql*D^h3m@WpHea0ciHGTNh05gXi2Vr8>*#5-?v59(>s z3h=Dx#z|vUA>Agc)iGh8&-Kz5h;idF#qL*TP_$*JC##&ue%AF4XiZi=F#)B7b6bGZj#Ds1P z1k-hp8q?kebf~ObfrE{asK^M|MLd+VhU<_ZeBrCM>XuC2&?s>Q&-gy>*k87;+O759CpB1f~M{R z@pS;Q{yvb3ky_B_pKqWEEPbp1C!AUl0ZBhlZCI@jo3RTIw7+~EYjiP}(gi?3z=zKe z$#eYih!K;ZV*vD>Mky6pqedr;8}pa=c%QQcxS{XqQ<7KAg)Ob2T_4Se1znXRoet_exhO z`Ml3mcFR>o&Ea~FDJWv{h-=N9EIyPQkQaqNvxF4s6UvO|rT&zO=zGJ;tvc)VKaZ`F z^lxKjD|jm-GdP?kI2Kz%B&N;E>PlIsR)IvPRo-|!jA>M9g}66?0VOLZr{_0dp7>H}M(dwbAp&F^m_ znNGxb*!9sb*94x^Jh&%9v(}P}Up5rBC*BGltu?uZrjsDVs$* z7%o1aFkk5$?SkZ)ht;&S>eDVm}r~z39hAiVeE$SxS;=k(x za_+AiK!jrWe%fO$C$0O|D%2yt+=VpQUTJ^&1K@uprvM^T-P9CQQo;`TAm1EwU7B_T zWY1&jL8`xjcS>8y+MkQvNTT9!NgiqIHicK>1Dyfr zz>bdUm0@?a;iE$MSfeE7j*K(b`nG(+6S&}gz@#F#i2uHSh|E#V3dC@gw$`3 zbN?$`>&Ib&<^vnZ0YfYP!x0;FZXG&;L&{u*D@^#!Yu73^FzQ)1 zRhUy1Z8h@KPi`}x>gcA0bKjI{+z6AcmpDfjp5_2&&-jGA6`n{%gVBjC|5HFaH0Maz zHpDteeR0yQLoHK!f?CJ_Uy+OBUyv1H2r7vUqlzSkf{UKpa_g0G+bGROQp$5se&90K z9-LcT?C*&tt!ZuDT(1CRY=A<{=k`!9s3CO3!(^H%wH8PXX{$MW4qDzeswjyQ z-J(|rEjPsKxp$wbuU(>1KvclZMh?_{FgVV$BT^-=`ihY63wAF>q<6>dr%;9yS>A}1dZ+r=r4gB~4-lZh4 zCj8!(G5qwlbg_uG{{MY#WMgu|{+S>UK5A=+g-^QYQ@B2qZLqo$oq3KJ^N#Glrb+*!zhkXxI(@#|d6Sf6p5@e@*ty5l}oHVIZ z_JO!JL9&FHL_5M}QUvV|y&bbCdcvUtWLoTxB+%m!jtB z%+H|-1HI~zq|udkdKDL=5_m5sziXlOMBv%uaO=X~=il=b%X12AX3v!Ohf`*@#Kkw8 z{a5zxOCl71Ahy%4ZG1Zd@U1E*&Y0LiDjHUwQ$e2JTdg-6otqs+#NF%}Bh?%DMfp+( zp<}k~#ni1{{TTnehx{rfXF`)$JwejR?uJ?=Bx+juSQ7yDCn@qTRmSpvaEAE3-tGH+8yer_k@>wtdnOcCh%bjB zhCHEWd_uPUl&p9m@Im^Hl5s8qdd_n=6D#g5v040czvW{+-M2&(zIdg+=D4c96Q@3k z{1xJxlKzMy^#~&c(%_WcT_5W%Tv{#xaKBPv)zhU+F|Pz)-jZ$ z@hHEOA!PUCfSL+NQ9wvIvGruf%J0oE#L;UUf$e1B8;Jg}a`u95bCT2Av*-91TB-R! zQc_&56&m^4aV%4Ct(p08WE?(t=OSsISL9pZtn78%M~#ingE_OxczCPD$$mS#+k9#L zM&*!b969qA<{{`hFoXTDgn$$=RvA$({`KrR& zkV!h@ztEt90K)aXS$jxfN5_(?As`4cG&es6)1=lJb|>4BWPi{D(OASj)fZ2m#E18I z8TwIM0ECy(hs+XNcDP-4TC4?%w;8aqJ6CIdWw?7TSvt(E@og z^-I`>!){e8>!5lQ=mn%hq5a-BxOS;=+DGufrr1@7H=nZ)L$kbgZF77<;{y;`(|07(%bMN{s@*goFnQKT9C_B6Gpf|06*3$lmV zolHzs+M$Q%s9huBYJ=kOheyI(cC)?PHMT7BWQ~fVan{914L6v2r|N6>m{N&K02zn} zSg0P>X+mm-jPvg%8w0=BsII!CPv1*twsm^_q$pgZ1o(x7j|qLTLi*@S9^$GEr*ZXm zym|7XxhQ_>dyG%oiKw?+ynji|VO5buIPinB1F03a6AL_Zi`Ma5%v0MQ`OlCyAsbpV zfl~aMu_<^!>T6<(VP_g&V|SCb;`|b`b%ap{2k5*u8>mF@htuNzr%%ha$^NtE_L>OH zhO^?yb7e$-RQuN0h_aZ6_R=nL@v%ozl-g=@G<%>DHHV6l`-&?7LK#RsyEQd6C8fRz zOoE?YKB}Uqs2F_!$ajjW8J)1oPN?geq@4&eZH)SMzzO`v=IpPs z?*U}Skg&kYAG1DCWjvU6-l`~jd*F*u7-w2o-Af*h*ZYB$tHuOEv}zHz)FWo_bPV4; z_6cQZsI_A_N-CHUNr0|sBU+w@Z^YssHhXkZ%(>k0%0EEVz63!_T~&-E7VXON!A_>hZDq`{OvQ{N{|DulCUgLg{AF@A(5`dax80J)z9Fj6;uHy39HU2z&8# zjIbkXmtM6(@td}L{fQ%H%|nm|!qL@}(Jz~s_saiQWfS4U__vNMVo?@xDQibqtE4My zyQ&2IiK6&1I+~DKX;_);U{qIE2Q6lqs>>?vd-xUD3VJikS1%9^u|{cnYM*KWB5n0a zxmgsx9#yZ}PSFp$^_7$j!(JfTWR&$TAD^mGl(SD>CwmY{5}teo+ljG9(khP(4NT|m zXsZLKo?K-x-ed3G^H^D?BwyIRdlP=v)}TicJTsl6tp-Jd`B)ZWIhcp_m8}<}4h*K| zAH+ZJ`dG!Y195Y}rQXSG2>-}rw7>dEdj-G4z2ejSR%u422QG}~Lp{%V2_Ulx*DdWe zi1$rd!v>79hIEI!fE6dL()EA)jq^X0VuTNAMMS}nO2*4RAe1cQrRRquEnC12M({FC z^9Fs~tFNsM1FUjofb^RGx7K8lWS{eg0$Ja_xvq6y-gx8>2i;aRC=zYj50%0Pti2M# z(s%D~(z^@sVt-HXr>>P)E+!7%7DLn$wFb@1RyD}^|7{*IQ-8rj5nKX%wW_!*Ch7-! zVA-r>+C@^HZTma;x*xM%${P(8S4=5sr$GR|L;$ctOx4oR(Cl5FEctWCg8{L9X<1o& z)>ARDSe^2<%o+-p0A4Br^BwD9>iN!wthA$2;fEs+z|vd(Lk8iSHM{QD6>_YZsFPV- z5%YqL?xTB#3_BC;E^Lv_)wKLQtooC8w@{C`h~72X_!C-OEaRFttqKk@oS@V@*JOFr z2R*V-4U^L?_-$$FpGjD`^g}N>=H|b|G{Dei}oClZ-v=5ih8u>3a7#gv5quSU}U-I5;{B*@)faU<3KIE^2_ zF~%}T9UxX!XE~XWF7Tura27^#seTq8^Q*vSf%h1czWQ2OY3<=LrKvXVcZM#Zt-E1C zV`K68_h!&TKPqh{DuCPyURdFEZPKkx85|w}T6U-T zB17)qcRP*amB2IM*uwYZM5M%4PH)%xk67fT@^?9`vTeEOHlOa@S8u*@r?d!E?peHp z4Cg>j13d%>x$TLa#@dsn*+>x2{vcIKV~Bv9W;kf^cwBsL?1O~F!n2PeN!61{$rSp~ z2!YI#5yJm{`4|4Kx;eMmqeNxf;LrU7Z@Yhq{G=uG*-$>2pB2!TDcO(bV%u;_b7b%R z1QN`Y|2%OpGOettB3%d1a$0M51EZ)I4K1x80M1j^ivj}m(b3TYt`nKHyIly^#?Qr5!YsMxAT9)Yro0E|TN$G00bJ_!6(_*yfn7VE_ z2|(#TVNlMR+AKzqG88PCVPoO z(vfjnt)I|nqf5CMN3s2r8xw0-x}{AwbszgM2UT4PJGN#kiHfHA+{Wesni>F2p8f|k z$;&$ermGCk-Nk1_#Ki3?hCm3)nvSh6D_psW|B|NBO5g9&mF_0;?r~ zWFD5cCnCdT?j8djXq6BjH_oc?~d&8xssHT6OrzK)b+ z7g>mrn0kID6_wb2ZjHywT=LRHJOB5?hY!Z7$>5EMz-_#){b$t+2>DLOg-prOCX6Xl zIaoT(x+FDjecWuw@c-fyz)!tre_fOCn5KiJ@8ok5^f3!RDx2Zqfe%t;{9fOYS$a@= z5=>N2LjJP>5Knqkxj@J4ddKe1{k&a_t3a-QPX6kEQD!9}C2UA5?K4-wUiuvXVl36q zD+Ru5pO3e06!s-wfDr+p2v&fAW@z!s@Nk^wp!El!K=uy|v}f7b0a#mDjV+H0%0_Y>X%QoEAYMdM$UCJdiG+*xRe_lE@i(j7;i zG5FO!tGuoZmd<|tN{1C@q1u-c<9VB5sVX45d-#D+B)BYXWkuxCW2-C^SgJ%kPkO8kpPU%?@CNWRA9>2p;CoB zR-Z)jbigR0GUhzzdO?)cO2eF62uUs*#-?(n;%cU{YBKwpG;W(R(U@wXkndgE37Q(# zKpw}Gt}e97m4E|fq%14pI*UtDBXz57QS4|fapXQgA8jGveh1KqFTmfo&jGrd4vRX! zkfPsuamHabCwLS6IBlh}+>5x3)VlEfrgj>0s7Db-e*elHs zUu74|77nYB=)?i2YJCZjQTLvr^f@`Os!qeuSF7rA-wanxAwpj|!bp`@HkAYab!Dad6o&fPAPZ9a`+lTMet1LAZv(iUXvIf5W61eisM) zVkcqM4T>qH^hS+sHP2idf95-#xKS17DZ4w^474kTGuE zc4=1|Cm6vB%C8<6LrweAaPx4N%vcg#t6L3B)~dz%(VIr4riZOA)0Oa$4VxPgE)u7pZ+J}L)Cx8@j8A4!z43OHMsK+)`52GLqf`OiIV ziF(*R0>pdICwey)h|3UMPyil++l})fVlOb#)2$s?V>7eQBtr_RNnU%ZAR&Jp5sE@o;=1e+3gBQDV40{%03Zl2`_ z%5KM@6qJTsdPX|boCMl=W>RLxG}j-lfNPoi;dUUa!BFN>yJ!B#qhsS}-vo>^W!5#nGY$M!`2nPf zS-1ZjJ;Hwp2eQO~1pH^7NKTl{@wfDKVQq|%B(2-^jBvxqRntn5jwFD%B(X6`2W??e zU@>X)ry!TDogL)v)z0okZ_sL;I7))QsYCxmuEjizsVe-y$mqznwhCMD-;oEo@UO(> z?%mke-t+2DdvP&%2lCjF6q7GfmGp4P7+X$648B)7pBI10($TfRvTA0QsSJ5}9TWV{ z%NyuH63T{DbqMirF!jbCfv`4{rOtarvUDrQIIJR&%w}Qu1(>Q<=ci2LLL5I9O|H8e zYxYaK@M3+1iImS3X)8I^o`$UCBg2O$CpLC=eVP1g77LHL+>E&vfI|&YaMFeu^kzFrvY5hxPSR^^&TTV@RnDAXb4>y z)>>R<&W49@`j>ILqotFQudFp0CFN9`O0{Q4!hDh@c2FWbWg z-b-Wcod++7!Ol9)|L{PP`&awUvJril=>JY}8vn+;1UYH35&`^UeMj{1I8IR>Z!y|8 z;<5+tOOH}IhODKlSvb|HX~rDO-Gi2w1F^O9pwIWwB`_-K?I^ySf9_~=_xmx<+&5nA zF>wSJhYr?9U}VILcPS|1jP`=gWZQca?z@b=Lo(b_#Nd%0lczm$0hWk~eFr0ZhCxB{ zVQgBGn1_*{rbs3X@n4V#yh}=21X_z!@T10x=Dm63fdKB{XhA&HcUi zgLcj4-F{j*bn7!L4+d_JKLKGs%TlEVZ1Nx9e|fMv{;?3yGM_A(skMea+WeA}Tx*3H z>~d9YTee;ItdwWoGp%y<&-75+4Ft{}rlrhP z$zN>QOCJn<-CD(*>{<}8#KQ*xLL>0aPg;wBl`29h35()1_(JBwjHlnCvj9sxnEI~R zB7IX;+beTiO!PW6GZ1JHoWCp%ESBv5$>aRbw6YuXw?VrY3U3u`C@`}@BB*Ws1Lvqy z9jr-&ceYtS_~TUYvw{v;9t(xOy#AzF6L*qhULPl%3D5Ddd?9W1YzdSCN7Jnsj+Md`%Y(%TW&s#f2I?~8^n{QY~J>$V~VGQ^0`>mKCV zP7nH1qE6pf)8Hy;u*tEj_U-d8XDfAL;}ICC4O_QhNTtK0xe#U+(!`_ zaJ=q;wp(T$=0KxxN`Gl1)?l~VXafL`w(B$0nbW}Pz-ENSmx{7~zs&uezas7RNc^W% zl?@^Yt~seNYdRTdh`>6|-A7o<)z9mjkyEwEJYIek_lgJ4L_@1z-X&m%yqc0t5H6{} z;&60kb!iS2Iq~$fEM|LoM9eb&Yhq_Z-oIxI{>gjV_w8?mOOo~`>K4zEVJy6zurG)q;_(Z>=|izxA=WP{JrR=;#n?s1sFU?H)2-+~ug8rSQg~`7F4WdA zs#~uaR`#=3t#r;$}-Q1jsAxj!I!p5$1W?mIfg` zo(du_iaMQvBx8H3C!q3S9ULwV_gm?y>1^U-&spI;>t_c03=dY9*(q7GQucouSon&? zuPglr>1UYvw#L?NpenlDnV2q*d_M0m??$&#FMi7(%*>L}1Nt=)PU}u8T+80uCzXI` ziN*lcYUDmO;azx=Z*i59Ls?x4NOMpoShcTKCj*|5N>C&TXvQjclJSPDxcE zMPt&wo-%aWof>Ht#;#vyeR!cAA4UVgI!$oPR^3C3#lRjQ?04eRA*+uGRG&f)XM3;a zW@q<>rc*gJFHm%MS&n(&-a6->VtE$s#N!Vi`%!RI%-1f!l!m||oJStuGh493N!!rb zpz^@jqO^l_3I@D5Fh2>;>K%4ADf*@CTz4s29ylZnC23VKZ%o_?s@Y{%^*E3e+M0RT zh$*%CkbA;fm9yq9xse-SCe4nSJkErnk3HnjnB#*xBcW&}o8WtBj_jRculCB_)Q3s_ zCvPYB4OBA!mVlTqQGW~feZbajaYe|WOMnHDq&}u1%fuiW5&S17_ha5jyl&?2X*UBR z=4NsfTZC@a&&AxIcgg1{>Uq&iuNnhLNKbO4W*=FC^{%V=eecy2-?RIYF~2zdf#3Nz zWs`~?c6Np4s7mvhsB7EiwC@_up9cK}Nq-lr>XpbZ1!V(YU9sH`EbSLYY~DLci!Mo_ z9KD7!?L1umtSAwW%btWpu-hA*Jy#tCmXE}6kiJw@cs9e~@-iPKrzqFRZR1jVw_EQV z=+qXfg&yZtP^F|7t@PT>CQj9_)UHH{*Tho^lUq~MfmoBia57%ld9hSJ_r`mC?B+K5eJ`x0X4$eQZvyF~ zlY*k2s1}mEYuS7j{A<$;kA)~m;oxxwWaQxBKH02L>xgyxPAg^ZyB!bBpGL)>E!D;9)$y&weE97jbZ*e*@!=xMHFMF}!@bu!fnpHoa!0Ha4iWxY9 zBy&ZqE655$e>^}Eka%^zS#=2yfCLU6xzm|jY|$T=vHt+mZ>jE3!d{Gzco{cq?XaOb zVm71Z8(;DtXXd4Hn1C^o5GU!B85k*t)zu2~RUyC8n-ki~a5Mr13BGzHmN;+S0X8#5 zDWkZp=x26K`C%`Z6kULTA0bIEPF~0PUl(3^)fQ*2m8qM{`>RL8tDUi^t#>{+)PGB& zyYt@#Vnl|4rBv-(dlcXpmzg8JBee3_l}yI-R{1Z!xg4IDH#yKPOMl0)c#ZN%@RJr|9)lfw+RnUDafFMr~)T?QLQRI9;62BI^<9 zIXH@{%P>X2`mFn$#+aT5uVI?JrAl|_@?|;cMwcKY_h)6RuyYm0_v14cZ`|@>FV4^} zKgb8hKPIpMDrHRfChaep$KR1z-zHpm}#d>hVoOgHMF^2}6J&Fv+ zGCTpU&V^esP}KnPBpebVbh`7hB|WZwr7T^~sVMoE^-{(Yr8crdOQ}&ScC%iuh6tx? zGM7Lwvo``tN<>t&e0*7twP?UdFZbkZ92;tjKa%qtAtmd0?7ww=dF{>_M_<8^EeGS2 zRuC25IOo{jmH#?BAR2J-xZb<*`@vb#`;qHwMEgh{CyVaGG~|N_Q3)zGE-`_z6v)pu z{-t(*IN{WVxMx(o!Xo^P==p4P?c`LMX8S!WrOSn-71!%y*Y=nm zdtNERYFL)=`InCKFQ-GAK}ZZgK3u+e&GqV+Yrq|DdFoV?nZ|(pqO4xe>PzX171H*% zDO-*%@hP)65-@ox%D5C5Ynr}~i@;I5^P2rPd>NcZMWd*Li=P&k>6va^&-UwbYbhc7 zL5Cli&tf-Tql_{Vt%)&+^N`CcOvg2*DkZ}QbXIYO3cf;JAy&`Ef{@^d?gtQEU(;Ze zchJCf;q%5P{hrl;=-{Hy;^Hp!`pTc*XFvN#uk0~@l7C}z-^tmrwLHLqfKbt$`MTyJj7U-TZtI6_vcMl|y;#Ykgr?f%OsLFkaZsgID!B+@o z{>oI;8*dewD#l~1DoQ5%QrJFI_Ys<*Z0(4?W1rqUcda?wG=Cs*-pZm@)0g-_QZO`W zAQZ(F$!88XCB*@RpSjT+%D-a4WH75?aYp!2}{eAK4S$#zaQa92L7t=ODm7vD< zDC7I9jjo&6kZ1hneP3VPpx+1b{Ca}xkGQA;-{>J`Jgc6{;rhD-iQLlLQq-zwg4fcv zu#OunDIh9Z9cZ_Eu;g~q%Z;rnmrHnDBp$sC@V9PUcx^wWYGc~rO5bR*b8_%)K?uRr zNZow)F}#$vDJ!E79Bs;}WABe$hi2}@rXyi>*NULTC2t*>%*D>ELkluEA;rb;dP1p5 z7TuGRu$~317jWV_oWf6zGf#%Ve7T~7-P;~?4PC!$a;R5y+qE-&1At~+SDU5eFqj& z&lhrba)EX31jI4@Q@CvlzjWp97AG+k0n0dvJie+I(q53Aa2WprB$Wi?P{l{~dV>Aa zB%63uNCSkI?XP@Q#O=E%JWm6pQYX;WFY@-mS_@=g+WIf8=dsEoL91o307@k{6k z^i9X}^I9mSzPfT*xYG@@Blv*a4<<^fU9Ed7n6!ee*@R6%U{kN9vx3>ONS!{Dl7B$? zhHL1ei}@ndBD2d`OTRGJdi`_j{Od~@?HCH8B-$h^5y>D)Z^Oh72Znd33rFR$<2P$5 zl3gFEpYXXcH$jd~hn(3@LM`e@uh9J0lQ4!ZHk2hB5Wxg8X^ANSo#x#D zVI@nF27VhJ6KVL1*{}YPie8oum}6W~(vW#BSK0oo2=}!i1@-(O%8rN81%(|c5NCCL zZgK6n1V<4=dbaMlFL9}HB#D#=AKzZW;9f5?pB`|;vTGQn&~RE`KHq?4)Ep!YB#i8D z?i?h+sUvyK4J2W{Rmg7xDEsk|hW(lAA;?9n>%26^{(AJ}n-zc-;ze94(8UL8#Ey(> zs~W8eZ_TYd1>z+CovVB8{zm*sfzl#G<(*F~MmS+Uv^+UBhm>JVBAr<(-VZqTEkZX4r&KNd&6BHkX;>vbvM02w5IZaG8C+Y+!_yc#XDbZ#kM`6V%< z*lK>DYy|SS_e<5qto`aIr%7}9_VfTca^D<_TST)AQAiJLE9CCbZM{Yfk(rh09^WE` z_S7~#DRX93R+>jHW{&2FKU<1eE1~na88#IyM@KY(w5wCg=lBP{svzWVpuw#-I--6X1;WA;4_lzW$sx zsieeuVesLd#@+H7y2B>S^Ui|;&ZLP2e(`SVi*NZ}yU{;odO^N(IR_^p2ThfcWMNu9 z)oVxU46(kfY2Mk524|QYL$khlfef&A@God3C|)$yj_N z(HTY05crCSb zMlm;qy>#0*7kqz&;%agCGGFs65)+VAm39o0A03yLh~mr(-tt$8R@*{;lhfXA48}BC z#9D6=#wDx4tT9>OKF7Gg;iDSJ*<-@Y_1;VCR-blq)ey{vX7Hb24lCw5Uek_$R)ZTY z@xP}yPOE#J`8B_Ji%1N;#Q2-{svNyL?WM~gDz5Iu=E)#DEY~w}jG=#u-JGIXhM%N* zx4$Hg-{M|f#c~kk_E9NqL6$-~8mZ4O^dj{&iU<0{s=jr*FZOuu4syYnU*~N~t>1g_ zrfT!iU691@`Jd0@H*HmD?Y^i$=7X3v7EJ(C(QEDHH`8s0)XZDWc3ud{N@tM&U~k1D z!<)38AxB>mnzPI4GrSPyd zk8Y=o-MzjV2SmNFr~~+ct$sPCYF6@izet$ z)J_b;ql^V8{0}KzD5i$=gSuyGLqccS&MS2|aOKbh9h&*qItI0G_mnL-Yj!!yb}p9> za+TFMl*o^3i^H)%B5{(Tg}o zjxX93AFKNec&-NzLSdz*A|0@U^K9EW8Z~CQT0evt z?L2N&eIIAX&K*|6yX8TUfAJwumL+8%7R8z*IB%zu&xlKjs~)oEFUgV7G_n~nk6Bo{ zG+S!?Zvof)s~?jB5e))o5*8gMvT%+=?goWg6)&7OkvLWPhQ^m0r8QaeC%6L* zL6Q8G*?iOZk-VG>WVHutNjT1m`{Y|@?IayGRoysO$ILBqT!Yi$+rlJnGt27^fPj>* zr%RM1!EGWLi_cU9SGS@!iI@Sr91;G4!Lp`f233%j-nNR)b*kR2cNXPGHnCYhC9~$$ zu;ClEVlnA|rAb#Kp>fH!+H^9+>CzGnExeIMJBT3f0-}}=upw+}OutaneT*thYI#M6 zGDdOAssYD^TI0JOba6~-e6>_csUOV^YLj7)1Co@V=O)1fx7bQ8_M4*z!Y}2{A`d)m zV0HodDslV>DZ6_dc9$EFt8$C{f$Zys+6k1hkbHWWPAvlipp5Qx5_Lr6^{J{ERw3SYN(wS4f88h-GrIlwcDyvHo0;_4kmlaa zNZg@?oJ-NqHt*ln{!~Zte#nbGIA}#I`GSq~m}P2}tE|?ECAHS)*?*%M{%@g-;EtgB z6vYvMR?8XEo2Qq$8;{oGG1%MC2WkI~{0z3>9DC!>xIISA@7Cv`Nk%;a$OrEjd=9=> zm?PpcO)WP50zy&2AO#7Vv=`50;BW9y)^jZY98IFSNAF^_>mt6-SjJ z#1#|abz78`#v>jsQjJnjixn?S^8F8Tzo%)Nz5GMJtV}lw?W%Ht%{EqlRNZiw=TQq# zojv0hqer|=z9JdocH|gE30Xte#j>5;^fikekC6CqIflXI&M{>>UkypM0c#3)S12Wk z&^|vxY*1M+KDcCD4v!?oJtQ@l9ClmC1)gCqQQ<^}8>cod?l6I7UDnd&Rk9Ksw3gDa z5#H(3_`H?0J-UFom-nH+bAs#~bFKx(@Z8V6E=Eau@-`fGb46Gk!yRi!dBaxm5NX-Ln35Y$40=g>Tfh)iwGo zrU)OBGmi0%z$k6RUv{EIT#}rH9Uz#lsxwKqIUP8yjJL&X9T!ZIgS{p&ZQ934{kPS% z?p)DUPi+(;Q~ESq+Q(BWSO1~pyW(ExN^_r30MN;~=es@P1D(3b=TA#Lkbx`bT>kr} z7svCim&ZQih8u4An1Dr;nB9icZflh*aPX`Ir88ZB2+gm2Yt#@w5Dk6^6U*RRdphFr zY-n?G0k7&VQ!vMo5z_1g{ol%F_cx?M3Up!T&k;YNK1fY2(EOP*X?m`R_5&BsHzV;< z&xp6z0%5XrGUZF029YjMufQHo9YHf5De*&Ple8k8^&pGS0!nt@U-|v)e}n|Krz&L6 zv&J1WKvi=qgVMHKwz2Y4W61IN2hC2jvLUY8^ql7|sXtJUscv+7yKhi|jZDM(&{3q! zRGDi{0!!rQ2bd;p+9I&`XR;2ihL5PcYdF-K;zuQdl%wQ2O` zeHT(xvaHp*Cven|c-rmO`*4K}?7fEDbo2$Qy|qJ4a%Y?scRQ!RO4mB5%zl+Qkl6cPwx z+;Fzbwm8Wz(bX1R5kF0s=KY?l_s1$25NUT3N>X_>@i#E4>LVX#m%I*CW7+iFYfeS6 z!BiXII+J)+q#4yV(qL(^{Vep1E>*2<^HgrDYsw#=0IrkE-RsMa%QHLctUPJ`^Ay8J z+rCyMms}x&?0*2U!#^QGAGhQd|{9RECI z895d_n`Eeg< z(t*RpZ{E(07;JC<)R_CRlbG`cPuPHrG=a5(#P^I_7vw88!fER4dMXtCRe@OBVCSxw8 z%y{M)_Z%#~9?91WXOK5}oueZMHf>Z4`U#Eo7{-Hk70gb^pEi8o8QO<+F0=uOgqme9 z=ft{0iFnlQmGy)R_W7`c8D3hQ(E_hdNGiYtN{9^()Pp!EE`UH{G?pbpZcUdB36=Xi z!`??kU+AdqY;GNqkhk%Y5tJHthDg^?mIgKve^wpy%9XBU#U0UlO~HW{8h&^OnNlk)I^1K~g(GP0f>Z}=0mC(=Fh2#Q z4OiLT<53kFinwI})@%ljea8t9#~Fr^m2k#62ha%7#g2R^&X3rA9Qj-NBKmq+zGCV8 z>k`^-N{s+{K&YD{3R7MuS-PA0Q@zD^3fl3-vgeH3@-pdqU%Jh-{~>p2gmym4gDi;~ zvXFJB|FW&Z(AN&?b%K?Isk)r2Iz#E^SKZ)Ad(zja>q8`INyXAx-cnam@P$n_?! zmM1J?i&F}P&OKk1zOw01SFr>I1A)WY?`mma&sz0isxe64F303@)9cOPOa9=tpi6l0 zLSvUo)Isi(&_0Y>D6rm3_yM9)zYFHbozfih`s-v!T3P$ zr*jmOx9`^aK0rYi7oWeJSLg*ZOKdVaICPM1Kv_gIciq8yOxnWHUV)VRT|s5JD>9Vj z(}lF3!WMHe>zCDsCyA;XO5Sm!%-oivpagAEn6 z{pAp5+;~x+(NLNz>I*;t#e8eMpfcU4HdL{Dn{$d|5poL`F#HoZ0%x7HX*RR({3&56 z#_bo75)FG_1H55@gh0q-$4i}cahq~OF9dMOT2RYD7KqQv)$%Hs>4Mn@E$ZZ-O~}$T z4ms;Wf1%DXN>l(Cq6d2S*<=ujng{H5fCtY$_}KnxHX>F=jU0J^w@!z-Xhe}ijceaO z!*ZAA?h(8j3lmBk%&w3xkCvr{%lO7R7g~1vj6*Y9=u$C$AbznZektFBY04Y^XlSM$~ zt7bP9qc)9Z%?qg!Jg|jDW`JM*KqICt&;&SgKZ218<`}s@C-zq$3RLd@zD<0%7kxpM zAq8c$R)t}ox{E&(*KeAO=lk&;b??#rFcoH< zjL&^%ant~&P2O}i=fe2?&z-P5bx2!!B?+tBL5U&6lK}xwHL$a+gHf|Ucvm*h? z?YphT;o#mmJj^>AVhnKOI0j3;)!&ckqoYsAtQqM>qr}?OsH=BMXBa;gmYXzrTwdvO-SN}&qhpN%Ra8KFiSJ>-ta8U1Nio4i?eAv=_dB!;*L zo%S%Fd(NXcr8T+x+>jnHjATz6u}ieGZO-h8K1r2-KD^70You*8k;PNcgQP8 z8Cd{PO9qmNW+EK8vt}Il@!tJBx$EEe6c(Nc3&#bCSZ1`y1k1ZlGA7|$h!r@lTR$lP z+!mS4Dnx4QHE(-J^WMCMe7|5=dE8|*g3hDmu+ziG(>rez}(?2Mw8-M*^C*}R|)?ovMkA3KTfvF8U1vi$bs=I576HrcYRUClB zfuUT@%*q>^&b8xu$<4k{Ffyn%f~AVFR9~=tIHmiPXgKI;O`^qtC{$S#*zR%KXqwFP zZA|%;(YeblOK;oT%K1~3wT9Lz*TjalbzM{{dm66AyG9?%q4|PC+$l^m&1eIDq2xCH zJe;xL^1cFfdU2-y`PaZ3lN0in^p*;nAXF`PBGoB*RHVZ^C*k82M`2s+BdQwMCjLID!UD3hP{>E+f*bRd0w|NuuK8r9vUkjl-(Vwr9oB8<+tyz z_8nQkHAR>;-5DxG@?iEBc`V{o3mn5W`=SOzYvK_@24qNCXZ6Wu4S-7{j9q6LTdfk* zG*;_iQUilCTxgex;oT)-K#d&6m=vAP`I439=CHDiQ5d9Wfm@52BDFEq?hZdzMzWo( zxA&4(zZC@)L;~O;H*~{+`z4o-I*Tdcul|t;6s@JRi11{;NZ6`J0}QNlEhIF1N*S(*L3A zz2n*5-~WHD;+P#;PFt&|$Ea1cN5$y2YsXeZYp>X$R+pC6-l6u29XmlsT0yi$5L*di z6itjs@_RY&^L~H6zkhR!o8*<}^Lk#-$8}xz$4j&OojR>vJP%=5{fFE0AREubKXSR_ z)9FT!_*f7{+eTGmLzmt!%P!DZC-1T4|E^hknp=0;8TGo^*2+#IB&A-$&41>DCNT3? zQk8FBTWF%&QO^I))-i%mTZh~jBQ3k@;b&xH1JKv&vLrhW6ns#RM$tHEGx$*hbOnjt zA`r3SmTYH*+fSTUIde8XlB-i7#h~%jX*OroGhbswjLHo^)%X5oWYD`1t}5|C=PJ*k z$-u%>Ziem*U?uZ3E$)`@vrCrlJuhxqK}+gmxt4+W5O;HJ;+*`|)U2G2IGt8h@BG=< zT&IhyC8jFOrFjas+sH!tkHy9lR4PsbYefCs`FIfD*Z2agwrO|+(4Vkxw|&ft2+JDy zU(HY^}KO8E@_`W|pW?lG81I-ZXgm=*bgMw7=v2Ko4vgmHT2gzrf8c`2Yl; zm%!US`=0(_JgOfQziN@u*B`udkc!Jc-(XD-NQ&vN_dXv98OQXM#1EPFVsb6Pa8H*MczoL?iK@v%IdeoGDM}@pyZ^3 zziWVI+WPjj(pt*=xkni{q{$I6uh^|C3milUBz4|sW>Jgw)4P_ zI_VRo2OhWHcH=4jtgNfj6BEg<<9p%+qh|&S$-Lh1B}G;BX{xlpdugPRp0O%BkHmJ} zvRdI+^qjjc3tO~m+sCw%JpIR#d3=VAGl8?#AdxEkoTbvGWeT1A{@6qftHZ|$OyxI; zDkjF=w>p%IB*~)o7dW{(b+82mL*cS6i~<$0?-Sa>Y{}mB4=Vxf$G@dvmpjyQG!kl6+&)6&wW+8n3cDa_Ot)B(y=XUJCO;qSlK z+rQoJNImSGiOxOpg^hIc2ywoG0A=~>loRu6x-SP0pXsKHI-R9l+j9#Gn@9{fEe!fi z`xsVJ8;N*yC?zV2+ED|fAT(K?SM(~!J+X&?YwkMHbyZt~&#pBcRWeQi#y6>fX*eu} zjeG1a5H%N$#Kr?b{l@1>5{0ncK@~dH>uRQ(mS6P5>6=<9rXWIZaAS1nROl33D>~^| z1KZhc^bLz__udr8)&2}*kcNz3S!vO?lVOfchIZEh$Jgyqc56G{_m7j`FH-*iaFzO?&GgEvM8jnB@C*5zEbTa0{T55Rv08($do^=D3* z-wuD=m#JTp^~nl8-qId>{zt1MMwBb?4*bOhg*%0gMAOQ~lGW z5)9}*=gu27@knj=y=1;4aDM@~eWhga^q@Z!+_D+ICnRPUU>QM6&HpswPzqOmd`E zM=$Y4scyF?;B+Zr`fc1GjEQ!`4)&|OALZz``oxz*Rj>ylLTfxJnvP?%^FNblGvisL z0~Jc|()B)f36L>ZO;y9k;Km__u`e9a@M`B<^ieY7o4y8x%lT`C!NS5v@5!Asq%|bz zFYcurG@XQ|n9|S(DvZzNQu(a% zxF+n6j$X2g`c$5{4IU{-ZQGKDV}kwB68CA;XvP*S+PeLgEC$qo5>0E|KRTSH6QU!? zb^{=w*yQov_0z^q);O}w$d--weJB^>_%EWOglv7{2(Iv*ag($KS6qlR%l(EV;G}+j zJc>is9W#zTG@JFkIdpS#ia&K%X0?BA+tu-i;>$q#D%|N}{D#w0wY1uA68=%y>r6ka zk5AlCzjpBrVgI;C=osu`B>>GDd?l~x_Qh3PqD7BN%L0=W=OUhY?C(6Yc%3s zxUrp{(zYOQkN>Ls5D|EvysY7N@&NsX+iO$h?1s`)pdFIy`u+Bp&jk*xsMu)om^p!) zW8EL6zVLy0>tVy+f8D8myToOG`4K*2s3Z7nC)9tU-ulj$V1Pepc_c+7%a2{?E0N^M zvkn-e{tdNe=cA{iX`euB4yuKHF$z5K^4AiXJ)(vuyj22nD{5sHKUqa5e`_mU$Xum)2A*%As&7Ho-H*w z)KLYzSfW^w*q2fvF)K%YZu(`qGej%A@&-ujS6B>9`I zpRHO&QVRdR<&2>uV@<@0{Xh@SnB*MvQ{%tpjOS`G*UalGvaSeiLVQs<&YVxa}e#)oTw$;?e z!GO+Rug{gp2{HeqH(|fF4(dYXORG1 z5B{KR8_QcNN)VZDik>;ZNebzPrB4JL@eFaTn`Z#S@LbaBYE zwv8L*?p+^{zJ5#AlZp*|cTy0?j=XkF+h6NIv!HXVF5}c^)Dk@)_a@r}NQ&@fZy$!x z3abSR?}Yy9s$c9~?4h{|`!N8YFN*p#i@{S;o6M~rcC)D8xs$mWg>ghFmg@-H2lc>; zj6dTZ7poP$4B&kIZ`_P*Tyl(SATUN4VrvWrZu3~(_|)S#0PH;~Kt50fwC=n9Ykmw> z8Bfk{W9olph;f#}yowU;K!D<5fYj^LMht`9B&UmVY@Cu#6yo+xwY z+$*8;*FYogLRz!ecpJDbn)ymN|4P=ExD{zIF01!f-iy*(**!rv(&^7>1H`jY2r=x0 zXzRVzgatmh{k|{nn4$Rsyhl2@FS&6@(Sf@W& zzeE0ABCa71-|u5v#lQD_V?Gk-d@a^2hYH^V0^}p~IEWBQ$s5qQfs7+4UmT!I#8MxV zoHV2@29UnQlj61rYS!PGtvqhKS+xBm0my6p1P^y#Z3Kh_<86$;`zp0cVgW% z&6nMO>Y4qIuk`zUQS)Ja2m8By55E7WA1_peK8>b^plaFyNk4I3i`H6Bw>TF0Bbflj z!++bP|5YxuIdY=ca1T1jX{%G#dezk8u)FZWvy07pm-8Fr2&5N#A+((n`kZ{%Alv{c zZR-8m_tQ4_z66k7skQZ>{cs&*3s%};R>cLSkZ&_Kt0ln=uFjRO@`RLMiLn!mj!}GhAF;3xy9cK#KP9;mBRLpp(U^X7oCmI7WQR>EY^K znnf3P+oU#}`*zh$!1ltCXspowD zo1xfg9;-PYKRwdTxa)uXqQ4AKzc^JI<;vl98y5EhXy5hrEwrE2jR@hK|11v-jNRYk zzAA0iZEz>73b1th7*sngr~)-bngWC5{XXwMT5Rr#vKMbCwGJt@BD~?B)2l3E=x6un zOy7UN|8i2lm36lVeH&c{xQ*;29C1b(9VJ5PEghw&@+=45TYv0z%P@3S{|JnhXEp)u zq`!diVPRQI=`_C(2rC@6&3ygIQb0GR)YRwh6J6bl*nVCc`IvO)bHK;eEDHT}{x5w- z(I1}zoQ3tiJo4aa#lDp1GxPlW#Q&}fKylP(MaAPdjI;>}a7vypHZ6S}VR&^#M&t{> zM&D>J%DD4Y4)hekO~v>EaN!JMNc^Q|*CGSYxuE3nVlOx9qL_1O7GgB_ma5#d3ZUs8 z7?+!mp*jeCxj;LNo4M)3aX!I-uW{$5K#5_Q8~7l#)w$Bxa_#xTx1OG-!c07|XFgp0 z@aJV=%>MYF0pe-(?L#KvP2UyLhW9;01A(V+=NpPHfBGp%vg`ri)4kv5a3tOK_)2Dg zQtK!00IC7;ks3Pb%<+`Uzjc9lRhfTj9yF)eGgw{<-R$FW0xZ(VqglTD1$*xa++|?dB=*`7=pQsX_ z&BgOeBD@K}WDvl>kRd6)1pfnCo^J~z@=)5lY!N0B`+E}N_8|t%3sQTTu{I$19<4uG z{Eq5zn7TZ(m-y+gs?jR+p4sbTn_W2jkyo3kVNIv)4vzX}2HZH9RN zg~b-sdpW7E18#N&YlB6Df7hs|)+5g<1^YMu7O0%|jyLF)G=IBx`F7Uz{gim1+yLH_ z=V!St=97>h*;jD$?dpd4Ag7Y*168?!pIj)@LV#?AedGb)IaPb8Vg|a6rZ1#%_ui4j zxap0jvlQ>1#hz=ZF$RRqDFX>NV3v{k^GoJ+N0eS#*ZnJiaURw;5&q%W?&x#M8R`Ff z=pQ@(6*Tapq$& z?(Wvq@@E?V-e_do|4Y(^d2;=7oCB5{E4~RqOj$RjrLWx6y3?;KhK<#_k(xUVa zA74o5ZjnhS8N5^0h(79Sc?s`5y6FHA>%Lkaby!P>pCyge+R{B=_Wf^fpV965FDgEe z{l_1TdExJP%wFv6osn0P$}Q4i_6PYnW$9Y(%nJN;n-G<93C%B+mRkG*ayrKc%+}@n zO}!07iBQ2Kt|F07^07w)&92oG6JWT{D6nEok2{xS%n%)JlCmFgc2ZZnIy)0LHUL*( zul;DVwM)Xnd#l#}KU|}##w8ZuC%+Q!S0C@3H+Ycp4A>TLp;xR;`AYxhVX_6Vx;NgD z{)T|{*x5d--5;VMbppg(jxQGF32;GXW@d-HDsxHH$GNGgJmad8b~WIx z^jjb0mBw4t;Tzi9SH+_S_sYx4BIWshAcss(0u|!_ZN2Tl|CAnd0Y>XLrk@EePaej{ z^_)*teHNMrk>Ob0WTUsn#?gG}~AB*i!$Nh`CS3Chf+~{CapEB1^I2-`B z5_Qi|T$iyx51B3YjQj_m*axBr8x=tiEGkoNb>et(WFRqgG7sa{%> zL4SwdORzGN5A`;CBJ&Iq9%G6JK4%{Dk(cv9UvIAtqrJVcae=qaI`(ZW-;y-PPzLD9 zjzWSIhqb}Tqe;MyceomNt@Qu(0QX1z%jP=v{>oZUKgY|K3H~g14Pwutnl)%@JjG@t zX)FQghkwnvi-}4aoqu`&HUl7{?&4avWV};KIbOo}zyVjaswEyh9RBW}_J60u8UDC` z+fcV3-nf{^{5dU4)qgmPT^O)wDjZmqFLP_yP#*SvX`Scw)b?0Lx*~fniuDHhRVmjS z*O$7*ov({PaD?nlM5OGa4hhGNg9icAogYxmQ9tn+V&$a8a3@ilgWXeZ7DDbbQr8+e z{6~vI4Nnk$dQvYQO8*Lfpf`OKd1IT}HK)8E2oepTx`>rN_~%T1L3;h~2lKyH@7TA) z|J7h)?s~LZXrJk`ql67NAbQ&#B)TH;-}QcTx&c3paEi}_o}I61*7Re=dH{b7f4AQS zlwv5HrUj~kX${tEsl1}6GIm|B8MABP+2~xyM1Nq>M|YtC-OO>v;zjlA2ZqvC16CYn z$0!h?V7{5-^9<&Lbj=0W@{pm)%8kK zga_DADP&}cxHN+_7zVUOwbq(B0Cxz8b0`PsQrtm?BH^A@vB}t ziCDVZ`*8}0T-S{sW-WZg#Wno_z6r6lwG9O@Txf?wDiv^V_d@_}7wl0{u!E&cy5~$_ z_V2Ll1Zn-rMPj_DX@R>()<0{ZZRWoVER&DknA*r2z5Ug3e24$T`6eLoOgWwK(psQI zfnERQ@e26(yjPFx-=hjHtBerp!$G1f9WnAzQ@;9l=bv_^PxEf^P*Y4V189l>fGk=3 zfU)psZu=*W+w!lC=9iszNhevq?b16xP|wxX0*(rdCf*;pDQ?r(H7IP_M>X^Qt$}m$ zy}LQ`d@Pb2?Kd59hfV%F1z&I%gfu!KDN9@%$*D8qML%5Sk@@(a&yT|Bu=yy3^psU+ z7z{>licQJcWdA2jE-3!*8(m5*+}Ho*>cJPi>V-a!dfeviletlk)<&X#17R7+a6jN8 zdCqBt?VLKE2;1C5Frosq({QKqfFbh$eR@VdDn`)f@m9{-RQX4JEDcc> zjt;`>$tt0>T_haWu?;7kx<%Wi1K6+XG!gbc%j59h0mBIZ%0VCEls@mOX3t{q=cC3+ zz<`6H_G>6#<4*yBk8XWKo&gT*-^J`FU~&gaEVb^qf`i3=l+&aDZ~KhWL_gvIVykK@ z@ETeZXmcvR76tT4q0+8KUoWKo2iC31{V%-mV|tGsb5`0h(=uAkV~Kf)n%Pd_wDiej zma{Avp1cCg%3q1!_w)Oev6LNk_yfMcID#`4&2*$&J{FwVzu7~K{bw2qaFvHiVxaNo@v_0x*=xu&fCamvOWUd z3V2Af?PuHc;i$(skY`r>rxVWleGMP4_FGF|3lh|7l4&vf>)XV9VE$J&z0ch9L)vcdy4;tCB6gkHODYQp}m@SU4TsyveR7lmR;pmNnrT%T)~rT1l{y_OFT zeE+j?Fdsm(YHhyk)@X_+nxgkbeqV`A|KMW5(fV?u0sd#x$vZwgRg*+byz|n#RIHzU z8i%SsSk`6fq?#66S$kxN>4Si%8B2y>8hYpO?lVQG-OmDEO+= z5oj;YyFSLbtiz9HCybxfbQ+=rUd+X~2KFKS>$3?9bbmDzjsj+kcX*-OWQX9=SG{*` zW?th-M@zeUA0F&&zb& zjj)@2lEA6&++^wuxLdSh$&>2sj65$|Fz6RdL%60cmHO`H={6aSkan&H7Z==zX<`Sh z_hjb$PM+#%V(IV~@9o(<+|UgvV+baBcU)Mfi(P|%Jo77TA?lYvef-4FgAY+ZLH>lB zlwj%9N{zbCGOzZZWLMap?E84Kl%u!cSf&?SYW})<>lOj}geZbWYW>kbO4_K0f!GOb zw$I5LgdR#0&X}74kqGkx8hJYN9Q7 zS$-a*5N?YNVS{c_g)--lx-tW!u7(-;Ij*a_5!yFXH${Ur#kNRC1?1A{&d>tC*;HcK z&dfWYyw*$ER(GDT)lm5Z-@BXV-RvnjAx7}onem}{U>oy;>za2zMa|q?Z7T?>^}8%R zk9FxKixQ$IcRx+K>_s9{sm~U3b+k`U@6orF9m}0MuTTx65QTaJRkQJ z)Qo$Ac)q(k1+zWL-|7PA9P=Sv)o-nr|J z-Rz+6X$0zus6ORl)j=7Qa-Dto^zYY?&et~9G&c9|B>ytrM$ch9!D|-2H(s7{eXH}w z$%}vfb;W=qC2S>^xPS`eCj=V7e{^^JSiLD^(P^|d5mDCy`d|~W-!(*3z|4ewuO$wI zjVp6L+j7KJ@2$@MXkG5g!mp4?Yllj(q^)}VsDd{$6Vs4Pj5HHd0TROGi5lKa{CsdS z5VdmiD-v>9p>0`TpQ){0QEo!pkGX}LXK!oqaXm%K@u0u)r-}YUrHaX?Ygh+L9hk}G`+E#HB*amfV-~qN@zDFv&$9y$Wzl~wjufe>*ydv(B3BF9nm+-VhFx4 zk}Wpi?X;Hjd*m*#!#(wBzsJKIN0|H-X3c@VLSaS&ml6ND>Ke^DZ}hTK-9-1Qp|6du z3@?4c>T>gu1=h)8jAEPHb+mNdkwT~qE>K_HZgIxGn1=05>kE$XwDew{-T%Ba%a*V< z>4Q1U#zjK*$aT>$pz%!EJA!qw8uK9*O5Fh4x0KjZ>t;uuLx#J(VN}AbLsCN{@aMh^ zAL=NKRut}|;azuFDd=#}Zv_G;-Oxvq_Ya+xHbXSa)O}X#A+Ky(18GPM@S+dK23_H$ zxfK#c3<<7_m?mZH4UbZRzO+?0HH0w0DAh+-(;R4PR@YmRvrcqpG3|%Kw4dTy?Z3DC zN03rx^Is1`=X0T|rO2A34yc2nZLm?0Mum(5whZcE{<+^m!$`Xcxe{u;_xwDs*U|S( zRP)ig+9*Tc@$h@8jFj2Nk1dj;0_SXArv1ID*|i#%6@fKXbz}9Uf*%Kg!_Xr6sVM3O z7Nrq_qE2tg;j3a*#R#X(3!U$VvL$wMKh(*~faV*@e`#VPoD2^$C^= zZRX*!L?%c6yov?Pbpo*Rm**=G9Y!Op=`e`&dX|Qx=W{i8aaU8#Q8uEDCUFpc^c61c zmPexl&8{|->4O{ie#-QYUtKhfl)1Qdsm+IQXtPucl8UBQZiL$rgD@kz>O+&Y7(-a= z#-TpGGRa*~FLw@{od4DkT{l49Mc4{%btGFp>mNU$KU3*)R?93O*r=mL1GC$Xh&wID z80!NmTUzO^54x5BNw$Yec~@(jCvOD$60-e<{S89vNF&E(Dp{fr?R_w*#j9WaMDe#M zsc7;PxlBD5H!0|j-s-B+Toa#G*}#(_lUpK9@P0G3PBEx`#BT0vS03-Zd}eYgMGgN2 zA(Q`sObe4)wZ>Bs$-gHpqDk%E#XFR?q}E+G-O%hUfsUwF*8rjqqt0m33Nt~CL4Nau z67Sb39D|VBem{RWTgTlQ+)_JP3nO*=NDy22cF;Gyj{mOQC^kW7UwiM)T zlI3GB3&euc|HJ|crnyF2$#xc`<>@BN^ zv3uVwZgb^fLZLVFH|}SA$Q8|7gf&aQ5}nnHM;kY|^C)?&zL)2`=7KZye0rb|TeUBz z$}hEDcQRQiC6YnKXD|bAcL9VQK|j>xcW3M^1*fS%&P$4gQy>jgyWzBfNeO1?HHiCH zU7R`#7l${LE5ZBHh$C*nqPuyP-PK|yd$4%VihCJ}i^6OBFI{8$&a^wQ_N}3rQJLAt znjkdR+m6H|tMRPJ%aH|s_n3cl3w!=)W6$umxt>VpC%`hFo|&u$x<9KTeB@ zfTxXwE4(8%bxx#bv#V&LV0abvp;h0&yp8JJB2w=NKCM-K<7U;&SQD`YS74P7wMz)# zV0lwXICOH`8bfk857a~i%34IX=n!t^Q`9!B(N(@<%jqpt4-C6duuP5CVAw23-SHmg zq2vCq=UQE-w0t^N%S4AC9snDrjdp(jv9Kn}+uV4=eQ2U`>slZzVCY>^X<#HIV$GdA z>_3X!DDLnjY`8$WbCw?CdvlU1m20N*FXvxxqJ{ZDZR~>vE4?&*`j8|)2+% zMIKzW+NB~a65J;D2BPpZ?^YV0lZ=5P^hV6^T5Xi=gnGtgr%OJrhD>PZ84NS&9x<-U zRI`G7uAyi)-5v7M#~iHNz_UosgnTC#{0D@4_Qa>Ly3UQ(!;O7{JMAc(Q@Hpv#^G(g*l*d?a!Zq-m|g#;Hy@3ZpTztq@bQxfSY(O)8GE`rnCI;cp6MG`eKCt!z+_$xWyi*UvPJQJgA_LWOlrOg&b8HdDHz81ViUD@M)%fFW8hf{xR zaR8z;B+G1kr--%5>pNMv)1qVf)7uyKff(JvRj+dS0l~n{`wab-@Zs>z)+~cI>&Obf z7xV16hHjtKTKMse%FO;x(-#Vtzn*%RZrGINS_DN3448wy{o& zpSi)jr@(6lH9^b#Hf1WLoB5T_^~EPwHLYIdUa>+Hm~|S`tXj$2*$-=pR*nhF;ZxH2 z%1y!fKJE2uzm7X`Gn8wiX32;U8RF7_fSHMxCiX{%3}K5fOQ(ptn^)1)H;X5CjscDt z-uPJ`)WNd2d+oI^!w2SJYXquJS$nWS7{s!4D067>5C+vyxU*cz^+zf>jk8Z_?r&{rzbO9&3~C4MCGaHe56>&qxbe&z0;V0)}dIM-3TpZxu5$ z{7OFe*}&1%C=A<7maPSbZ1@K?fr2(r&|sS>QlB8u{*5~v*@ORaoP%{tVW5+Z6C zm2=HglQbOFR@vB0>z@f0Q^nIegz&7RZaT=J@-SG{_2^%5!gfRWp?8!Yu$JBU$Kg=~ zkGt*U_U~$wff}d7Y1#GOv|8c_E{Q`%umqoXeDGL1P}K|8BGziu5y@lxMDk+ATPw^Y zg+l}itF8l%c%y-o^p+=>=E?%sDqmfAnfpuT3^eA4P zr6KS~6CriI-J1p?K;Xa^9^S@d`)B+|@C5q6nv!!D0zYa+wD!J%%?g&WcEp~hen7ON zu(fQ>9(;tVFSsrqVc^(9*{3~WoOsH4FD|z`Smplo)1`qkiuyD~CITs(Ihp%5 zpI;7Gw5QCKZ`3KCw=4_`fo--X2QM^1VVvbW3@N`n8c|cE|RnH#+OPq$1j&4 zYFI=$qM><$_n=dh%foON4Ws&^aQKP6I7n&s1a_@XA(1q2(mb>VHinU;HunzPR=(1# zhKiM>b)KNCRnqYkNl)mAGD&~be#s}g1$#;o(%xE``Y>Y1^xY_y-sml;Pf)X8X>fX| z^HgXL6Mclis>v6XWrz8!JweOZ>Q*E3z%d@!*uTiLP}`xMSQeADRB|ch$H5e8(r06K zz@_EjluVg#o%`oCfr|C~z0rr{34e16t+WgBxuJc~G&s-!Dj*oG17B?wyr-}=65L|A zq245GvD`mKv*+aqHs4b)v&Xj-Xf+Yn;b^LuB3QW2N1Z=+scys4S0U91b92ejv5v>) zT)^Shz>(sf6>?~)fAnAmbl6_tUqk3X54a;Bm#XTl@YI;k+5QOm5g+?jj`u+Y_{`4b z-+2=u^QGYMz(aGW$pndLPavu*_KqS6Y9mUBGuVMm^Zl|OvyE@co{3~HXR~{r1!W}U9c6ltx63@~I87req ztjCYe4&(%NkgQas!abrnB=JGPT=cROh=JK$*+#b)W3@CudF#ai}1uMuhRx z%!b;HM5x0EUp_kD$~O#ao1uPFQhP*M{H<+!_!|U#bjWOmq6w$|Smb2hn>4%3_pye@ zqt&6cBZ+UJ8a`+M4m2#lPfRX^MDK+#@Z?7sv3Z^h9U^{YHcHSaES$DAMlMocQ6F=G zEO$7Zt|~vgyOh6owMA!3+z8K#+G1h~_4vc}1e4#xoC$^Hx+7|AK*ss(CHBtjvz$z_ zkqV)Nxd`SJ?nu7fT`z{FvUWqRtASa3b?%8Tzy%f!op2Q8vn>GD(VibUFhz;qj zF4w&*oGMOG4l&YC#vAxISFAa1nJ2}vxCs7{OR?V@T}|XgWjj3}%b{(Ban55fb&_{8 zgT2Nu^wD%i3w7XeE(NTy+3H5fLawXj!^z~zME3KPu0t5Db{B=I$-GPwkbWVLUAz$Q zfh^Y5Chg7#LBwWg_4f%YU3pGMR8pjmRtpTHs6TWet=h0$%cwpeRsfUTP`hLNw)~gg zQr+nqcY9qT`AMHE_u=VE%2&5rf$s6Vfdrv1QbKHi)5CQ<}M3|gj z;P|WT%;*5c3|3cnCy+ZE1)85ss@*MR;95#CW1IHRIpzjZD#eu)P~@BukjHZ*0o76G zBFi+oJ$9XA%Wz-bo>$5@d1KGMv-5LEevLy_)6lMu{hqjq87;^s$i5nFRq&(5lW_fL zG;DHEmnf+b>U4<^h4|6MQMn*JQs?XC*Gf)oaaVKAfpZ?beHB8NTzw;0 zZd2SFD-j&06~7R6A;@Hi>?$hTJ4g_v=Jl*%Ai%pD52*L?Bvr_n_QjP@ihB{*w~nZE z`94`i;YHAEUYjRW!H_F;m1y>Ki)4jGJ*XPC?ad5lumJBXwI|j~K`~tu5R55fO_gKC zK%@f3e@^Q0w;MIgqU7g?_hIEucd>VakJ4pYJU77O#f`$vUDI6axEnR3cBF)eDUI*2 z%8)~R;CsU8p#RKO{7}MUk79`LY8Gc{kPjkrcl}Qap^-j1a9=?K>^;HQBQ#R0TVwAD zy1Q)<^QfA>5PEhv>4t*717x`!siuHE?~vp9<-s~Gr^PaNY|Aq@sT*$ZqRzIKnBD|( zbj&JMD32N|N?-h4+iznJwXZyFu|-w5IfxvxgpN?$l}n<=#K@U6(i)0=VAIh`%9l{d zrI2b#Zsb|`P3^aItlb)Umajl1qa33AW5w+ktEg-4u;?n{S{2C+R=lY3IE-3sUND4< z?Xt&m@?I#&bGt`?&>BfU>=MI`sQFRTN-b}}7;AJbNc}VTnF4u~+!$dtv(`oWeCn-{ zyF;Q!>!Fhlf!(E+)QK$E5Q1vvQ8}b9tgq#anMS8O57oA#>ZqRwg25A=6!Rg62q~AM zF2pFWyt*D?#cmVo8k;c~H43dknQPRz$LCyJ@_61qMy~Sw7QZN#Hn|01|JXi*z;*7jS zBPA)5-ZIuOjk5l4J-*C~g3-yjC&pR95e9B6C|V39yX9x$ur-Fz<8`8`e|9cnJ|~u) z+gc@*pbcd{;{B2{d_N`@@~V9?Nh5;+q*+eC24pR}1uWUvXsAWmw@n3M)f*Qm9=3wS zQ^=(6yH&}o1WxIoNbeA>W(z^fG3fo;fb(7zUA*53TAQ>ppC8Oz7AiM3n61fc(Z`rM zRydmTw>)Gf@`^~jkn3#%E7ybHLRLGU>`nM!938LSZV(R7^6S^pX<930dO&>=+@^aY z;X+l@$?GkH@gI|_Qu~;WWvI;`aQg3a2HA7YZc?fy8o1X%RBv!QB{>4pWZ&xMR`Z6G zn1J}M)&f!~6Xv%bkFG=W#EnLCL>>X9nFD#!uz#%Zy_O9W#VT8JH+~56tfEiNJ~2mh zwb1k11Lhds{%Qng=}2@E?|_=b{b2R<@+cdvqg?bsZfQR0`ZS0B=vd&i=BAqBiL8E; zSw!845S7;EX>p~jUC3gFmd)WFL0!t3NbzL&kf`DYlN1R1*kX=L z)Duwsm#e7qq42C*%BWE-G*KNjXJQ7$s zK~RtpQw?c)pliHgC`ORQg5hh03XjEM<_~?w=3RTBdm(%TMt7ei3tj}30PB5vq zo@xa0gS%&XPChM1vvyvV@P68G1{;1i>tfG3IqIagLYun2r=Y9meKiF=@1hfArAXwGu>g-4UOqLDrrwki#i@H z@Mrc;ZF-7yQVDkze1d9r_o0vHD#CMg_WFM?F*+hizou4aaxM51w95uLr_N7VUneOm za9#+y=4H0xrb!xnNL5c=V#7O^FUqc_gTEt0m2#EbGsddn=36YBIZGuVd72+&%pR7sQ{k9h=WMj8^zr)2dNa!2YcCuui6G zHg$v`u=o(5-(tfb07%0qcbC4TKyI}mt zLo}tntqyqIYC82(2j7{!6xts#tQKOe>LWOuM;b;Q{&e0GER%ca#yBEsp??<*#n2|Y zA!VKCv!p^t!azk>=yN~+rDYA~ma z2d>&8k2{%@Z``y70$Y@h;p4JgN%r9hXxAz_(<<5Tv7j@KSzl3~FyLhG` zW{144j%g{lQmr%2TEbbcXl*M}{mYoSiKJ~AQ_Jd|3oaSP8LCV-pX%s97aC#JFQ{i_ z$B)I-EDGSP@xEG|<2CMAH9RhBx@X9EnzV}ys((aA*)gxuq~DI>vJYxHZb~=%pW~g$ zt^efDI*s4smpGPpkABv;4oX6a`606U4N6V=MF}TQ0mt`@XVWoK#hUfUUffmQ{;DZ) zc0NN`OWhh$`!?pAffWl2XQG8W`Q_n=vslHC8+<>j1z3(i0OLSbr;4JWf68dK;5g+w zUG9lplT5g}Mo9Ps5qnQFc}UG7x~)}**UX;pUqk7-Hx0&e*~+fA(67EFp9|x?t5$={ z3u2HiJ|!iV+dlGv3GnW#ZIaz67ABi}QCoW%Br95JaDnLFq{EfWlky~*ppq2MD`coV z4)HCp#@ijzrH+W3eB`bBUYxz$aISQ>M7?eM^W{tu!2bF=kY$;F-o`TwM-y=t3vB^C z$Ja4GmnjcT0VzPIy&+GgJow6o+WYZgGte5)HWdv`vaRW}dG0~So!aAO%0Q=aWfFtA{lZh8xd%Z<8lI4hnZ%soC4tE>Ke z@U48)JKzRKpBPHB7_aQHh(mMVtjlcjh~}33&o!n@ez3|zCuSA!b#Z_!%IEuql>8Y)1*@h-<|?nC4l3$iYfzghL!Tz4Ib$D9A5zpvF<~hcrV;VNT%p_=luwmnt3)YDSJE z(_W=9%ma+7?g2BlZfumxtL%fYNbg#g6-#ur_nH*<9DnmQIcq0Ug|?*!KB?9Fci^GH ze9c<7x5dzAHCKX;q!rE6IFv%+Bzql6cNv! zSS^_hUaFJnlwE)BEH=={ zs4-vEu0z0+2!2|$rWrv8GFG`(s=g|qzTN_Kq+LY$kb4~*c{=C?59At%1`^H68S@&teErOq%c? z#fV9VPc5A#PHM&U6eG5#A!EHC9E6SBk%7bVoe6HXDfNMMi}2xb|H;DevtLd!sWOn% z-zSfk3GwS{#(?Lq*3!XCBVJCOF*B^9#IS_3m(oxqL}$i{5mv9uUDF=*=-y3s^9w=~ zr_a{9_hglhPDU3!ODYE=IP`1TphPvn2sbb;OOd&e`=^S8t6L;1cdUIerhQwUI39>J;5a3EY%hCz_hg)n{1WMMTAv_UR_DQ(Cq3TMg|M zyphnfwUCU4sx5(_BeQ2TlR;^mGIqz}G|O^MO9}m(NCak_ zvL-b>yG6Ly8%tyQbm}tCB!1N(fAIC~^y;X+&nhT?c8O62PZcQ+sh}V;sAiR&!;L7s z1FHsE)5bCqc*|uxQ!Dpqi#Dxputsppng)3Z(o-o*!D=k2d5M+8$_>8%A#Eh+f*Ip- z!(1a~+px)K(CM=cDyg``u7bI5r-qUgcVpxE7HIp^BwuQi zsUdojG2MY~-qKZU351n3o@bI#Riai+b6`gBoqU6@b$(d6zZ~r!-Xa0~ux3y}Qqyio zMu?2pq+0i=7X;HVW3`l*y`jFGeQ~q!>0^B7WLbFCAZM)wibhvcP&LZOg_(;~c6dey z@@$aW?e&v+Ye;`aypbv~>kQ!u<_4|gSPOe~s(E#ivN2`81FxN0%oI)_?Vi+i?_Sa< zqiI~1Bmzo*lG|78;e!+JeMTo9UYT4iCw1g*Kn*u?9;$UBf*lOI$A})7e#cpx=LLBD z2n|2RoQ~4 zcW6jli>D1K*KjfQd^^sGn3&os2ADjUhq>oeoWO63vtL|_H;Bt06!=ynz}?6vo2Bzh zMlsZOzo>WUG{$rVxhV*eUq&jyQF5zf`5@sFhf~na4bO}(xxEZ?3VZPH zeO*OC{VOjZnolTo?a@g4Go?Cf7p;A?JKVNyQmW*Bh=aCYx`97K=N3!!TcKQ|jwce? z-wqVg4-a@vb3}iZ4XUF~vNbU(XS0=_uwp$^qh`OG{Zpkq-0xun)k|wBB|quh?J9<> zsT*KS@-31{E~|Mff4VC-{TOr@4ZgGvM`^lkWKpLFD5;Z27IopHxMnx32aqfU2#jRp z^O9HBsEfjk{l;c+%==1g!ECU4#)k~C{9n#X;ad^2odb6*38Qdz<1uQUw@;C`<+DQ= z5q#xM5?Z~YV0j!^XyqLYRa!&1%x}vtwVod_KncUGlQcI!a93I3q~UORlzDU&eec6# zlBk2bMw7v;`ma%n*!a7CW&OTV%I2X}KEGs@w>%9=42}vZFj7SXHvbxGcBXnAv?IQ0 zmiv8`t~@lahz{1QEYL8vt*YPsU5I}@S&m%2WIjRKO0Vn4+HE^*t^?6r^KSB06 zh;syx)lIZ&>3FP@k%nL^`n-^5S(QU_xXijGJa_R)F3Ig&vVy*CGN}y$?rP64sCW#i zzFCnJz8P=E+1gFA-?DM2^V|XesL|ByGk6rctl_vXqGdP70$t-nyfgyy?tE*C|An}k zuP{pMfbZqe!0V9DK&fGS zwnN%Ij7wK&bNp2iGr)R-^O542nZj^|ZpG@);mgVGO5@?*O-$p~yqjSBS&yBXY07ew zTbKUu?rGNf&i;7NOPj^y(rfQut;41ma7FRr%6_Mk>J*aV?*8O*db!Sg*yeG@7O?^J zqo6ael6j{0A&{Bf{e=Zkk!>f^A8bPaKynuwD~U zYDzcHARE{STDeKTFoT&pY9f`_!$_r^8pzm7Oc#>o3gOC0)o< zi`8|LzhnRj_FUfT4Hip}M2eO|?Fgm{4$;d7DbmOl+IPq%f@8_@MXFmI7U2*x=*U7F4KY7POXL~MMKuFahWly zy)+TgA+rMkfgRw&s?7d}bLCE!~rERe!--P(p3gWUa|vvSYjZ-LisN zWVxfcZvwu&HNpihl3Vx;G7R=t&qgB;)DoNfZmhK+2oZh>eGib(`WrL0MqkhQ%k2 z-azvuK(@C(bRs_N%}Z(stN~E_WD=JC+FXGO#*aBvFCW?CmVp+OZaXw(Ab5^PezdL;s;PYfj6+T%Pupfk|Oj8J>@a?Jbg(xNl-@y+M0yD!Qj&mx z&Ws2;2pC$h08#@4g0uu-29!X8w9rC8gg^oWfg~m&$vtuAz0d#ad*A!v-shS9frpdi z>~qdOd#%0KTEF#Mt8@;Ef4aqOs33j{axH6E_k`WLmhNM=J+GM+@Uoz~4{MPA`_Bj+ z`tRxaY}bS;gYgGb%lHs7h(9^nf>1i1I9ufv7r1fypDtZYLk=i*oido@+!1xF1iG!WK+ z0x9A&{CbB}?k9qg+^dM>4PKj`b8|+J7gD*I!aK}M)a?8_$Rb89T|Ok2xdC+|AQeZA zj7_h@6gKR)^2*AGbhx5tqdyBimt7R$ebfi7`y@M9CM@Gx4MAav?nV%d$^^rP{{Ez<;cbElQwK&9?TCJG#72Z=N;^w z@Ytn(m_L*7S-(u;=rJDx72e<)yc$t_)y)luWR8X~Fu5h+{ko-Co=0;(E?WFvFq55$ z0+YKO2^CkxBr(@@S;lj2L+Pb)8^kuFH$PSc4Wj%sV06y+Qfh@Cmrnrjd2G`YWp3*H z2G^dl(=-N~jW7GMS_K~+AU0d8be@sSS+2L{vY zk*i~hM>YYGsvNbytwr}9)tumG4<4#0q4sNEG0%J)^S+mWR|?%_dcP-J zz5NDNEC2Sd`SBwXGysQJyQcGXz+CgK-BQ8SJ5&7pt%HxaMCjQR%)p!Y$vhKs_RU|5 z;-@x7LU*N#Pb20-(4x`Iy=JO446J%UGtT8GE_~O_{WyXgJtY9*@7IHFRT{YC%O>Er;<`%x{4ZO+WAV(G`>i+=tcUPNJOY@uOe+rE1rL-ONZI=2Cpo zzcsrY4T1bsvSr6dRuy}1pfROS`*S9%Kv%Oo`6gM#m5TU5m`(cNU8OL z1FIByqlo=jz3ru*dxwQw8RQyf$(~=^OlaH+8VK1m5n(zoU9*5{bb2Jb+%bCUrpQSn zX^(NCm{^OG+%}2{KL5Pa-o&9$(J3N_eW1U_H>i)e8GqAl(0qV?9&nv*`o5aO8;xX@ z?g*TV;!Z0*v)9<6gV|eMh-$j#w1im??h*7Y-b2a;!jTEO; zGAf*M2NzPEyugiPC*z&Pkrhshm}YIWIuYw}P-DZfbA3LnI(4_y+#vpUi5aa^@g97k z!ooT~FoTcKvFHO@9*(udo6IUMR1H!JXCkIl|t=0Cq^9ba2##>Z#8k7}wSss@DsO zeg;2Kx#ZY!doN_>eW#)COTtT+)=h$1ai^=;=BN4vhYZDoIGo#n9knz^-40ipPC`1Q zDC$z8#s$Dc<_UYEJezBBQ;Lme*Iq8)d>29(CbE3e%Z(SDMIOv(Zo^h58c$q-+&8#U zJdc3oA*{JWD0IW{00?AglG!xt6yh?&7Edvx7A7FdQ}bV?(_WJ4t-unM7LZ>pCk)28 z+)Pn>#u6yKjST1shmif$l$v-j3()7dAdwsOkFZ{s>gZsR8^-|r#xkJ}f3hFt5*@g% z%M3R)D+LEQtt*9%v4?LAPA_~{+qTH;>W^@y*DP!d(=#>1d|F4W4d01WK?7s`=Rk~X$|JlfmBQ+29rF<4Z9^;{Vqm!&l{Q{GEk<$ zKS5Y!L6L46(m+@WlvU7SPg?m-IK?#)hmy3R7JcYJaefD{iRDK}H<04jFy*X#;s7!T zf0pA6$z}_OLi!R`$9VeO1|7RPBZBxW=5#rG(J*M3M?>Z_ArBwgY2bH`tq!=9gr-1| z`(SHyr+O0!!R%lye6KNOF?;VIPc-We9bvkI2k{==r*0094(0@Gc#|b4J$w9nU-H?c ziz%?D*=!%G2aTL#DPqpLXU^oj)ccr&?$a!iE?S_3YH9!J}&%P zKWqXe9SN(5%w9p$9oYdL*X7$p9bo2!5FQ43m_5B#I1fvSWVq92z7Ns{6Goz%bLTSI zu(v@q3HVvo&_%%OZs=-=0#v||6#CeSC(s4A1#jz4)=xS`Tu#T$FV-{jd|g^HlCzIY z#z7x(=L5m8S3p>|r4K6$?gB06koJ*Cng>D~dukNVXYR^T>J*Cea&nIhDyuXV%!X630sr(rS&A;%{Lu1puNTu26o?1D;Q*I%{ z_X5rE>LwQt1RYLR^6uu!fhN98Qvr11Kke)<07mhjR{8TkX8^}>Ph|j(e_G(BADhu1 zN8X42UMYVZsL7mY|FofB{~0^rxaG><4vimQ{d)-z=-+|*cSijy7eCa&|91-H1F*^Y zyAcllt1SQ3WdCY1KpOsC;X$DP?=;!J8@hkDrT9Yem0H*5?HxsbB*q2ZQe zd>Lw2;=DO?zwGIE|Bg{AJuuj&Y|*rKCq$}~?3;0N=n6xTPY*p`rs&gc_>}WRP6s9H z6^Om={*$#XB4l*CoToj#Pjt8>`Zz*Qey zUz1qnZ344aiI^9Cq13ep3M+koC4whF|=Iz~V$e(f7T5lxH8`uSUZ@BlB!uyXq zL30TaAzwE80)8a{O<>SxO*NV$YyW*^q{RT=UORWNoKqgRQo2&Pg7bC+Lj>b{CcgN- z0PY{meqJ;U;7}&k+-wZ!f}w7OfFCEGz`MI1e0IU5#m@5B=7}%RAHNNFzRspu-i2~q z_7k1;8!RA45e%mfc2G$raiwZyd)$*B6Zzv~zxSbr8b99gww-I0_6Obf7%Q7-bU)h= z`fWQf={`P~+PZr`^N6e)n*z-9qHIpdL~)(Drrdi&!29KX&-Isbwa`1E)-o?s=)rC6 z4>yw&tAXeJ$2A6I*HSc;T9Z1W6n}Im>qpl9Zz{J;4gj3*eUob5&?uFN5JlFxBOWNh zbfavtAclQ27j){s{-OT&bwSKWfD_F9t*na7VsPUE;fLgkWVmp;jGrP#vVtW65Ab(0 zGyt|E6Tfg}oIj0J!Q0COK6Om z#;={FqLy!cYJgn65TraHnow@PDinAMsyg<&Nzj66sr1=`Kw@4$CH|I*Zi-DEBjRNG ztd{16aaKx~5@`LHt=W#+aGp zPZ7^9)pYIa)c)qR-|4;#!OUV+j1c}Yg-zN858VsAiR^E%K(tL^&| z&RsO{c6I93I&o{A%j)0Gr!JU`IvRc-p%DqO2vHWT2_n!D6u-ojaOz0k|I%F~uH5Kjv&Kl9PujuJc zp7nq`IIr&H<{^azCz5c0Y#WbU(#eMtEv`}fd%@i`#|z~qFh%EhV^=teoBn{Zb%&U^ zCgo9PG{bEX#;eP#kKiG6ct#@QKeruT`{)r2Wk%z3-qF?y=8gyiuYfBzd5d3v%u>! z=a8h5X+|@`e{=yKpEPd5&AZ>cuVeVwY!6EH+=HbL3Yq*n;))L`odf1-pgE?aTq>&y z>3|dGV6MgJ4w*v<{jEZ`fq(=fc61OH!^bj9v_~0h-#!TLli|%3qty*%In3AEeCl&Q zGS5YDHh|orA@bY9_5#Fc9kvn9|7fr{lp8(!0jqQ8LB1UsW^FdM@QOZV)BkA)XEAen z3ywHhNKXg;7Fq7eOWONWPBhl|Wn^A~f5~Iv;I0w|&xPTSU<8$34>tM{dmK@ z8g7Y?XUxV@QLAq>*|m&<>YelV(Mo@=I!&v$4JY0x)EexT_k&X5oCA=v;N<*WdW!Ow#v)MlxzL32JIN7+c{tQND~QEr({5 zjnqe15KH+ZYk}*~_%)Hc<_J_AGZJM^wPKWnV1pQ}73=9Nx%N3XpH}BSire}RN7wX` z_2@dWJ$fJjS=x|GoQCZIx$m1vjqDSp>c&oPCbLm>nLr>(%0{-Y9sIO*uvHRMF1VOI z4!&6#tA39|kR*dSVKIyW;-K6Tmk`Y{LoexJ$uo_UK({i;=jDs{-SGne*1kID&>&*Ky28{)BIjt&7D3|0|$75i?~9$!K{vNei8~C!Cm8hek?NIBkk1{_b$r#f?B# zk8AJ2xg+M#T}51T=wQKOsi)duqdugy8w!}yZafdM`ani{70)!Sa`Z(QJ!8jVBM&!9 z3>6+zTGf?k!}`FL(lIgvI%sDOt1RJRU-?=wQ)rQZXa==-j3ml z#qUVT!ju+nEy*4c1PUER_t6UoG@)*l!;PDI;<@_z$VvFwg)x*rQxWaZ&h6S^*M1z$ zoF85-XB8v4Q8@OWfe3n?knf&-B1E*>aS9X~HY+}dn?r7#ZT8_9iKgf{6t~oJsgchX zp%0(tm~0ibjhQR2s>2uxyY)E8iyVQMWI2QK@Io|=0D7G?i60vS6>7IcOidW6ezhzn6j0>iDOzlka`>(-hBCa>dAx+ z$ah2_;EIMk0|Ly^EN(1BJZJXT`jgh>tr|PpUp})`Jvl3GyWX~I#c6PZa&+qLp1T<{ zBj~a9JA}iM5!(8<)^{&lY*sii_L;~hzw9))Q-z|KYaW32B6+Cb*4ycm`AEti2s>QT z7{sL}Z4pM#7+45g0ThYv3?*V?H8_aeX>>3n&9IMPEo1G)(^)U#2nM7&A+t z&hM{ZA<(Dz48*eDU^GnG?0wAFQO1zj+Mhg~m_^;iiuXJtVB^=kd}(wjgo$bPBZWBd z6txE0!6qY^t}_N?euPKKV(F;Z7@N_&(;AEBeD3Ie6&lK6k1G zor_@Ir3?$hME-?k&_r)p=vA1lG)ey59!x+wW?WRN=jh6UGxoz}AHi4WH+FE-sUn>pXB#3b6;MtQ~S;E^b@$WN>N4@iUY0NCHm=2FKs zut)lUhX)wr5(1GA<*E*b^bTG}FVTp+TB6P$@r}g_zP?GWrzN&%K(8oLWjTi&XvYou zVaxqT_j!TCXGDsA1!G%!yJ4r-IYIC@at}|I##)>&d4b^o^mZgx8%r^)z{_x*5o^d! zqccq1Ff`xm`oRt??YS6o`BAhEs8VL#>%H1!P|zmMMU2nop(O(I2nX=ad+To}m7La& zP<`*WHrV|-OHu3T_?n4aNrVu(6R!G~qNVS~(lamm;wVZ@XU0<1O$NohI?@6^cVVlB z`55Proqf@VoU5vxwPjb~@?V`-I9-M5#{nURtF9VVK(R=IG)r$cD3n_|aS5Ozvwg)0 zOf&9yi{IyP?&zrI*Q)W;J6>E%H?jcHoG>e75S;VLKUGu_^Cd}W$y9mzD8g!-bK0ksSSIa5C|_^XVC3#A5Bk$jQp+`jL|Fs_M0dv`MRok$Sd4zFVZ z?%CchA1AQA^W=H~<3FL+lhs6|5e_eOt(iN+R&?sek|t>BIOkG^k|o;K4%75 z9@_JA;R=>KoxVV+{mG|%gxOnfj@2)Y)=pE6$@9sg4h~Js3_Y70qG6`?iA0Eb&KZ#R z3Xq9qV=lO75yO0>iyWD*t`<5e6XZmXD{sner)ZsnpsHu5P3+lw^ z>M1B%pUecXQUf&0UL#KX1QZ z_(Wzc_2_O++vu>{zGQce#)UIXpFv=}Zo-}>Yax8IwSzJd!@WTmL5yDa zc6}d4qaQ&nW+n7c%0S=_kRJNPY_4Q{OQlSsM!(W9#5)!Qr=w;D;PI~U2NMEU#u7_< zNp56?D+J4yQsJA&lZ*2P$R2>`H3ZS2j#$Nq7mNk20LV6yMM~F0c(G3f&PO2%1d_^J zOINNXoOwFh6`P*#(0X$KMA@z);&VuPYzN4v&hU7Fr)ry`WAk#S7JajBQx7MAGu|xz zsX(m=MEX+zNWBp?Kgc z2Suzvw5Ly=O)DwL!6R9E?713T{-t2~WTp1EP5oiE>1 zh4G>KroaZ7;b!nY%OHyxcSmt#;I`00=4qF*a;=VNdPPotg-!S ziC`e>EM{J0F5fg!0<9dHOi&Ac{|cvdcxR;}@>T2c;ld2>yh{V<@U#Q!YsV4g6Bh~B zk;i>giXYq09vsCPjNRr~UP(_?M0_=bbw%AfEOhmC>^a7<9N3|$KjU6l_QGh!eGqyl zgIO(akd{Z@H+3b49Cki8J^K!gv$%`YROe>=?RH&L_{B1}yt`2i(#<~F;}Mp1??sk7 zG;EslnwUn^y_HpYRL5~#lS1u!(cwqf?iZMjXhN~tv%VZ=6Q$}iHtrbQXP!%zX|e= zmc5fC|94x}X!?!kR6n%2DD{5t?tTmec;1KXEIO4tt+3qDk*a;vG6N|p1v z)az|#xgrc)ONmJ?UV!bx>=J$J(BP;|k|bQ5=qlYFK%>T7p*zcg&SLw`PWKw$$>Oem zZ^yiLHCBGC_;Io#;$GtmjPNGQI3VePtwHD0q!&hK$8Mi5NYk|8y=kWXk$<_lEX`)KhUl*)#FAqG#jn`vp@S)RBBycdNi4((+4(r`on z^4WkOEc*cM;`j=;%Q>Cu*h#KeRU0d#IzB`MY^Y`xLOZ)6edJvPfz$fd+h3P4NWcOI z>>rN6v)1U;KjK{;>1pk`w-neqRh(e&k8Ub41UUN_ESA9ty`v*)8KCBqFITAU4PSsg=ei2J$Zj;rO)!R|K^4Mm?t zfE@4+rEJgXr1lkK)ieZA^s!DUtgrNfo51C!rddv}aro3BJ3Hp{TLXX<(`WX5B0=2c zI$W6N2KG}5Iq;QHeB?>2^@G_V#9?Z0dOIRT+et+X2Pgz2gt_Bz3Ex>JWJ!=}eHkH%L^y?2k3u@Q7oRO_COoTYz5Nk~{5E3C5FH;D*ZVAVP9*Li;k2#tvB@3KBpUJp5nQuC{^}%7XsVAJPV$? z@7U#eD(0hNIqA%CiCO|XC=wARza_zc` zdCsfgZyC*6jo7jP+{orA(n{8e7w~$jBjvUS-bW$)>k0<>)yyX59R%@y2&(1V4-J^# z;sVdJbL6RHiUO4wX{X}bPvFP<+%IxP{JYrltmnZc)Lo#^f$O=)06-w;kzr|YMLw(Z|W2K+3YAiU}T>GwNc?80J zG}V#HPe7NB+rf$b*KJcj!N4tu_p#BE=ICA8powL`3E=&9lV`zscXIICKC`~_&`%#^ zHl&!A934FouOL+0^jvLSy>>ap_p(Xb@5X#k#6k|F+8({I$Q!xtR2|{lm~X z$IdPypfi1;_E8sX+X{M0BzDsdFYj3)i!PiTwvpu0oN+M273(}Z+$nBVm1WbwHyW^| ze?fERunQ=@DY{JWuKF@Q{DBS|>A`kSDZg4_o>@$GoCtcLgO_GqKQ=whO)<+D))@aX45~ zbJNzd`?bLWRLkH1JF7ctKjqI1>V*_7?Bf?s*z!~UpYj_ZE5n`o*&TY%9LN!aBI9n3 z7ifa@2T4#IP((6Z7u<(4bw6S1Ww-lNpz|DF+#Q1Ik5=}R!q$DW8Y+?-Z%k7+Jt)XIbTwsFY3@ub9;vz-b<;17HX8;!yu+aqJA;ph za+$@M_Dcuj<0q31v>}kxw8`J91cIm#}LU&7_TYd*ofpK3RjsS7PP)YJcNv*(uio|hm3TgH zh`85Yp6P|ESN8GMm`kw!NsHPDForc~<~?}rWi$zdQaNnbVol%C{*qID zMGX&l&yC$vj)*!}T>C$RCR`_RM?qGA-Sn|H-tSL{a7IQ7wfo8v6I%c2*%hvgNg0)B z3#q!1pr6b6mX3=pP`*5{952IWB)L?v+Wk~pl+9LeF)$i_@0gHVs*}gr0Hk4IJciS} zC|in5zT-LjY*cAL6)4t~8>*l5zcgHM*{jZ7?X%&(syI^(>+;Ll40y<6-$kF3v@=7{ z#(kwQQU>Ol`l}4zfLHQgRA%>%|N8E90OpCKFT|o)cc8b2Yy8-9>}l>DrfoIp1tyvn zI@~%LP(~ZRpq=dm{@%6hJ~Lz0Df0v5xO$(*7dsidCkk+#hJ>w)}=clu4tQVR%0)cYd{lX{ykKLWW=@0E!`sD-uh6!=gwxrM@=nF* z!%OLsX;TB+-O;S#BH^|nS%46b$~Z@DeBOr!{T0UyQ!6Yy#X>xx67IDhOx6Oqx5?Io zTvj_III-YVHozG<)uEeQoucZI`^f)-gWpadTH>F0Rdr0!x1cKL5%;kMYDiZhZDR<* zrUS`)eq@u@GY8v9)xKdbRWjzGG5A)${U8Qq{Pf$IFL9k+>Zlun<9xTF>r^ql>I%q^IF`qt&$usozbV$_Q2OPN$MfRFMl z#unD*`%sak_>PoFs7-9YHm<*eiZvgAlP%j4*n~jFu=c&;KXn}^O@WZLZ?HD3nHQBT;uo)42yY__3ckJg zSqI~4cs?%?0U4-5m4A%MnXqrQ-}~XkaEbqV^wUa8jHjjj+gCAFp9iBP#dbx)15Ui# zhichZ&J%D2rj7;?JJy#juW$m@YVX}l&&D1pOZNe`#SEOw23to0^%PJ>rqO?_&$=}l z8@qfRZQdJoaCIXn^r@R?F&}G0*Oo3^Ld?)q(8oJo4r;_^1ID=ad=n8Hv>D`WJEz>W{jaG07&$`#M;J^W4Y>Xvx_F8t!CErcu4cGi-r{p@qhyrfSlvVo}#7 zxKd^zMN&#Nk*2s(OppWkt@U1;!*xnSP#FoKD8K~c+j0=%61n`fgH?hT%vjB*dg zDqcEbnBL*KJ*&YU9lD_V0c)yLm{GkEH1c=)T%1ei>1eNUn)X;ItWFGEXT_52fk4%h z-BrYE>oG^P4t?ES$0Mt4@{s{`+&ut61Df|>b*vTXE17~$tdt$URC`4G2j2z)o%xTf zv7Y|`5}y0dQq?9364D5pa->$a*BW4-KMVy+(`?h(wCgE zb;#X46C~rc^tM)vlieZc)~;Qs!SoS@YT1uvH$u60=JJVU#fLd2hzd7XS619` zCpR-7Br2*t9a4H_De({u8FVJCmkEB4!EsWh=B z@w|6rxH#7w*j=}LNTExY^5&%I7fJ{>8WrPO>+@5o-SoB%z3*h#0K^ccYVn)w(y zGM9LM`^S>oyX0N;)bvdpD@YOd3&L_Ax9iJJ2Oj>yhn20)7-H&nOo?5{*+ZocPxr7W>J z`RMu|zl#gV1cpjKKSR&z{$V`dKL0IIerj5%`Usy`^>}F5tdorWT4??h9fj)l_OTmHW~r z%A&Y|xq(?}qba6>3MrbYDI%333IgwiHFLiopZ7T456_32<0us_&g;DX`}RBkbap(w z5xfHo0)aOEappHy5J>F;2&8&;!#d!d3r1syfgfs-XFQ`oply2<|CIi4{q+;@rc#va zXMZru09BB31MiRDPQc@p=0@CWe;v%@@{~6?rKVqsciOJJ zee14FmopkWUcK9|`FCZ{`nMrgIr}t?w2hBwzWOWpV!(#BEugocah>t28gzJb#f9$q z`H>N3MoS#j>9pc@x4k{+=e%0V-RW$J|5t?{xI25$YPuUZ6`kguLb4K@Mjz3M`-7jHss-{aOz=#Kx z4(&O##^cn5vMOede@eqKYVzk-+FkUd)wx=RnJ#+q?Yh(_Cw8lk-8`5c^J1fUSa{^IZ)uKSV|ryJu-$Iqpg+Aq$nwrP%(51}t6gE$(}bt5lG;34 zbiZve5OSVkzlB&z@DR`>^IudYSx{O0!f2A<5Y(_$wRL>&UDe2!nw7|g`9_|ZIX8J> zs(oj~yLVT*4%zJ{o^vCU%MPimEj8A*KNm5EwDrVRRW?lJPueXS2aQVvbb528>$cYM z$;@M|KfqoQL&+_%3omqKkqfQgZPr&J$wvN3=2kjyRR6jN4|vd92P-{-jT>eO#OyqW zeEx@_>cE1g`Nq(NH^z*x;`sM}c7LAe$Z6dMx^eILw$>l|{**YFkJL3tmpJB|p{t*! zfamaboV98&yvh7!ZB>we$^77(zk|(YZVS!dMZUNKEt~aBkO~LQbyJqd*QLHV4e(;a z$Mlv6*LMliMFmOo4Iye5wB8VYcGIf;x`Ks=-d)oyY&AG&1S6MrEd-xP5YLpEmtbTo zQ(cDicc934z|`-$QuI{R{bvM5!YR1%Q{u%1fJfos4U^fEzm6=Xe_iRuX;ECh(j+^S z>`P0Q&l1Vuqri%#$ZThTN_zlmzeXq#k5Ic0rU+U@28f#ch+S)6Onos45XPd~Cx(gW zl9uH7C0nYnv&d}`8b8s$-QL)G0kbW@%s@G9!fO3+KgQ!S!VPBK7DKz7&X0u0>~)1ecm z&#Z>Pyi4`Jem}D~w6Y;sEeLUlc--v-DUOSdQwvgxJgf$g+!fIdxsQk;Illb*pcaV= z$q$;kkAti(Ph{ob)uGE{*EWLI50(OyDkWAnB0UZ0t6xgPzG^AxG^}5{r{z&`letP{ z)!K@x)s@fQ-|7y^@tu0Qb8QD>H)m~}Mb{e52Yv=P^l9xXY_%8swes4>7L5iii+y|V zG6%4LB`8%qMC9={%g(n2ATuKs8t^Xt1HkcEdz&DsTu|6+gJ{Z;9J-_M=?Kl&C0AyrhABK9@hR)YVyQRu19u)4k*^)-z}z+M=X6z zuM%T+hss)XUR&n1xh1LAOhs{^FjMtZ-ygm)0yGwCu2n^S;SSRY_5Fc3y^sg1h|sC@ zy3y>#+EdYG&@}fl3S{B_-$s%Tv91zqUDxDr&AzGyqv#Q2ObJq2o0;$i&d8rpPx606 zvfpn9$YRgG_80`xei!?SpzKkSBA$V&2lCHb?Mp&6DG#dDdFj>d%~fs9+xp**((-52 z)cqY0BtBlv5N^dD4Z{$EeHB_9$gD|JNi_zP{wrBpRb4!4{gX@Qw~z{ zWLmXaA+Q<&Bw2;)Hjo71_rKR01(0l=V0%K_JhYFbQ{0={(_iVOQi`e0`_)sWTmIWZ zbHQ0S;#Gc7(-GDQXR9)#yH<@t!h;&-YUEK#zQD!(|3ks%{KIZ1%F;Y|ogVAo*2Og; z-5=>+)dx8OM)K=zQx_f|@3)6}YzWnPX;~gXsxJ1c-ZH%Ab*dB5#ox91t%*YFFQEp- zq9N>;1`k?F&$zKUYO1|wjuCznSYSDs7p@2c%=&TUlb$^krJ>tv(Lv;|T9pT$Equ7=6mA0;y6$?qls~DQpjESJp}xj2 z7F()tFWm>p%{50;R+npZ>B5SLqGhUhcy5rjGT$UHK-7a10bk)=09_s5Y-d3f`X=!y zYYrl6q1!wn{Mq>RpraqneJtBUF^zwi?5QdK4%CXRDxQi+vp@aZ@&a{JkWy>@zk4}z z0Iu-U`bx!weOl!+!L2UK!Aq|QQnoBv;V$TaYsxOr<@XaVIQ(92jM&WsLKJTE^84Eb z{&Ml49fA@!nh>J)h0>E*1bC(Bh1b7i>329PRa{H%4+>9=S&b`nXA(=#`oo61HUeZf zNblk9dlo$~P%m7eM1_G^U&Uvka*N%bd|N~8Z>|p>@hY3b)%xn@62Z@?E1x@b4IkWR z$7**Z9fQDvd`OKmZ#C~D>Sk9~noq-6EQwb0xwFFsq>?@8xf0U(&o=V#W6_E(M^U9MApwtCakAoFvzDwB} zniKW$=%B!d8pW!TkOv(@+B|e}6e8~W2VcSb zBi&rIXrvzg9I}5XBOBM$3_QPC+`G#iN37qUsK_+!H#3`j4;VZ83WqM`Oj78pU34FI zvLZGBfyQie4UO!vq^K3w7sji9lsQBEE+vU=>%XjYuHY-r zN8hNu z=-4CsbC=RQLNq+`W1E2t9$S>{}FGhLVQA)=YZOw7-3zEv~Rb)uq-uGF*lK4tz1 z{ixFRueKn;_)$)qfrHL6;CfMhfcKgdrX6zd_SpNF6G2DUhH{wIJ-Az2*E>nb(&3bm z6sdjm_uE5{Jzuv;XCl6DCSF=JWQSkzWpO|c>eu7VbkZv4ak<8e7YN+s%mjRRG2l=p6 ze@W~!y(;xhr23EHGx`UG+E-Uc)Kb6ESM&S4+oM=NaJuHpSz9sSG@rqDA$L%K+eWTw zXzV3hlU;{KqG$BpG_1Rg`y0-z9t9*|@AjeYTtSMcsVh2v&>tgp{V^03KDBGH;2{!?jMA|R98iQLxqAH>N+rWWJRa1mt6?SkG9`iB7d~^s97P_ZFv47l1 zOo7$>vp}^A3M>^-q9Oj$oZZXF{kFNXAxsQrr2!yF8Yu1g>ga{qPih!q{lNZNS4B}@n1Lg zUT-b9C^T0MZXA;W>8inad6R&a)Nsyk!}}1ZWO!?Oyj&{EX+85OlMvLcd2L_+?^1(@ z^_eWG!e(j@aon^&mi4)4zw#EScuy%y;XV0y!xY&XWe;~zN(2xcZRo~2fXTux_spDN2!R5k#Ai+kX*9~(w%V)$fgt2 zDzvsDlT>f$Et`!0#qPD7jY71~H%u4rU5+If6<*?dxd%E^YN&7fH1B1&OUV@({{&`P zQ|~x&4tzSoC+W1%8Rq=cCFGWhrk%f1p3p{PIjFAgzhxtqqDGhMO=p-mvEL^Gx}S%} zT@K#`4x$XmVxWv)57!nem!eopnNL_8^XX6wZ+kuhy_R90kDOL8&AsSweYU&KofJ<)Emv5JHIg*O%8Eg3hM#dZe+vJ{@6782NfG?)ntvmwH13{YildAA|=p`)PafVO)1K)wI!d+n|y zb)Ly4RVQ$f=8>6o$NNnj&s`ww@7|b---;+F7Oc})ymZHFP$lL_04aRGeJd|o3yr(w z{ybOtIJS>{YN9t*oS<|ca3J1tIFzt?VdJNEYK-Q^eM{h#~H7*P3Y4XypLkwF-1!rs@_3ONmbZOPfWzduVnVmUjpN<#Ztly{^> zW&9zDOmQpN*%S^o-DDkshh{vVE3mQR=5u*NXCFOWO6Z90i0xQ}W9*b3WfrwRiZP?Y z)Q}@0d^n4N44fFI6{R?AC!#-xV3tC2Qd+XtgJfRy(bksquPN6kwa- zuwub%(rY$qKcWOOSf}A^nG@NkBCygfp!Zt} zc)Kd;4!zNl?TPa=+Qy0R#9LaZ7{(PV~!RialL!2tvbS6rnffx->8}5rmcd$ zmcy+m#qAZ5PD%xB#;QBOo|^d|-^VLuE9=742)DZwSZN^nAGL!wQz?%>|L?kP$c zbx6zeioj8L2~YuAwJ^A|PCRNB8p@kNBeoTE+M$DO%=?WUA6CffTYhQ^c+11afU~0m z&wGzQ2|pWc{6u>A!87qH{5A3zV3-OBKE^4vx`Yn{UjP?rz(ZmNr}47`+Xu7!LeYpn z^FOgIhx)`w*q(>%BPb#lor0?ko0IqidRp!KHI@q7=2%4%GLCS=N>W&Yylqjqp)M7* zknfZ955u85I`TGMVfjncmo*&^#+xY7_!CXS9(XZ2rM)HyH^iGpHK7af>nMYZ*h@MU zp=_J+#|h{qL>8sxvf)c6?cQCoOl$oIBElx)TG3&R(6!q>gZmKT_Nb;l=@)0 zc`;VNlKU!w&S7FrgT|Bda8c8F`HdY%<#Q2#^>8e8SMNbr?oFZ>N^{i4;^xi$V?PR> z(1x716Cu?_rz4znF$U6@o(F0Kvn z{5HNBk$ZuBdj|GHd2hFUmpi7hoV(!1`x*1RAm(l`_2`KLwRw0^*>O(}e?bGDKMUg- zh53ks;{%5lVjhr{O;n?i-sbxg=e)UCOyfUyV|MNk=}JGI#=vz*DD!Q#^J;aAE4b8x z8SuV4!p-teo9%X2q6TwDz4w;3*BO083n{Te)S_CCLp(v`5LDJ)G5d#n1uCD`fLWXU zB&-)9P?vC1p+ztw650*)F^qoIW}GNy+JyRMoRrk@85Sn%U@ez$Z$s$=Wcd(n-Z)6qFK$lDC{^Bn{4J$$>Cd(5U$633qghZ)NlU|{RRpYox|H;@eQ8Cq} z$qSdpOegLbg#1qNSKn+_3_BDKiHVt88g7rn&3QXrif^3p4o1EdhOm^%)%H8a5N(}x zI2fTjhH~dLsxks5eK;7L*yTqq89C1L;E94VGZd-UuoDnk39aB`M3JK^r5#7BuT=9e ziU3woQZB&23Az|9jB?(Hmm92g2<`cBpBCn7=f2LU=YG->$!urGbu<^nr$$#&-<*f4SIG%6pjy|27)L3uY8 zDNbgDPN<~(lr&cj1uXcw{7VCDxAD8}6~g)=vAZs=#)W?{vM*F)%qwf$zF{bq0|g4f zT_)j(k6uo`gWCrLcukZ3_`2dYXY5^ZdxE5r&!pxpRma$;?#D)o-`G10)AI3Jq5bL8 z;6RG5>rgq3+QV|UniarphL{*v+VLW@jQ!3=fTq+|d5%}Gm~_s1K9OGqlZRUi$tcR3`{^3H3eH|a(_%~=DHJNX z%*#`E8cPRvIZW0I&8(KZc3i{Tn_eXxO>v65hs+dsviemVkJqA2$LmZSrK4t*3lg}T z!)S3!vXoi5?WCt&)Y(@-pP;0AqG!<=zX!6bp%UC+NMZqNxN&C|m21g~pp;#LCpqn@ zYHwmtGFOTUW+D{X_fm+?OE%(F`3i>+wWQf75IoK9k$PEb&-v6(&m@!SMwXlT&6QD% z9$krTTV&sMeR;k0Q!eCeOB}(3(!y7t@YRoys+xfr_EosUZqS2c)tfGK zFNGj$@nE>elf!nL{KK8;%}DPPnK8nLcoWn9z1!dr6>LXYEx6@W>n{o& zzG6(H4^OBB<349n8Ori*x&#;xSM z=BB8YyqcCMz?dEN!#s&Lc&BYBwtXSK{e2TqLT@#5-wLRDY7r#b_2c6$j#nSpZp7|y zT=sny&~`}GqjCEvT|KW(GkT^)!#4l+2WeA@xz_JW_ig-dJ_esfJUd`-0$Xs{{uEO` z-*(LRCU+Ad`Ui~i;g&Kp?fWy;$J;G|!l}R(12sJCI&@K|y)!+e(WT1$aH+^_9hxT8 zG?}SR8Eh9i++Oj?%6MhJ;*ms*hZjs247HSXP&xNeBX{*|e>o3>e*CyU_cF4{!F{k1x5HDZvYohd zZv|261@$^DSz^dn894{^ch;^fiQ!@S&Tw+~{kRh3qw*g;|}@=m-cLvLVPd8av^yXP#e#jI|y z@vE1x5;>c7jIArb^F%dG8&jrMsy3mzO>U}ITaN18Wj^ak3OP3Gzvm5Zh?FIt;;t41 z5&GL;hcsHlR=u497rOjqR~DS)i%xduA_-)nMMnf(BZGOz&U_lYmHH+cC`#PvSamEx zvQL3PvLORvU`2PsdCWD<`I1=z`fVxwT0SmPwr)0}jP}muCY>GwzTi+gzL4M-N9(~W zStNh0$`wHtoj>A0j!O5HA*x~Ev1pg(Ex&2Es>3v|=Mr3C)|UuI-R3#4hv$_&vCZJ; zntF+=pF48YflN$T>F_(AWyHMJu7W_GKE@j!y1UkKG;Ez z!IFl1`kkOHi%)j`Agl)4s-kBZDy~aMw5T{s_=$&*?ab?H2%)&K&qW%BVP=Z0wEmtw z$-i{T8hd-k!qRCd@A^M=mzs{aNRjW7e4MJ|D$Q;5=9nG~bHTJwxP2gtZH1cW9=sfQF z99pCK;D$krI<-R$lq$9I4=bpi9O5g?Yuko}fbBGG@{hAr z0;a500oC=p%Y$n(MFg?2_CIA}2i}2QeU>4FXZY)grhUf-sNS>wS2M#wB;wBQC2<0J ze-+H)0l^NrDVD zw&mkbuut(K3S|-?4YUK1h4vgTf9b*BV0X*MoB>N{!xihD>9_Qb3-sL|48a$Ll~k4u zavt<&e7%ViM{DiuDOlIu26fnHg5~UDG+KTB_>N!RMzHR?qlr2WRfL=l9xv!NS20 z7jyj%>Ki@4=%u@Olsf;BrCK2Nd~glyuqbGAR;3=fdME8StP9~T+=Fe2~}oFMYIu0IFHKI zaNW^YIsHE@$?}fW;%(G09% zDJgRVUsFHdVY!9vdX6}8vFV6Az26~jD0j(=SLp0($0n=R2PeKw>2T8aPyvm}Ex9o3 zxcR|`^V8R*Bf|}`(}_jjWy<^t&vq3}3+qpjiv;*`hp3IMU;ALOk6T}+=iwl5?am}W zkLi`^5~vFwcY6T43w#p3@ggoNbVt3xQ|(wUC*z?IT)wL|(^&hR6JjQW4m+iJ|3I?K z_Hxh}?Zu`l|K-p&2aU>EimeVJd)C;P;wj*TAUOCPu688_haIE^k^iXJxBH=oc$9ls zkKo3Z_NOBIEl3@o{J{`$Yu)C?1JxyX9;@HTk+bjV5v^NOXing0NgsRS?Z9%C;8n_K{k6dW6|xbj(#Zs*CLrwKo0TPn9v)MsT3e$~bUj zX=*)r22}Oa^jH3RSQTLBX8Fgl5 zHDh=I-`2DV#m`^({ojU5(f*IHR>I*w`Y~EpK2l1gb0K!BrD>aZTiVnA2Ip&L?!Vx& zy$t03s0g>on;vHn9^wA#=uXbnVuN0D)cHrb$_T7+rY@8Q|K~un%W+}~LP%gp$JRqV z`~=g!y=1cL#p2{I{{(qa6#1DQefGsf7L=y)hTRTNhs=E@`b&{BJow+1oUyJu-Hl)R zROpVTd*6v`LoCBYpGpCX^LmAl z7Gt3I5MIEu>$o&d5WC9NL5Lt!TO_BXNG2-I{IkuoWz=GoEEm$Vx%agFlP@gV~MVct(R%dqOhs;oHfkzP%Lni*=u%RYU!UNMC_7kTwINa54u09=TlE8vcl6b!x zuh~$g~zqLsADz|NAq|pSW@fhMw@@uhZEJ zpYT3Sq0>YzXqV&HPKaal56!7(o>#^=p^ZMi5pw}IY|}Hr?@l&5<*wKQ}fe)dsr1t zeFaMbza5aXZTbnO>r|m&>|%4ldIa|XsBe3>$&MzlE}uCabGPDh@qz9AHhAlOM2otX zVjC!;$d$XZ323OrE$qX5+1F*`<=(|6>cAd^+LzYi?=u%Nuk}-Ac+cJ%O>nl8h~AV4 zPRC}T-+84rr2U-LvRUBJGm5TwbytUKwKx_N(8BWEzymsWp6%3vQz&w;Gk3LQr;9Yx ze#0fIDXc2Yy^$Ru*NZlR!5v$}$*p6J{!K^5+t^0^f3akF;Z>ja9~&0<1=dvNQmli5 zO9W^hII<#%a8GOhT>QcG;dpbz2lxt4R2vih3wbQajxJKrwX9xek&DgynMUsx|NMfC zk@6;2pDi$Z1eT|hKR&vVz0epf8)}Hb6i|)<9nyU=1HRp6sdYVX!6QKbf7HmsbvM4s zMVqF6*natG?@8^K^)L1qk8slM2!|2R@VDo%kHN=Z$Zd1y8?;&gp_!s7lw4XEn z!4i%SY^S!ctp^25dQM-YN43Zt=X{<;)e_FPDEjW) zhkz^4z672ZsTSZfEitgzaC6Lr!(I-XwX(Qckx+^9b=O~)`gP=D?l-Pm@ytM@v)`+y zZD-IY8@4zHT<4#4*7!1Gn+W>@{z%XFnBfqjm3X`p9t#0%bv64`z@KgU6_~WG9PNIQ zM(k`Y)(_X|&a6b9)Slr?k;=x}8!g>R3Lv(y69=)sH~!dr0 z&MeDRz5A;F76&yD%Lne7DwQbj9Rq2;?JvZ zUwtun7Wb&R^*5;C=cE!r2#UyHV3*pqYHUD`s5B7VpF=7FL79IEF>QbdkMd|+)0T-GHGL){^Lgfj%zIS-hmnLfhrOkFk4sBL88Z9^YUQd4HS0!8+eyxY z`XzuQM!Zw-tc;KecvX4ThxAUQ-Gr}|-l9o=nBxYLa9bVEVn&2gj+Q#U5qL-iq8Mos z^&q8`+m%_*_TO0y^p-VFw4{gzS^YUZ7;hZeI7P$0pmT%9@q)k_t{`(#Er~_XE*69! zoj@QnjHa{YV3e?jYpETU-zJ;iug_gUO_Pe6gyz$~WF4z1e9@h=wXY#zxE=UN(_vTqA4b8+)&)4S|=^uCR#dWmPE*p|eneBS#V98lSuFWbbP! zGYaEOiz_6?z!ZaGAobE|K8a5$n|NxJG*g;mrf5|jaWr11b1fsP|FV{Q`zpNEdn4M$S*tIlV&(1rG`B=j*@T#vwoL$bzB zB>K}~d|E8vkTvZ7k0>As@z;^XY%`JJig**mp(AIQe<6N4q zYHVXdZ$>(Pi`$l`HUpzGfGrI4l>!+;pC9$09_q&Z;LbKSG@!n{Eb?riM{cn16bK;OND%YU3GVOc5R*er%5 zOcJQ<$-if7ta>jvu{CDzQ{#q}%ecT}9hW;iAJwwG-}9NquvyBETe&5*<=HJj#{nfb@X=1&e;08x0K(Gr+rOc@@>mo^_RlYh6Hl$2CBweF?;LOh|h*YZ=1?2Q<0yw^AI z-S*gp#|2w50{J8;rGa4EpN(HNz4}0{i8)9J=-&9(Qol+sdkTKkj{i~bv{Gq1hL9x` ztKjvH!CoKaceLZFLRw}8ehBW(F{GHn8bZm=oD@=rDEbkpp?Vuc#Ji35iG7TWGI3crkdkF34TA_#bPdDme*!cx-XhKSx8LIRv*Rpeuj+HgN;M$5zjS z7OuOWbRLIYEo*gBx^9pt*mvha!*)N@Lffqs5=!#xAPQvJLOj=0O4da})tvoAO9R+3e+H{ztyv zQ=z8JQ*@cuFghgTU?dSv@aqIu7iNwEEuitB9=V``4i?i{o=LdAwVOOW#boh#=|h^m8F03`G>itqN&x7ufxRvmL*6T#*v8u)HaY zl8I-f=w5+m7SEZhQav?95@xIK$`q&>EOAR@1;+hh3e8^FdtJC98-|y7IsLNy@ky4!G*aro43y8W zMmD+5{lhkX;G`}VHACLa2xvWME4fw9VZ`)f*dezE`k?Dyhs#(~?_*E=nYP9EjlurD zSDy{y1)|4G@CXRVB__XZ8)9*$Z8=t0PJL^pj_!W7pu7!@8&pTzk3EfbaDMv9C94cj zgi%{a)K%J+H}LNvAl9^caG3vvlH;D^xue`^K)tge^FW(q;7xs#dP?0W9%O>JfzzS1 z3=(e*NJ8cIJD-ipl6gsl9a_ndRoA2tuVy~gEVkmw=Yww)(}YF2u~#4mC`C40pmWd9 z1126gTw|KF>GI4%McHnSO3jWNY7WAo5NetB+yZK1{D!Vy`;%B9&Blt!urvSys~7p7 z9Dt%;9^nEVx5F#Y=S&F1k8j$ZG*jCWK`>$#`w`^`|9r5U#u*}7h+;t#8TOtQ6E1SE zV0~VaBsyh~?bx;0@`-7wZvv~0zIG}6dk5?@vE}m@qsAwwLIJYFn}_mVNO~6G_UM4W)Y|HL)W~%tqG&GWEn0au5Hyc*CAx};kHR(@ ze{xUwNTjt`ZjB-zc0fT2VL^{f^fVv@KK7LOjpbd0B(VZ~8s<`7qMbM+<>C7i1@yUDWkqvYzf<1S(Z|8 z)=h|!1iwd>~1!bh>pcD3a>(YiCJi*JJO?DH^z9V`w{Y#ioV zZ_6$?9v~%mBf`Bzr4!-G@w|uc>f)9XlpcyrnD1vLjbNa4)#@e?v>f6cXRVG3#lTn@ zeU%hggN)S(E9iZnUe2!PG3q0B0$x)w<5^^py)SH#fr~K4+M83vqDEU^a!eStM{E*E zFS!MaGA1)(kG%=xAJ}Ead@&V2XOVl{O>#x2k~htN!Wz^xcaRSmd1Hd@_4HYmmX%&v zJ3~RBkZ|h^F5ICz&#fyTNSxkIMYI@i4D3#vYQ94I)ITx2PB8eqc4cd7)Tw`q0kvk& ziwQHD+d6%s4aaJ@5jz;zIW;@2x&4RVhGKu#Tdo}q=atM_p)k1Eg0W70WAwQFf>{8m z#UR_s3fyQfEF*{)Wi9M#l-Sn-PkS?PRrM5-8y46G;W-19N;9EHYw4CQ_)NZN4zVXx z*%8KsXy7dKJQ>NjSXO&>p4Wx8KuVBuI1j~G*8X_OyLK~>S?C#$yta>us{uxwa_~PR z9+Gym;6|Oh{10hto4p*4n-l2)?6ER#kGeC^{v)H`(|un-uYHi;I#%N7YKfo?qNfqD*SFW|1i9x+g>tMLp#cng${@JuV!`|jZBBgS=ljr zQ-@_sf}z#siLx!sUB4K&jOE#1VkffwHEaqWvhO%dS(I-yHsb|WU?Q?QRHReu*sC@@ zHQYcYP%4zF^SZME-kBdYwjvtNr@y*l5I|aKz64gX_;GFEV2tGOapHo~y;|QvKNufH zuMZ+z=J(nAvaS%0_v6`!?ZinRw)6H2xIT*q!ulG<)9!`5_9jqoLtT`Pij}(tC?DxL z-39+(Imjnvag9KbGY+9qCaBvK}+xDn-re{ueo=Lw}d#6)g1Gw-c5x6?2S$jP~jc$ zzbKqSZEQAxh>^d`uC!#8m82x@Cth%PMmp?f$58r)!%(bZVF*Cqv`WE&XxYkifgvym z-K$cI+pK^*ei^zlL_{k^rYJ@S*Ito+nqFQva&TU(fDJ(oH%UwGS^zi&(Xq8%c?`bR z1zcM)g7q9HT*PX$Tb4f}#T4wjS$YHjw)_teCc*t7{wz$Rw^LDR-F+~~VM{1G`IVM? zAuvGJI`egk3|J^~RI3g00bY)egmnQA(5S!uz9Cei!g*l?P_FB_zvey_3u_Og*Gyfu^>Fa_ppddO`Ak@&YMwD_Y)?!d0JP#@PO?N)#=GUH<)P6 zJLkqwzn~VN6iNGU`N-W4zZ0`b2hUd!&sUU0+#s=FKU5wJQEFZJni>KGX}$XNmKNy7 zTL5nN?<>~!-^c$yhMN6<2dDjC`;D=?jR2JR+d%?8#~Ps@e)^&Hd7J_uI0H=cTYO$6 zl49MM)DJGX|(v;pR*x)qXJ7|-~MIBb1;1t>X$0|4X*eNkE? z^w<0U!V-0pKRs7qD!n0r4u`)Ywo;!2DSOVydP7Rkz<76L>l%E~*kL^J8#MGIP%mqO z{(9%%CER^3zxs2srpv$^02QzCFtQG=LxgyukJ2T!utVyp*8q729_vfzK_`9S@3m`E zpOhYU)2g8XSe{9m^HmZafWbZgRe@sk1;Bp$99z8;zZ$02w-g#wrS|H>WVZd9lwfLjsP~R0O%_vE!=>v2CRtmc?3_@T42mx*B3bJ0fM_H`NMdAJw}57I~+&ZtSs3DxsALAXSatRL+4~Re@#PjBQ*}@M=o6Z6L8ps3*cmP(X zvu!pXzbORyf?B^RNhr(vQYGxM{1z5jcmYpp@kP%9v4yzBO^ZahfY?s}d$s!wi86+0uE zFRPuSG_NslG%<8xsvsiV4!gLP7VmdZ$y3fmBOh{Uih8^Ws(Rm)87O3kS02^ z3=stIMXh4UO#nn2cZf+K+(J+Ywbh#QAZN$Aj{xoYP&8^6FNDE}Mgs$TrW0>d@;d{+ z-R4FHAnvD6jTfsen;-%Jsdfe=_ydeWD2SjX;4@>WFwNpW+x~DkZDFTWfpa2dsMZj2 z5gYJk4(0d`oTxn?#{qBA(`uG+|UQt&M2^TqFf0vh#u zAI9!k`r+$sTPpz<9eF`>{=jbdb{+tx^S~X{H-SGO0Ox67qJZNO3hNJmoys1%JgQQl zLlcq#{8RRlX-ojB=oXA3+?@w|6?uvM~tjM%A*_8)A0(=Q78-XS<>eLC;~&McMrf)7=qle6IFC%Hr6+?$$!p4GiztSCT8wd8@39kUha$8{o2X04 zIN8Cm=w64zytNZg+!dh8t*?<@uXlz&3&7`7O3n@28+iuRW@|cr6I~0ELhX(KJ*?VN zTbAO?igYY^9R-MGI${2C+e0=*PBj*tVr*g>7kdzmEJq6ca#Xb1k&nXIB(B z*#^LVWsBzGkgrF*_g9E|t~f7geB;wFFk3YCy1dO*>5;9cYC54W8aUr#@BNs&%ASrx$tWtEmb^Ye7hHLhz5g(Wq|I!MUcsLCX)!C;w&@ zk+}*1DA#I6-fAM8r)X|M650+{K0BXRSw(4osN7XIU#Ytunbm`L8U#$St_f!sjb@IrlLyJY0YW+9L8*Ech9tljc8>4}S znOzVG;;5wp8HffxbVgEIsZCh2FeMYCo32R~V~fhUeTKki?%^ z-VrE;j+Sl~jl^+X5Ac%HYmlcUO;T>%pAMeGq5ZTxa5a^gT`Xe%lX*i-^^4%mRR{SM z9A}d-Eg1>Nm}X1tU=DsWux0)oysa6(#OB-X*5?4ExZ!m*+g2ZYmCy&zE#2*gC<7~N zojY0%W@*;pE3j4I51OwDhS)ms@QK<@ziMqG7Gbjp9}yPB&y`Y#7ak?~Db<5Lr~TOi z{sL757&wEh41c9E`=JWRg?hkB{7LZLXQekgs8a340PJD)u^y9Lgnwv3zg#?E#4=;w zK$Xds*eTz6@)rn?fq*)xE6@%=a#lP+)%CPfg`)T^S^xS9xx!Zf?SkJ`qrf$qSMs+w zK7Vk{Lnm~A+IE6ss(NeoBsvf2w4Q12?IFY|q*d*?)-ELbH4xILEGXc}DH0AhoF-}! z6b)%N4Gd}CM_L&ICyE?FoJTa|+f+xwI!^4tbAU5j^4vZH2zIXGm>N58^}qQsJ_BWt zWUXJ5fYWSpVwQ$nnPiqVVo02f7|H~omgz0Ph}3rglut5qQX;`>`+10aClm$WFqX zFcZKEN`zP;2_y_!s|aCGF#!{S@7bVT@AnUU>-*u|Yq^##A=&%c&v4(@eO=dGTbScg zxgg*RA@+n6%(0fqzFYQ!L(0ZLc?{TWUeikpJ;i=XA*MPQ{-eXwqPI2+RRhbTHl>HJ z%%u8F%PYx#AeeSbe>DzOl==h8b^Un?v*RG;3V}G~*j_01rV86jFP(u;3$D^snbxj> zNyxbtP>i+5+<~dG((U*ctQzPjU8eba-0Pf>tXG2}R3B?QRBN!ZkCW9|Q?-Ebuxd*x z1zUD&Y|INhZS6>DjPsM|ofaHSVR17At4n?&pB-Odv*q{w4~%I z`retmsI-Rc|8&pGeO5W9AqJtG+jfQu_<5wxQbNLcFpds_ps?|BgIP53xb#zLlA?6y zTS%FgUKQAq;U1{>#ku60o5=DWSsh9{h0CmhZ(rC%i)5s+qu`5}zjH!2ZMa33Ls6yJ z0Q-k!u)Bcj72Xncv8Ag9a%W}W0Tgx6UQm|m%c>h+6Tf$;|B%)Qwi54!c8$96A&2%2 z(*Q*y%$j8g6v93Yj^A=z2XPTkq(x_AR;m}dwJQ~nTF^!8R*n*wP)!crR5%@FZgeB* zc?;?xXX&DH#*g-&$vbbzP#3n}bqyoJBcS}C#c6Wc%>Vr|Fx9kbJYW6smMi;(Wh_c; zT>gX&DsjEyHYJdjmfvlU5Y)cCj(zmuH@{D(=%=80FqUEv-CVfG4RP7YNOqAbnz+iQ zon`p#;}we&6m&o#PWz6r#eWi$W=)f7@blO9if30pOo5h;jZzpnZ#yYdY zhII)phYt&#~^vlPwK$Ct_*p@pBP2shBT zWFI@+Bvaw^aOlm11(_D5t&%tOq2U&)bLF;@)%nBbD(RPt@w#vJ%Y)31y8c{-bq5mO zBo6++aY#M?sOyfh86cIltvq7^J*R6H!kh7#97RHVMTa-5?yk$RvRk2h)%7uA?5F-& zRc9!NKn56H!tT6@^FAr0_ytNe!EqtS#+_9cwD3{Y0|MtEnP!6Ngtwst!WWe-il=vs zjZe+6dMH(!Z(17uv;3|$#i(qFL%9l%<=&!%tglu($SP%+KiPHk15(db7K70`4VA#x zJv9CekD_ikV5}LN`|bX8dlU-0uovOe1rC9O@m}I!DY5%Os)vPqM%ZBS8b_bJAm~j2AP#0DHl`E@Qgmx0#OcWAjW60qnc1AIMsV`&Fr5(QZbMx1ssqO%wCd!+pJ>lcVubv}k z_vK17Mi)4wjL*=mJ#bO*LT-mQg@MKvJl+&_qxMP2u7d)XyA$4|nVP5!VxcIz!_D4j z;1Cep-i4^N4&miCz?m617)=q!B?Wp`GWLrC)^7U@v^noaHeS=dPRxMbqw4j8TR!;r zHMZak;HOSEPaY$OdbTf{TLfbE!~ugF2Rw~Hc%ZhUpxf;J!FE`!d`h%MeKtp}eX6_B zf*sX#UeIi^s;2WU+O6y6HowjQYl@N9Y{OP{Kg`fX{hnqhs8ZFtl(Fx#VV8I8a*T#M zQFhdJ6?M^KRcRmOt7x=8TFP-XfX683CwTa7Gcmh7a|WgwnU6uXa%->*%3ng8j1dP!J&g&QCaU1bHN#d=TW=Bmq+sw zXi-`h#1Ah}lvS3l>QGPrZ_6e5%883z5Abn${L>bh0WU8Ccba`K2ODYMu9)GeMC=EU zTSOU{IWWpzg;D8PRDd|UsSRy8jUs^x>V4~9*bS{)Kn%XH=STde@m}+pA>jPC&el&l zt8Um_xw?7;git&8vPB0k8`=B4D3*r^{s1(D1}EXTT`XgYohB5;dHTJyI95?ZZ)8(R z{2v;pPfAE@(E%18o*w7jP6xRhKfjw9-Z^pCk*BsYR@yqtQKWlAVR7gz`1?96YkS~PM zGh~Q|M+zMnh3M|b-Z##C5S-g8Ct<1)WFROM&F!$4P^r)RfZ<_tb45~JVMiS0!LHSr zv(7t+!&k+kXi1_(FGFY&f3~~(7V6o7K1_L+OVDQzze9JBJ-UBaExw+1)Hd@L+7xqv zp6XJei)UADcSvE_X~d8*8ctSWXEY~RnryD&@H&|;IYyULyDV& zNy)G%03cMG7-@*t(=(3sVRl{X-(@Hyp?`s(EsH)76UpgvIF&y<0#r7PdlTYGx%A#| zk+<`aOfCrzPkeuidbSX_9`1~VUa>mM=K${x@zXI$Qr0CkZPBJQOoP<0XG7xKOFbZx z{&W&k-CLTIRVf!NDc%9VVjt9$sOY2a3v_DoRCu&A)yf3ZsaG@(P0G@5DK7TIWh;AV zn=^0^1e+GF4vH!(dFq(LR6VQ_4Igk$xpQe_H`W~&!Q`+ttzi>* znp{#~8@y4_bHpsN0Sryn(>{Ez=Nu_33W66toU?S>|MqlXU#o!~#AUqC#F7P92ZOt& z;uH^srdGCn7SZ`n;#VeZ@}(0X@%X+t!v0vMwK>WI&Cz_S_tfRus|&cleD+X&MZbJ< z0Ef|XX>`Km^*+3OY1i5Q+0J>pem;l{NLyo{r|diku^Cy|qxNslNLDu>UMM@;1g-fh zIAududWtV~e?Q(<{}bYZxeuqK)j2VWs*|I+EM;Bct_Z7_2a2lY?;ZoWgLM;Fm)SDm z6|t}RSXJnzu^L%Wx>VI&w~O84?;-tsOs(I#43Ger(=}JC{HQJF_gxuL0DBBue_h;% zr$8qrs#H8|1B$1v+key3nA&fa!5+oB6ekMDW)jBCZBY!GZUXRE%Rc%He_1SCBmtLD zudFuQDweQstklL;*-YP#d&DB(DcQNbl^gLm)e$h8dFQI#4uyl7C5%^2dCf3<$egZQ zs0MTZw1Z)C@M}j4S^u-ebOD@**~=ifN*{eFI_lB)oxH)?Pr2^;=Ns2XTnquEmEVD- zxWWneqoolJ0lC1zGfJ&p^NQZ1*{D0$?V-!qgua7ulo<3TAF%j&E|~k5&Zgp_P{IwV zXxe0*h}aEc7-}tkcD`nvm&fp}gGmX|~AM3ws!dIWdCQ9ab70hs$=|?GMC#Bdkz80TsiBO2~=c z3P#H#?L=l#9BV(mf@%|Rf21#7U7zhI8BIH9o2zvWBz`r!9$lyo)h8ZT-c!MY{c=zl zvXpN#qyHcIw#5fY?pQAYN7aWUJuQ*SIMA!|)KjRg&6lziPRSK>{WoQ`Svcn@DO&3@ zh+F#FVXMkXn;n?|>Ig>*G}HBf>qrTetr?*=^%%&;v7x=>@U(c z7Hm=r*O5jQq+9j{cJU`cEU`q;nc^W{4{9!KRtK)sjI-;uFq)85#I(L-r2E3|)aa=y;Sf@e9fu&DIR2ySVc3d*KP^nVh!X@&^W_Isnty3i3t(z^j32 zqKTbJlOF@<(fx-oTZxC=Y78fq#<8cSN|Ie{NqYZ@E}xKG=AkdLlY0NIA=AbW@Gs<1 zxGUe3TnvP$$={b|IAYRrY2}r+7&@H$0b~PVh;v)da?H29<~0PrI=HPGx5G3l00S=T zx_bqvIUpygXQ~IAM0>zE&<_x+q@OZNRz6te&;W7{Rl|k&S0(U9kCO=XJ*m_!Qv%`O1F{9xhT_l?#aBqq$N?6=EtbBC$?+X)7vf}$Z{-4XKB`x?+};N z5x;uIlxq?2;%(mxRec_T^nGAI+_xE36fAB78PxCCBhXGp7VL>(PF(E!POBDs0h-6o z9>Q(Xs>cpM{=_V71cYKt;iJIE6qTp?FL-bI0;in;MepbLT&@wYzK|ZH4Zn(sJ=Mck z4+1EE5fk>YKf%1nzg&g(l?}MOCD6?C0bhrufA;hC7I6XD5uEAPg4Jbc+Y%>;uWr|K z@CsC$JHEzI`;n(1z;=ee?IzIwvFX>=M(#WgAfYapvfQ^2?+9YcoHf@IDD2a6I7EcQ zpcF>ZXGl5X9g-H`bE}+K3Yl?;+1t=-?sw7^v-`idf4qM0k7KugCfq-(-X%X*yvp>c zcEoh8VY}b@^_z!(G9B6)^my~34SyZEY5ezNzuUgiu`#ZYDRo$enln!vq0e@;^uv|NAAM$L@cJ>wjn0|Fd;cs--f2T{1fA*IQs-2fWp_ zc{xTQ!kLBX$-x@GM5?U1u5Rwt*smJ97l+SJ^wXopm5*=j*8cp0T9GpIe78AS@!>t7 z2kb*i{O}sFV7-puE}QRSe}*b2Y|cLL3LQl?n>m1^`~AcWsu~?CxIzNueB-|FzI>DvIi@*y zh)LVCcMY^=0%-DmkK^pM=K+J7b*wBEXKx#8078Kdn1J-1U>V;R{J^*)17~Y_I};9u z45Uaf(K=f&!xU8Sb4~kv)+KuZ5|~O|0pJ@L1QeBKwa(1URIFkm***`c`LMCV%XiC9 zY$Sw-hp&qRiO*BFA||37XAMC68GL|(K88KrlDN;XJ|bM7f$(*f;;_-esm#`NakkG* z`46~5G?K(|!D)#-lx{Yv2vbzG4iv^ywfi`zkBYN?vc3;XCA7ijjE1d74?}E~p_81^ z(7sRP^wD=5o?yt9UG&Z=wtAM}H>{1)2_Jj6vh>=ffIWJ$zUS`9`SHFI!_}zeitV5w><~&$iyp{(&r?6SfRq+Rbi}Yhm?|;Jl=~)R1f=*-N|e zd9WB+fN-FRX&pDS_ZvzNPKaJK&^U{&bURad?tD=s^*Me)$RURhr`pPr0+lw_&ZxTt zoIuhT;@P-0O$2Trs;5Z~6-pbUo}~LkFgSia^HW2OhJ|1gkTr|P9&E5`cFoR<{I+}L zO+I%;&;nAi(=R*z+wq@p!2a00S zZ+^^G6<$TB1~SjsnMA!pe94;u^@Ugo&yo@r$El0sT=k;>T-QGM>6Kwp1L6!$JOoEc zI6o``JqRm>!~3(IVb31hkTm}wj!Xs0Qsd@%ZQ#$uGvXhqxpME>J7ksbU3!`H>1LG8 zQ;;$x_#pI(S4f>>Gq&m3Q~cb~u0_M8S6pUFP5MXb(rl8i^aL|%Kil>mdT74Q@p@849M=|v|-A*WP0ugFHfa4A`2_d9?KMO<9 zA1?Stu0QDvm8C|alF^$4rany_C#FJZKQ!waJ4l;S&!DPlW>8Tv)=2=SoB~z0`8Y+4 zEPVEcrms;o``o&Sd$qpR|6AbF)VNDGB;G*L@J`VL)FBr^EmibV`^khD)7|uY{Mppo z1;>w7b_A0R|CZHI7tUG)&;3|rvGNS{nC0#04*fYigdZqPw&^Q_emDb5hu)yg!LH_G z^vaJ^;DmcoWo=-}$kD0}N;M<*&g?rmHo`HW#0D#Q!+aL=2Xpe_h1egA)&&EBD}EFF zZe&K`TRDC{>PhD*WzJoHK{r^_;jGLij-WBP`wwduR68*6sq@$)wb5+TPloCl+ufD) zbqlT0QO(64J~xgS+6zvodXnbIo+m%xFea}&`wF4|!V_kuybB>Hcl59JJBw(^`Ux)c zC6Vh_P^qKR%!YKoLrw zF;2_1iV>eKT5B@^!S3#=exx(Nwde;}q-qH~P} z*ve!)hGF*lh9G_Loq8mjDt5mn1$20MU)Ao6%-*V?4&?k?zJ_eleOpyj!mKF=(Zpr| zDiYau0X1Fb7w|i>@hyA|Ml1TWtoK2Gz-esuOo44tZf}W`#~7*!U4=Qg_qL!mHEkx< z#h&a@Lbry}s5vh0T{eddFLMk4hqr>MYygCm?2}-jEUh`+a{&yC-f>$LGZ38?y>NcPCPk0FV!7& zFIwNnK8l$oetP?^eYm?0d_IbAF_+Ll4pg4%DxZO&MYPEN;)R8QEqi1^x)5KIwlBh*YLQ~h0EFv^dK?u8mkLXL5*ZA>qT-OkVnE*j6U z1O5Fe5WmrVJJtvaUU1*Upqkeyh^kQ2iyF0d+2A|rEd(4Sn!~-&k~WRd_`uRIdQvR| zP#9-$gXOJ-FCBHvN1XK&SMIzm4GCckNboKUR_|xrp3gD38^_Zaut)ifoH^tn-)8|k(iDY zE?S2FzVeWT3%F)fxz~2yZOWIqkL(yFJ+(yCF#y_R6#M^@@_WZ2?Y!!#V5j-}99o+* zlzRZ8x(lVAKf`+Od_nh$S4f<&Jh6A&OT|e-zc#`>XAcLE2jZa12q#6f-<67ZH*fBQ zJ#k6;pdb^;8a;>J$mXj$QPLZuO%J?vyGGwXdNc7uF?Od< zv6Y4#_s_3 zp^z%3in%c}(NA&;T|NsUlD#NMuu}`{7sYor0^mD!r>WWgNIjGTA&^uM`qu1fqG9rT zE=I1mABrd=*q}t{AIWn~qds57`pl%d)%1r$UW9@NH0`qm)<=_;=90KyH=_?=4q`Jt z0645%RP67h%*pWHQEY9k@m^|X3_VX{b_fUP7WO*s?yO{-?U-AW&T;L&kDA^R*GJfE zY)QE6f!#WB$e0(kuJ>%;94BugOy(hiEn&l$OaEo{RW+6SjKXJo@RYuC;42(u81L}r+x#B%Dgn0g<;MyXQiMB% zoXy5Zp^Ok3NB`Qv7x(DDb08|G7XkP_Nx6&C&8J~L7YPwpRlvtF5d@_$@47eQoGzNc z*6TV@Qjq5%2|pt6;_5akOMK;27t-TLs}s zGXto<(9W_!Ii<<7v^&tg)O{PKQ~Y)HYfmVRxfs5nXgTVwEHlv9L(I|p7P_&5`aE|< zYM=(;s#}``@nQ0DG){*oT^&z5koETQl50fSUB?|%+WNDEChOz&zlttggaUG9Fb97( zgw`){$Z?};O^0rSe$RznC>*pW1W6Ir((z*u8M{U}iZ*QlF^;qmbq5_B)j~Ka8Qz22 z!Pt%)6Z26yXa|AZ7FE4V6JTkn-bnMe#)=2n-$2>(yKI?sD1=g_h!@=_n_Q7H68(}m zBhyi5TEy=0V_VDk=84BDJd)tk^i4o(2Cg<1z;=?o#P~jCdBE3dDPQeuB9Dx`1D>^x?5v%?Co?eItnQ(ld4FJJmVUF>_0 z_%sorQq>=Qev=PZDH>$(?=p}>8X$y6VTKS1$G*N6c3bU$U;{JRC!50K9Y$rI6hTcc(+YC?kX>JV`9~s+`0G7{tJx#&y z))tT(yxDbCq1sIURiOK%)&&;%J7(V9wYHXZ19}V7=U#6r=4wY1sL(C!BfyvGzmiw- z`=H&Qhca6{LAo?RU(B)0JqP(6TnHq4Br_6;!d@OiwTC^xs_C{Wi}s;f?gNoLU4C=1V0e4V8C} zGqdMKO@}q596hIbfFN$q5m%`=+ZNrkK4Ns@MF7rKYf|q=Xk%HolfBFh0zfs(s_&g{ zFl>iei^ccLq4-7*Yimjl8ZU4x0G^(0!UnDM&1`P8BZko%k0lB=pd|b5FVgvvr4L;u zs|StGSoz#c3|mOIdQn$yPYP8$&T6%i_2Y_iRq_Ov)Wg>HGI^3S;z67OB?w_iO=>K3Jz$8(a&f8pNFyRy~jY)J- zBDofOM*1L2)t%iSXD9Ap%17Pr(0A|7U|W3Fa12c@pN95tXvvRO2kvSQ$r80O=0#HZO0dtVt-v& z7+qQr3t?Zuw&K~cQv!e<)me2MFHp7cF|JX6H+V#c6 zCJj$%4F?k@9Se=zQQpem1h0#niEb{>)>B^zYU(qQ+=ZvV(dE$Tjvr8mfVnxp=LqUK zr-f|zrh47|p&PsQn*_jWZ9ZZC!SU0m^R#D!)5Z)(xDp>*`V19t?N%26X%!xm99f=cR)~Wp9mpQ)GDjN7Lg@WgKm9X+F_!tpB6H z6#7Ga1(V0As&q}M?Jc$hD}T%gXE`lqWw3QiC5iATBU%P%QcS5EnW^F`6~sicV1HM4 zCa0I&I5kK5AE66jYd?|*DZc2l{(zZ>7%=<$_@#NWyG#jOrKif6Exlh<>(ibj%s zyj;AmYjuLz4E@eQC_B1BVk3rk|we58W<`Uifv`^{p zfht+8p?STfQyUl=nGZlJ!Fuj0x}V{u4ErcbS`bC$SqI4cNM%&>;3Agde9X3~7)5hn z@4YW2lR7lmaKl1@XG?@8te5b+B|{Zo|99|O<21E~eqpF`_2u>IyN35ph{SHATGe{7 zxTj<1;Y~f7u?6t~MZl+}qyXDu)rz$15=6;uuAR?trd(bz=ueIRt-pRaD;Asu|MvX83$?kkOXqo5rtn|0l{%g?Maaz;@@AHf?Jc5K5P8xMAUdY@yG z>D7KqJk+3hr185_1Evq9C>U-K`!*>X{Dsb#=L8Apn;N-p4RjG+(7N3?a68RsCg=Cc z);#(qFY;UUr3Ayv4geG^8957ZH4nSOEcz!&;bIWIKRW8fM=%`Taz=sLtXO0qrSB5326D% za)o4eQ2kPN55`~F73!e+t)lvtdsVKpjc=HUWV1M#NKtYsvGS% zNvWZ+pfK2gJQI%7BbeSj*j2a?W>q1ajETRYZyZpT>RJ#FbxgceG`AY62xKr7-stM5 znPFz56N3I@nm#47U~W@*EezJC>sGuJWpSne5ojWAn@MM#vLKN2rL6ZkCpAIRIg|rB z0JQj!@a`%5jt4-V0D~l z1=@Alz6}TrSX^Qf>Vnx!PIUw$aiX!eHfk!STxqfve~q=@MKU)BdJd$eaHeJ`9GC{J zVI(^R-d|`zvcFBt%BT2H!l=gYV%F3C(dbKoZUk}Nx&nKu?!)QZDd3G%K}p(Xj8pC+ zdg8QL)01K2+RhYOU03_5=iIK?m^+KYC^G1x(>mEDoQh2=5J$LgBC2N9^bGa=gB;kb z?2^gF$-kteshZ$0^zAk~@)mvHybjJ$ji4pzZ^!AV_w;jA_oA-QI{JFf(01>2UuJnB zzJtODXzaZypgBwDkmQty0E5PFac_fDLm*WpXgAdKscuO=D%_9XQ6&8M=gq|0=~BH( zwEYx8qKeZkP9)@|p1_?rEBAXUMc7R55w_X7SAy)mQcB9^uP9Z7B+^G@M`F+9IfP>m zS;z+hdCO?_Cf9mmhyPSX#M2vf@+f!X3SQN^c+8>Px6ZF)v>9(r`b@$k`!P>)v6Ys3 z{}El3eFSvZih#0ZF1_D^z>gI`SA23wVGjFyS@COAwau2qkQHc^YUOo2jt?wCHy@dd zI|8GjMH#ZnPj-u1XD!YjGLOS2Z3H@lmWp{$s{cdS?+pd+Fq7!zN1(^ z{DUIVxX%VO#}XYn)67{?)KWngli`ie@VPK4EnC4<@e54qj82*fXIF6fcd&TjvxW9RiR;ssV5v~3H`$zcZ_LAKUiBUIYei0`c1Eb&b0Ae zg-#v|!ue3eBhn#e)aKa&KeDqsEAYO|ll!ukT5OjF9xE(*@U_ zFZW2YKNFhjW56zvj=YmBA*by0+&94ZDsEUQk>_f^)x*oS0X*Hi>*TR&IFLP4ZmTS` zahYn0uDdBW9~(Y*e&@?ja|6GwJqPE3bfV(rJw5HZ%%SM7JPguaru!`!8~;tAsW68N zLKdE0G!~+Nj@p;T?r_hB)7chsyao>#E~T*53&vJga8@yYL$&StWRg;$mZIxwu=ew0 zcU_t6=QStw-r`~dX=iNP^K!Hz;B7wP8lAb_?vxwodVG6XvK`O)s{^K&tW8jiiCJq8 z8EMLJ03Qo)bUK!sYHURD#T1}7TD*VsJ@M`vC5Y5F?{9Q3qrbdx!}_ETKc}Po?r^lC zb#a6pz65b$znBN^p?TI2GzMF+$sKVTiX4JN%?$80#>i*!OO3xrjN&U`vg~+Z&0YMe zjx8KMNn#dlt88!=)X6=zPa%MsNE%mb|DplDr5nYxx2m;Yc-DW5T_O)Jz0~}aBjMZX zhG5S`*-zw^JCFTY6`oo7@0I6W#_soQaye5xaSS*z*K1nq{RsGX*eT6_>-~74<+umx z7GXJEaY%E+(57n7vm2g2e%Jw^{8S4z*TxQY7oCd{>m>*ehm`kRu2Y=Np zS7cs1y8u%RjQ)kq&q^hsV$+{dNuosJ$zvshF}%yw3a=E_6dRk*W}S{YEOyIeD3~lt zIy%SR&*xEecL#^%XT~2MN1t4<=o;VBw3YHFA?XFXc;u_8rdifzH^Gy}n`SeLy7=)! zmt5EtM0exrbPuH@DNtI8^Id7l5f{1GDGE?i7anEo11FtcJtxO8w$+A zt!<1bBa_@$rv$~FHz;bq^t5{InlvU+P59@^FAUjP*>7iL^l>WI=Y4(7+-I~lq_@Vu zpVG^!5{!=d-We5@o+K^h69YQO^FERT=r7m)%R!|)k$%%GxNCX~z_pj}eFX#}g{W?! zn4ud#L}pf_t?kQbPQPGcclk#S6)|&8mBT+mRucmVKUi_Ns}OyXEpuHL7-VgWy5X7b zMUG@CN~M-K6<s-b_79LGXSh4_3*~Fx>Z1DEHWCAnRAD$#2DDU6V2ld7MXn11!Z{aL zK~1hrrQYh@z&@vht@#?XRf=YF`$`=vcwonU7qmu-xDRBvYWEUaI^3ozV0Q!E5c%h* zv~3C@07^VlN?|7`2jo}nWq_p+&hnNa$u#aetsB@fff=P5=(98U90jTcJ4wXOq)JY_cE(u=D=y8m!Dpb=5m7m`n9V zTxYYXH6yF*S5}xxeG51=-)0*1ZP2>cZTE}&8<^k<>i5K+3zwTxn1hMdK9Z7+u27XH z{6!A*neA~AX%f8oKGwD0bcpZAdw3N85s!05-N_oyeYTXshsSw~? zochA{@TUJSS#9|Z{4ee9^F;jrmO%KJj#6n&mCwx?O6N|p>*``od|`J!mm^dNx9-*D z9O~@CP($QWS6tI*9cL9|YEEOfL&C?V;f+~N|JOp52*WZ6rkB}1F59Kn$C$%ppFKAO za~?6O9yK50cNnaUMY05GmR-jhJ}NLt5RBpf5HBo*VOX|P@pWdx!I}{6Cm-rn=t=JA zRM>`<-)LhYyMV+cfI)i5uy#4A=0y@6h96)sWZCAra|!zkChFEhlEAuiWj|9cpI`6Wm4L--yCrg=VogAY};lfu&M;`HKxpy)hED z*ye5*?gg%H6aAM>#bHO8a>ow_5Y$!bou?h!Q1aZ0CTC>jAJ~p&&qMrrJsfQz9 z^ESHj=(y5WX8z7QUw$$YAsA&jAxbOhYwMEqZ)3h>OUZe+kkP+PzX9k{QB9PpXwvaBk&C+>3%#@Hw?BVd zTMxpt$`hJo;5P!xIs?C*k*%o8U6)<=FbEzr!__NyU*CW{CQj_yW)}JSN%k?Oq~YJf z61z+$Nrf?^@#M<^(}~0CU1Acra*Mbz*D_@YTgPg58VX1B!e|+0%);M+W zrbH#n=AQvw{$&a&h?#TcW^vv|31AqC0I%xv-FYV=U%bBwFyMpe*P~tqcX!#9_4HFd z|F;NnZZLgfl3_fGsxBYlbPwRWzc7td>MfBewSyDD%qh=!S;e}YY3=dP2aB#Q|K{qe z987h+S>IJjvIPJ5%ZQ4R5pB%tc9*=mVipta(VOp^#kca1bLanhK4v84D?>IU#>j_0 zat)QnpOV6BV*dJce&X-y{3B6sW&I!dP1C-B?`Sr^Q1A^;mzQ>c>>0#touk`)Sr}}D z^b=*AS;^t0agr1@K_A}^2Q5vBvKBU2xy!8<&UjcVV-L?gZVy?->hPr0?mFaJIsV7v z*%Su&WchS+$e>c(^35ohlOOxsi}zl3HLFF*ZI~RMYdc@8E99qs89Y%sct()`-vtak zvUHv%&tfXpXD!r(^nO!rnGk$FUo3(=eACgrxsrgJ-E3qeuG8z-VF&M9%kI8X%D$Wp_!jO zgZ`EJd7nDm@X~qDN5H5MY*1fT7zyFZ4+Co?)B9$w?QkE}kf%+pfGXvT8!{n-7{!dN zu;6}OEw@^X@BrkC=d;B@Tv@N~)-4QKd`uF_W4a+^uxYN9Z6h2Rcg$|Nv`~0v+c#Z< z@o$@W=f{S}vdyQP%Tt)_v^*7Whr;I8uFo;FNZ<N4jZe%vVf^%if0 zVQ3H^Gs4bqQBKc7HSitT{Dy3en-&fEv2UI$V-h10yB|pX6-^+zq*T2Q`8-R@*MTKg z{+rr2J?9HHwC?pw;7lgH6;?03X7C!^s6M?d=PQpaNrIMMZpeDo=P24JT*P+}7NR_o z>OZvZ{)3(GA$Lrwx>75L%Vmp7-pKB4hVkEUz0hIgPXV`d_r?1qohj&!IDC=3_}$3Y zFVKN%UskQRIhgL=+8S#br*W8QCKzz#OmpkkRr1l;i-+gBBY6!XKVSK1e8}L^Up1Gw zRNwNUZI`?xk+4E^Uh%2++5#O7eZw`MdwdeS9F3cW9$DXTYbJ-uqt*F`s=PP!E$v>P z4yN+TQ$|c7n^g}_;%7FV0Ut^fMSsoa2Mgen?uePr0o*#_}3FDXbyb zY_XKOWy{!auO_>;?TZg@1%o1g;b|cKW2BwB&5S)ce32hj(D8PtVRp>CZUZ>=P!&O|m;lH;>jMgQw_XAE zqPIhV8T-iFwy&1SvO_=@_nA5>LZoe?p|B_>aqC*bDDqa9UVMBt#N&F&{m9XW=Q_f( z7wSg9f?hg%*sANfjKg((iQO1IogR|iqOm)B!O_y&AyM(JdhQZ++o#_Wx~lV=qnGwN zCdI6SZ$Oz_FxAiH&&Z1pwf*)Yx!8Lk=`^h1E-Kywh}8hGzyb2kPp2<}#&BSpTfWM_ zTA62oKA^zGFYu9D#e!AdJ(C^DUmzTwyQ-ZX8NL`WxHNfdbM?@jqrzo#0yMGMCH*^x6X4f#a4Lxk)A@5I$h?@pSU+>iz7Y!J_l@8-RqKBmkydDq*N z)pChbcqnSDdg$DoS>}uLX{oybnR;)w&iQea!tRo5ENx2tU*svGpBLlphlM?HMN0?= z9?;fDHc#gqb$*eDRC=YqeUum{_ce&F79{aj=OH3d<3o{E6G0mJ@TfRibsZ{uVu^OY z>31jmj$g(72V=H@EV%K4#a56;MgcaJsF}C~#NPpLzBHgNW~R+1uzjSABzh4 zMmAfqnXC~b0UD8;pNolO_Y%O%+5@q3SCDTisnT9{f zBeRUELAk}rMm}m~+SC`!SB$85AO9a{!zo-e?R-} z146jEh){T)9FFCvnnNQW-`z}VPPDr2+ihAP27Fzsip&J`(g&ATtR;8;p`kEad2(IM zFad3}%mDHo{eEGp?DfOMRd-c?(&bjVy|^bg>f1Duj@~MWLH!AOqej8jC!rr2 z`&*tVP&#M>3_%IR?n2AS&6-ady>%;+y?6X(?Q6hh5TBjni0)BG0oJ|DY1>@(W*$}3 z7)^N(XGJw&8fgTFF{h2pN{>8Kv5U_c+^N%K_#1r;5|) zhhBpGkTJ_u4aJwA248;!pTwNmGvJh&gZFuDW4%t}Q5;ZV3*ivdE}*dh@Wz_D9lrtA z4D3sx1y59qeS>Ofo0`2*Z_UDX+|2fk3tgAcUzlh`MOl{Hmd%>{=kmjIIrL!lThV;i ze^n^YTYF^3T`|^DAC|AxzXq*g^KBvFa^RTk^e%8XxQE;})#MRA8 zaPxzu->j>D66hFPG~VKne`IO)*A}bA#ME_3Yp>)>44rGX2Cn<<$@$YsLyLD=yy3aM z%9+-Ny;O3u_XdsB#@{PDr~5-B^gqilSV^v1)-_FCI4{{Q&R3R{{}OP8rtt}A9@tl^ zVFv++QiJ~42Uz++?`W_Ws#+Wa0NB$|0ikKy&>DTFBJLDxFWunMtxF1$oei zQEIq^4~x})_2UJm*$X!<-OKG(F*SH2x}{kfF{2<-xH#6%&5GF@zuR%^r^UZNLT1^K zA=wLkrP&MC{?Kd1Ul(Q%8)QRi#B~t`K9aa;uohZkz=1g$PaKS&yvmbB7>iDuW<-^= zms^`klApQJt%a+zcRh)?JsVyCcP9WJGvDKO8vePV>gpjr<(u2(KBQ0|k(#fPn5NAF<$s3Fr z^>(mYlD_&tG~5^BmlSkZ*{S5-xUTI<28K21r)n0nHZRs58I3|*CyzhY4z@E2J014Kq zIJqC^8Ft(w!aG|3JNe#;kGMM%d*4k6&U1aj_nyj)rdcuDkEv8XIvKkoF8pTb1+YCzMtm)3G1E;XO9akfmL! z>x?P%dpDUAv^49OtudOg@af|4MNxIk`}ClhX%MG`H@D_1|C*hhYFw)fSug)M-bJJ^ zdxK_(ejw5rR-bse;dBrCj`TbO|0mtd*6?kqneAwZSyL8${XxUIknyqKrfSHwT)AY9 z-g!`||NPmrR`ho7&=Zv}UE{$~&}zI|pm~bj#P5J8-|xe8k8hFC_PZ68cDTw;7X2#s zT0^LN21)CDZOKkSd8s&-_ldP^o8EmF{LvfC?@2pxWrNR7ycGqX8!bMhmSQMsAVH2H9sSpF*7 z^NAWDi(~H`>bhgXXBWyHK>M(|UhiAzoGV(gCsbg?|vub-I18F!_OLYcOGpt zUo725Bh5}yL*ylJv^}@Cl@k~dzk$!rwA7H$JbUdfdsgI3M83G#ad;>f z{tx8;;*|321XnI{VbeOkZMjV)Lp>!=|3q|5SEylGHv8BWoTze*BV`K(C#MWfpZh`P zQB(j3gf~%}(cft@%dXo?X0CWoRHzkP_=LC}6o@O6JBSr6oOHTEiP%m^qIXzmPmGB@ zlbPh`A#9+ai)8*HQ*E@M=}E@RJE0rQ`PPBq&F?xDhhGNkY#kp+%jxC5!|CkP;*>3I zz6xnEX4D5S_%0->dKwvck^2*oc&5DT%(&8-&6WG9&;oljGYh;(`E8D#6SSX6BqZ=lst1`}wt8Q&)Zf zLmkFJ3Ylf_Lj~`gflNJ<908E>L{EW`CvUhNca7!9tv<|khUJ86_|oX)&vuJz#$>9Q zA9<5B%_#l;he#Hypr}@jP%E12+5K#GQ>&~!|FvtJU)wnU8KWqGWlL&Y+g(qHcNWZu zgGhBZiLt7m^Xpc+!?JmYr(FPxjXj$Hx#nNWnLc+*DGD60Ni@OB7;SF`_F55?!t~kQ z^Y_nG>M;$Y$?uY27E|1``9IxmsZ!f{V}W1q>a2aP#{<%9M~k?f#Z837t+4j%V)uct z!KNpEbvBCLyu=GQ)$5$<6(xox&D&JG7!_TGyypWrSm*4n)8hI$@s_TMkSzYE;_Xjc zID}!)8DaIv*|B!^YW^a(F(i@T+!3o1 z{$fO*zQpD8k3NSoypfKQ&$N{uT>``8{BJ*cbJy5NYyK|JX5(wUsoA8?C=wJj zGPPBI`h)eC#rn0}n`X8Si(Wlpy$Zp@O^dpGLCQQr1R%9Ir+r6TfW6$R`$|Ybvox)V z)=yPiSGrDQDg1izOrwaY`DiWNiyo;_L}n&Orw7V95BEe-ErsCnKkJpJtIxKI7=;)X zl6+6owU^g)n2BoU8ul*4tid+7km->ufv^zovi-p&`uI?brSMyk{hEi)jSaVRBbJ#6 z$OxN(s~rk~>OeK>tF|gNN$j2(f$nn$ZD>krt1+L$x%n_3BdkX?mdT_k_IF^)85d!2 z!m(<&zWllC9E}PH(OQZJ=Ya{d)6Rn5jt6f?AUAY*cv^L2N2DsGb+HsUYY5-`Sjb>i zNMu$>Ixv#B8SI>~TrFZq%r@eV6Nt6;Mi8av0Gmn|!lfNcK`lh+BwzsX93?Uq%irt8 z=xCRhu$QLu$&=G?h3S+ALXoXA3R@WUdM(eJxG7;t&zbBlq<#SS8D-UUQaf#$Y*UPx z7TGtrh7O|1DmrOWNIRzBue-y?8PxBBc)26EVsJN}5m3|GwiLHMI}h-+uhQB$%I7+E z(&p34i|v=U7F|@zW#ecw)Tg(au}g(^?B%8)`$JiBuDStCQufQ~=m$DHZk*1k_;5OF zekqEn!Ugx~(jp{^!a0R&)YZ*%+4hTcw#Jo8$P3Qb=)I@gl#0E^Jz5$hUgdz!rKY@a zsJ6UZ^3eF#n4|C<|IU3O7xyF~l1B_hA4n9PUl05x$wzn(=-~FZ>qZWg+owVfv3hR- zuqjg;7loPI@o(>%6sKGT$pi}wD|udBaGKSgC&pSS;`BeF@r{a6C0Sp}TO4f3!Zb3~Lp@r0%{Iz3!Cxx8+X{VQxaqCd z(?tH33*TGDgoXDp;ub%-Qsv@ui&ZHSCGjX%o_@$XD#o%{a|g(X>{1OtgcqNUvb7|? z>h3(7WHcn|q}P(w&c)k23NW^!K%MG?(U_78cjn%{Hk*Ar8CyVepG=*kq|LH*_Pk+) zAXUrvT2n@|h>ARN|G0N$gwRWH#gBguBE!Yn+dy~T>_m2|U!$Aml zM=-)Y(bTzgM{}-p2Z|dk3z!aipinQvc3Rw~^zQ2biNVT$pO}(nnU6NMTw{dk$(9gBtTv|Qa ziaNEyJbA8CYnGtXIEb6(w4#eqmx=W&HH+TfVRVfw3OaPOeUszXGsT|%W3k#$^aX2M z@oq%zWtrXatc$teTWCe9(96j4U2xut;yK7KrFNSj6%Y=F`ss z|A^t210&hy0__mt;ltpM zHfH_-Pqe-gpD3nY<-#qN|BvSrw6rT6j8e8zt_(f;^Oy~{`T5RNv()POrq2;lfbYJx z5aQeOX`eVPX)p@D;ho=Q%HjYz0Ru*3jAT=j<-zjPzme)=i!a}V5|Yj39t50q3sKlI z6Grry#Sp1js5KYOU|lD*vZW3wEPb(4$iX6My&7pxkE4RxWpDwLZED)sk7p2SlZ0S;$Xf1>wkM#PeHt4P10(o$`gOwXB@mf$Z)`Dt#8Vv(<6Gou~-|{0x z&q?d<)L|AanqSPP@;B>NiOV?!?YS!+^k8S{ZcJG*?&weeV0T@{lX< zV_aM;45L?8H*vR=&MmhySL8ve^Y1^oGt+TEO^jO^3|vm)WUFVZS2>@lQ};Yf-r0;) z5iBc^RFpy#_to~aUSPUM&n6@arR)dwnSFHKE1Dt+y3xOGi#wB@t57XPGjAl~xsV1a zpPWUST)dhU4P#CR8T?q zVnQIPU~j{-elm zd79M|(#y^Yy zi8yhaJFVe{sdYJ4>;F4Nb3smjEL2>#0CZRV>ZH_lu_TBw)!tE;u=SQLkxXk}+7Ncu zO%;(IT_D16#+Ss+6q@dQ^z0dY_VoB+h9B}~NIEMlb4qRm$Ql|UD{(LHxua=svHy(k z1&^-GD%(eJ!#sHf=|->)jp}8~c)-gw43b{*sWiKaA>YA4T{z}fiqHd^&$ZktcwrFr zn0a>IEsgT@s4;bSV%HPu{xG#f%!`H_t72Q;+{yJHsO(o?I4iw`)Wc*(GM^q zLUMDMH&atdW6l*G;)6IGC$Nby@(|agMa3tUI9AF&%X1<{GDj~!;bJ{60LbXG5YI z4+rjcwJW1QsvsZ)HIQV)jr;Lp412MzU}t+~fV+q%M^`JzaN^vW=VqD8VbRpb?#-MX z43szzHR1vbKg)9yTtDS=Q_+lzNzMnT0n7b$aW$3Hy8ZY;<$~fmq}2DeSInrA)I^z- za%Au&aHiadnq+lkNqg1G#XZ>&WMfCy@wOYx`Pj~fdsWQ+z^~tAZwnvYa#WobrAwUm zlD}&?^BSbg<6V0AF_P#pu?916LGH4T zvAXi`0q2*9bZb7bC{ZkCFeK;hr(|ilcq_{{bi3(W;lGG~Bo}5e0?p!m6r#-iAEc|t z3~LlE4uiJME|@RR69>1=-w3ZXSsd98%Qk} zE>jcl3y09~Me=n>Q9$;X71U0QYo+MHD_5*9MS9_8f52KK~pt5xCmW}fj7 zff^*QLEL4c^>N&oUEW&$1tPYElJnV-s+-Aopsk?;2zfm{X4Pb@6{a#k>rBs9LrmBX zD~vH|<4JiqOb(0r(jjfds8lnMBzrg!tRIhClxt^_J$~F>e~8~*AHs;aW0*BmaQYyd5)bgAVf}AM>bQ~5a4<5KnsabBotG;qj@IR zSQX4g+%z?HP36=1TOI9{ppak8g!2-Az_@ko7yc0>(y)#Lypi5;4lNp_ZZ=<=C%g(&Z4vuE*j5ijqvWj;_Mwtuer{lyz%qqS?_g=Ls zdC>Kf)KGJnO3EW>ZycY=k6{#VHPK_nVm)(tx4?yyH}%zd_On@g&kF0ExCIDU>)OY} zx&|8Jb}8a$+x!zjX`_ij>S7bgrgCqsrZtFqgJvHz&*UjCcIW==4}U<9;<%J#8gc85 z9Lp`1iVBY0jBz?O@(>+Tl7o(--T}8D@*1SK^4D^nyKuj4V`q`g4_W41GdE=!l%c|D zO`Zz{7Ybg7wh_Ht(i+KA!GEMg%KfHJ3POet@H!mNLMcyi~``nw4tH-3NSOTg@RLzN7`RgWPVY zvJqTzGoB*`evMBd#Oua0A)NDw7_McZ;Ep!cw8-wEUZzjQS(ydlI_D5n|NA$&=uI zxo_%#9Tg-hp;8ftl|3`)dTtsUm!nlJG(knSN9{{25B%*(-Io@K=qpZExs-``8 zx}b|Ae6Rz}LE@vr29!{!X+tH_so4kNrds3lclGKGc-A^U16@Twc6? zyzosxy3ue+$e=TdSY-sS8wF+o0eU>!I88PvLgFhTZ+M?iXp(mB-UE!!M~TLPq5 zs!nFeX{t^Bq;sm``a!-!}6UyHQI9XUy=QFzHkC*MD4V3LXIl<1NK_S=Ur za2yD_kiT>KoT=1cHdn~X;@Rdl12`GMpXUr zHqL|Am}sfan%s8`8LUnjXqUU7W_4Qj`dH~}i(aJ);2}T1#*p8roeb+xd^a5Ky%eI7 z4N8WYoRK;aC$5dmIJZp<$y@n7Az$R_2+`OQ(-@%1({e>4-UW}T$$f4R!#bw{NTF#B zIMj`VxC|(|JHv8P2y{1{SX6o7=t)%6i=xKU) zA`FJBiVwEaQ*jS#dz#XCjpUwK1f~J(mANW!gE?2yv(z4+xZ;43I_Y$>!NP~0x$P>$ zB^@$rF;c2M|GP~G#1G@DPzi87dK?I}B2S#_d&Z{O*_I+@z#FNBEneouHad2yP@>%R z%4S{Z8on)uqih=bC9*M_(C*URw>GgWIx=wb`gWJU%?D;x^(XToA0Y##<18n%%5MS6 z&aiiG-g@(+DeJ4PQs0GjKsFMPZ^BWk9p{3FOt(Zz`qu++WVoe?>H@dY*qkDno&&ms z8}(U;riKas0==%vi~3Jo{k}R7Kzu$wVSTE9-6JIy{=yUp=qw|CvYD22I`3q8**)w; zbJf;^uKia3dEjXO02yo!MsFeGd(Fznrm15obz4!RMRG^9BulZGgJ?iK<%W9q+nxrI zB=BP8n%~J0TSlBC3pInc<{-tc8?bMabtGS4JCS7D>4q31(qWA2US0J<2RGIo$!a)_ z)G!b2eLx{$(eue}9uA$G6t-+G)r1G+?;ziCA$3&6m0TvWb0`F7t4|LUQU;oe3@3c2 zYe9PEqu6C`?(%vK_5EmzbsUMkO!6|}Sx*zQ5Ive!XFa-pw`zvLEq(NtY}WPqZzDHC z(oSR;DC!Oh=31D-$zQa71}un6wCo;pDme&wgpy($>{xh<<4edwNASj3#ULmy^L?l@ zZMqEWwZt;>E<}I9(q#YDxbmFIdM=r?C&p&nfoZG~u=;v3U)4~rqX5@sX+sVCiW2Xm zrED(0a&>ImDDoNvC7M+rpBTVw{mcAK5)zGnw)*bHapzk9tg!xBvx?-|SvI}HZuAcK zB2r+cR~~ig%QOnzg3-dnceDq0ntn>T17E$xPzbixz6(c7vs{hBRKQOWlj6Y@Mu$+L zuS8_}U!_pIjx>U<^p89iJ`D=CPD9d%G#1G$+*YmI=tBDBR*I6`^9IK=^g!d=I7X4T zZB8}N_<-E*{<%j!u#Z_SdI0q@05RPHVTJV;trfh!(VM?W->Osvm5jt$EiT=o;>7OB zT)$g`9Zlatam(0JJ_uFZLuK(6@)Q6iX#>aCc|*=4SqtsWuS)k(XuWJ5z@JMI`@rIo zC&#>fBop&iW*;jS+OYRB3~t5|E+s0fo70|kEaxIV5+lrKs@j((77C;fQ=(YV>a~42 z9(_Yae=zD}^(wy(V-CF;T!YUMF1^GsjWd4?8+es&KsgaLX{tWTNKvqZF>_6w@rW0r z7ZWhK4lECm#odv*|C~THWajZ$P-pNIBbp)Kz7A(()%)>g??!}- z;{*$AfUP4c+g`^#jz6&()5ci{TwvH{*Pm*wUn*{{Lpm1X>%wS#s8|`6wrvex)LbbT|FG!DHc{LQ5cvjd9{VbQ!!j4n@zHw8Z{6qmN)4Wxv78VnTVD1r&ocLo0pZXsyyIGO1nSvLs({l z0G3dlkPe%<2jF~`9~c0WSCXlSJ(&p!9e}c#m=|&w%Wqq%xpQkc29@VWw_BI0)Fvfri%2LHNnVD-dvv?kQpnCvvM0w#^p>J z=6VO0#7(hifFqDoL|EX|M-7yj?IYVn-gmjP%C-QbCVz4gXnqv*N zd>hG>24>)rVRptEWGcOMuHa`0!P|+a2;{Jh*eMMA1$!_G5I<#)nM*dgJoxhw^EVDE z)+(h^3t~UGJ^NvPMC+7;#RLzt21cs%?sVeER1IA?0SheKta@fU_;C(?taEA;=_JFM zamspmfd5-@Yp80Qs&yLeJcY5}!=u?r{B<6J42OPp)rE{^gU(36<&Ru+eWq|)a%_=m z{Ij{ySi9(inQ$fJ>Gq&2vb;g!=e(EFl9mpwj?kv@~D-!mxp(OQ>r98j2Q^*0DUW zoY`~Vbgq6{s!uoFl0sbn6b*E5Ggz(GSVnIKX04=yV+)SW*hydYYuwc%2f>?_Zqn#{oJ=PlD39Q$Wz4 z`^^eJ1Rd+mexJ3E^qqx|0#;qbdK%4BuUfv`}szjMz$AsmA zThhNlQ99-!Zy{N?&cgR?GM?x(jE>?s zy3J^t^BEA7j&F<(p)wb{e(k`-P>>hWZFCUn#}LLM%~Tsnj7PL=_Li&T zV{|ABSZCB;(kAJSTmp!X#c|?LN4J&pBv{dCkcIkw6*d!}NfxW$4GX*zD8PushS>6D zCqSMC$>IRw4vV;UK>q6G^{f;&{mS7~Op|ZW7Kh&gMA8Y(s)m3@0+7Re!gXK)eO*iq zUvJR1nvDF}rgvlMWfIHCaj;boJ3tZ8TFS2d~J-N?B)`V!WCqU4~lGCm${DIrl5+JH4Ac z#01R+%d+OnoM1D7Bx@JN3eAMq0tn6KCLykU$CqN8D*VQ`0YN4jOxw{Stn6xzEX*h0 zs7_hVFn%(e9=Ah{gY^24+Czzr!sd>kqZnUP>Tpb;#P#F74BYooHQcT87f!`Mp)^NY z#O}z38HE<4tIZCQNg9eb$YZ#X?+zVN?QuUuvM*VP?pZGS%TVKFy7Tms5UJmF_^V8q z8IO}P|nZ%^O3oR36;jqDdt-GH+#;Gkt z`=yTuY7FnU__%l{23J)V+=iN5k_|%^4vaizIg{#y{#>602Jkp=588(GW=?Wl@lPVB z$A(c+NLf7Khh3bJ8yhy5ew8`NGzbH+eUdD-Zt_U#)l}QgPh{<3pF!)=6jN`eQ|jP+ zg=F1=VcRQ#t=~{OOudjAS^z^enx78ZYX!*L97Z4Z!~&DMIfLsbqOx0j3klW>iqFBP zfNaPLgufanMnF0QqQm7%N)5ri&VYoZ8$Tro`x=JF=|rt!@Z=B4b%THmcJ)ILA6pQ^ zWXvy7s#U3my!(K9@z`U&uA&J%gt)jN@inr&mt!eI#skmnp6 zoPxYKY^_ZeTU!6A?Sf@V66955tm=THCsF}3J<0HBY27r8n?_q*E_rtY?B}qs4Rz`D z=0e^}d3Gb2bCfmmg@Az^fFmg`*C$GFlcvwqTbg8p~t~?ZZEg4Ba z2ADYY`qwkEFOu%Ul?%LRDiXo86Qk7vDqQyP<?_@?d1EZVn=?h!jL3WXo zDJ@~3P4gK<$0^N2*x_Tc9P5DVYJ{w=I0I3+F)O2^N0$t9KrT4G4Xm1vfpXUw$#p{; z1zuP6JgxvyXoFMdEF%#hUy7HF9#0T^8%uYM!m2t^ zyu(wD789HScjgqA!~$#p_s98vmd-EX{8T(48v}trL(ZGcfl|1}zS8`owd0eZto_b7h zN&M9gzXJW8jX8P%oeJ<|tY)b-urMy>^d~1SKH{NIx1I2HxnwohGPY zuiaf{!R%_=^JyMyT$D%>1c8q0d;?Gia8xiS&b2VNJunfrW>(-i*boE1-FB2wv~ru{ z&bhj$JS)Nw$Y-f6gX?fX>Jr(_hM8|#JPh1CTf_Pp^;Oj02uzVrq0<7sD}94(k=7(O z!_ooocP!WRLov*A>fHL*?y&>pt0;!jTp)Z$Lp#O{T_n^ts|3lihV?luJ%y%>3TNSC zo;N4xGC%GrV*k=^crV;EG0H9j*@qQ5S?KS)bmy;+3)a)K+oa;^m6)w^8`?V%_ir3jWVr;~LBE<|H>(*UlQ@kYR`2U`)W)_N5eC+H$qBz-U#l zu|psRH51Y6EoN7<_Z|`u<^FPj2;}wI^Sik>WBCGk{Wg&3#p#EWZI_A_l-thp8UMx7 zWYbcT9jF30bxX0r%1N8Hw=hxB;Ai9s&9>)$lVo+S>NgeutBBoXRmZhG{2_7^avI<`=P>9hlP`wo!SG>zg<@SR6EV^Fa> z6VI5w(%Arv|?I&2c^$V9u{8GBR=F@zaf<%#U; z@sn(W_Ljj<7hJeE7dqHj>+})H3s%;fvQ}K$T$$!`<6L$GBHWmVSVr7-20vhT5M$^~ z@Az_zo;wxP@Y%|(&U+h3oL3d2KUWzu!rp2$^oeHs7|=qBZ$2~7BD=(_0$9wd-tB5W zs{`FjpU&B(w-vvQ1u&0Bz{bA`U>Rd_Y1>pmiXUbNks98b6mC!KNSrVK7{Unjc;H%|?EN>5S@B?}c zgslZ{4p3R6>31W-tDc7#I59MG4qt=ZfaR#&krvGlo*)|}JQrnsO4bC( zE;6uTtnI~=yRdIg(&cISY{w%tmR7|)SfZv(b-;LJTBFF?NgE8TG0p?@zVu*&$bof2jI3_TqAFZOKtF4Zs8KUj1#^v5EKX|QA^Na5-V(OX}jvjf0bdApJ zp*tmTGs(`qRu~kw{KA(M_`?aZO>{mo8QhNaJE2wBJ7JYpyce9t zS!Nt(%AhXHu*Y>`#@U$iG8Uyh^k@$wveBH>#4|rdvO)(=ns-`eeVxB)TrZS`g?dIR3L=654+eHb;D|tBI z;U6X=ICWvuTG`@8M6}i-I8?NmUp=SLkD$3Vj<#QdUiV4_C zW25Ys+rye=1)=H8JCWlCotcTp)eG%a=eQAJ+Y3PfgJrL8?s*0IOfcOx^rnRB)^$k+EhYXe4ALzoPX@?M)G^8dCBXN>LXNup+m$3T#?jtK!QDKkG(`6rtT-sw>ukowLx51j zvWy=7>ZaB(2?I-vSi^}5Ct-&XupjJ$Ch!`ruaQ_u4~2>^?I%ox_2qoj0S?rfh`zaG z10pNbuGj~aI$#}^T_?TJ^!Zhy`k)8$oe1fY#TMjzSuIY65_C%9S?=OlI(N`jZ^*6#r+mM+$^`@;)L6D352+;RvGsq+ z2<%vXSQLr&VU{pP3RqX>P(UBqm^%pAcP3>0x?+SYX)h7_Bvv)Z#jUP`U{??)JrvL~ z?GZ=ujxISpXYGm7wIfx;-14uBYKqM>9Jjd#0GLeX+G$TQYsUO`ACtyxbq(5j8St4A zY*M@aAGIq55u1?)#`aNRCtLk_3rYAXKu~+maQm(fMB z{+ujtC-7_SIiM=VhK=LamgghKAlZtAEj5>Ksp6t^Ts9Ed(^hB82$0u=S6;%Vk-1ZN z*53VX+kSmbSk>gurrd5gkY%1MZ)v6R8u9UBLZZlI@HX3Jw2Do+1Qz~ri{e}H`ofjr zK*^r-+BWl;e|p0toM-Xz z0W#3p!t$QCLLd)VWuK3X5ly?qvmZ^RWr*lvATeLGtKzuLhR|&@_ct)G3s@|u+Dm0t-1%YC#y>X)={{ycGJxL?egSeDO{zX zF9hSd6-4yg4yguqZhgNa`5t?v!<0p3Gbx4ujScQ?S5uDFI+Vvo*II6%7gs=Q{o3Az zh_|E-#qgFwbt4G_4rbc5GwmwbT54YV0D4EpyeNMDy(o8Dne6W4N( z&sbmdwV*+a;y4$FNJDJKQ`Y4Cj$ZiKSpMJ6l3m}N&Qcjmqv~`3Z4mIqnBYG#`Qi44 z{9t>-*f^lpc;a`Yn5tSXr|GX<4v}%3nJZDn+lzIaF(4=g*wyjQ93>JN`(|+T+lOs4 zNANc{xT505MOD10TK|Yu+iTUC&wwm@gHzYKIHm$-xx;-GgRL!*QY&o}s@rO#QE-r@ zDxbn9#IoyI!aKglyazjKu7YEsw_wFc19ijlyx}DrmLQS9b9z2|lk1OsI@-SFs-OY> zAeYJrqF~b}FG|L!j67u9YtQ}{*wy(IhAB49jeU^6RR(af%GhDDQmB?-orHdva6S5W zK&eC1*JYQ%ZS?!CzsbKQ*)dLFi*-^H<4tE3#fD33|N~ zn;Yn>P-8BP5z)YmFvWu@GNM67!AuLGy3@B}E?FIv^&lz-j|IJ@gN>XYD11RYBMt57 zd2s!5FH_7u**kh}wI5ff)aqyA;a?r-1of4h$mauN)h>=?g7r+^J8%5FbwhqAQ8kTM zq*~}nAT--pF_4}CCH3cuqXXLxsShls1gEp22;>OE&`Em6@eJ{g`D*I?!+iJj8j zvNLHSrRza;atV`lZe&mH$o;Cz)0(X_4ol;ZiA@jkpB7$+F}KU1GuFc`pU3iF-{&$m zVwlG-jvlMNwEmYJWjAS#H&nW(WnuJ}$Z~>D~{QJgJF6{Ol2J0lc>?t_3`A%E>^40LnoJfT8 z$Fg^(s{HbM7OL&#frR8+@Oy+Z@@K<*kIL*-a5Xfy?;4=fPIt~Q z{tu?329Nk99V4P_orZ#Xr0B?PSbd&@nix8YzgDpPqUIgP_4ARj5neivokfT|kkHRn z(=F-Ta#L_0^_1y=K?zu6;)CL}TmZ1PRhdCvxF;tx6pLHNl*8cn#$6@f!(*IriE^x{ zUclBRYt`n{<4y7~zG`LQyZ45z+g2Uc!m>)pj1zsOp!r-m)Q<6BaYNW>2P#2syS2(w*j?+$1AJVxr#8$hZ(y@`IA`m1f!~Asxx7_nX18#16 zn`-1jp=?>{RP$tdc^7#4uJ4zV2I=Psc!qTbY&wT|+=`+{i0ByrIJ~zS&oq^O)5rN1 zh9w{)(#>xmc@50PY>~dvut_bJcL_e%V)YIui~%b|x?^HWC_35w9ct=V=_l9jLEY&5 za{hWT(R*BjS>Ta3)Ln#HTSB=s4nH1T&&me<6`oxg72x+Z8m0HPc!2BJ76m}ul<;`| zy-9{Le$L1#3qmZ?#}uGeKb<=T-m1_d;>ChK_q0VVJG0w)9|OPYWvBpID!y!Dr#J&i zjorOcK0524_0Ckzfj*j-Wk!q}T8S)MeAhUW21&fNGJkKFl*XPkUG1`D1nGrLJuAyK z_G3>6*3xdfA=k`qK-mVJ&lal@?=@i4Vwq0qi{(J-hJ|tVsO#`)UuLcsp}=!CUbK*6 zU5;RS@U2+%3Py2|vZZ>AdPj%lgV~!f6k+7L`lK_}U*k%)$ZfL0`=f3#B6bEh4n81Cs4{khbU`f*f_(CpB8kU zG)w7n(6>uo!St{^b?V5Lx=kqMLc$=LklcqQJn1Iu!@dRWy(Ipu#WJO`3T;n%&48aO zKTO@Buz_{1Dy6aJOAoPhf?e}iZR45soh45l*7OR7X^21%G~}xb zf7^~31e_@W;Um+M+Wd5qqK z${g5<{38aDO|!k1c!*>?7XTM(g)F4(eCV>)8V*?4g5a^svfe#I%hn0D_0u8;WNd}= zMdJD;e6DMBzEEtoRgomOK+p>(4^c>&6G`Iw^l|@Yw>4;Qmvtb{X^lB${U7O+w;(s3 zz>WinTr6WnB@`!G2qQsa&K5$!($-ae9$W9`E(tp3X_rN8HZn3qXW@86^C(9yc^7B- zj!&41(Y*e`-7D5;x#V@o-W#{=!S;2g4FyF<)lKr0n{n5cMae_YJh*wqcRP-I;Y>=E zVZA<{Fp>w{GqMHMf@?8#FGE`2I9+n}VAvEj^5k1jiRX{^?Y$wi$shPl__ySzbdtg6 z`CElao9=9$|@>7 zP&cSB0vnDL3LTqqY`!PC`(lAVwCx2DjC1hm%-kZBL*xBG&4=|v0~(o_nx#$|{PMKC zv3-@t?JIOUR0tyN-oT65|CnCuRgRgHLAA1YE8FekBUkImpTOhV-@hLa`VDjs27`?+ zEWoR(sy?K)6E*A!rlzK$oaM<42h375m(l2^XaMeu`~!N&;a0PIWzk=}3BdFC34r|x z9s|wp+)&-hrzhR#oV#CKsb8mYo>Z4FC><}XTbgamn(lx_1YPI7S+YAXq@jKPQ?MPi zzqL$VO1X_~V5;g|fBzo(L0Nd>9(jfPd)|E-rWTTaLjFYTPCd*3@|B?XW~hg<5}F0-GMVuH<*Ae}5N;0iRcVI{SUh$6I}WeIO=m2S2m?g=Cfo! zM#^_;3x%g-R(FGbk8oFtF*stVdhOb^rM^r>wO3`Ai6bK;qA7?5A7#JjXnmFIV9dyt zC4Tg;RRW;Wz54{rWQ4TU=B0;Pt}K;Qii>)B@w(@-Mh+;o`H!x0V()E|-SDR-G11?P z+&R8>?X3@*ksei5DOA>*GFRew+#hLs>N`7YCjMjk@S$?E|Cbkw5vJ#;O1V)3FA$qe z?RNh`*%*C4nli7(Dc&Pwk&eaz*8V2{Gd5j!6uXM~+f-~@n@shsb;4_ zqsA74zt){d+keIBpK?L=0@{#xaoa=t3&T&FYQYspzn0u3zN@h>_4RUlSGN^^7HE;^ ze3+lz(V~|&>ig@Iyz@gYPAAk9tW*%65R+M*b~5-ACRt_{ju(&0mcGn-W=6&@z$z<% z*Q1(^?nXwJr6yYSc4q(0yAu+E2%u>3;`0fw1NGXnl@Ve&X#;j;YWE}R?USp?%BFf% zKPO8%9$lE_uMOqvzXHo6VP#i8Dq=5Ke@1Ps`aR0--Dr5G@lW}MgbKoTzdz^+?B`=i7Q2bkRnrcn{+3y*um4+SWw(6lg!OU! z5BAA_^MC}0&HM>WoP=60&DT6XP(RXAGrV~0P;Zi*wai~I4#6Su*@7IHGi7T}EapM> zAdlG@{dh3?A8eQ42<-^L-IXUcs#+hzJ08vI=$Y!1?CBf*hs`QDM)iQ;!);2f62)Ip z`mO*Jf^BoiEmQISZJGamf`I2PE4X*74<=VFTq`Zg-NqdKP{x9v|Fylt16({If9NYk z!$o>1u8o5IjB+oTaeI3@E48+D=-5BU0rxe(+u?*rsN6YddZje*2)^6C`r={dOp3(T z|87laWfg!n2o*TUUIY$viPENn#oQxUsHw|G|BF-w&N;IVkQjxuGxhJkRj}V`8D4zP zlmsT!3 z-_QVcGb_;^|~%A9`eePSf6M;-)A>}`RM;uobBDW zl&$~gpR)=vF21f6)IjO(He8lIRlUD24gB?P^D^*6R&J72PhNch z`rm$k3B>l;I>!a*^~eMxlsJ@L)@m2 z|243Jr0I+I|Jx6=FTf=T6g#rb;{WM-qfiZ-e@KqIzbY4enf|@)K1}~>^AWPA{BY>JwHbJ`=2d{<{@x>;B!I z3#{~D^(krnzN(E3_8p3BWyn&%#?BW94@JkqV?#rw<9(IG5iQW8{<7~c*f=EDoWNY# zUoH1gK~pV$W0P=@wQmTh)Nn527UBT^-LF#KLs20@;Q9fXjLRIMDwQPD*L&! z?`qWRDKa)%Yo@T70~1+V^&8wmo1-R#=`s>4&QHaT<_Jrg_qSd90L` zuIwA^xS&aJOW>v^k^e#s|3rR{6(nx?TSJD--@7LB9xW+6Rbv&6AN6wbG_A zE=4tLX*rN$(|D0`$f(-pr~DJ>u|NHx^==6BLuHz}&&iiH9%o!r-WE-~q$audX3Mzv zS9@)gzM-UIdx|GG&Ko-rs@u$HShM?&cZ^;}W}USpop=u)>hK|oCRxAv3CSBDedwHz zCPxKnNFS!&G?x3jG^zr>-%Mt9s-8xnMQ`2!97BQO>y!+)>8*~+KdXY%Zgj~UHyFB< zmT|M}r($nR9vC&c(I&7X^#0BekyN>TgiimE71Q<^>z7So&nLlQKGa#i3WP3>*Z#U~G5RMF zd(m-9jUvL=J1yeZ)Nu!Mz`Jmzf{Z~R&3}Yz+Wk`SS6>iM6?F39^B4zZ%Gg*BI1?f!x&&~2~wj7xWhU!6Kk?J4^{fgTuHn#0RPwU%L1fGR6_rfPm*r9Lfd(qUAmpkawAr zr0E$Y;kxz8)7h$4)wV#^H}{A~inB)LKr@f|{0v44#MMvK5l%hqP0EVlYS*VR`jLq7 zi3cC1a?s=twNC~zY<+?+GY-BSYq?-pe)VbB3iWZ>c0{7>Z$6*K#oWb4`g`gobfKO( zXR(uYc53u5ksfI`@tUC*9Yv|bZJGU=`4N?uv^3rz12gTMeP0(Hv%j`md4-k)+UuFj zH#M)25cvmXP&?W7Aro+&1n?(NFJ9jn>vKGurFcKn@#vGOQuoD}_9YgJWp@^D;s|xG z@C3&1rQeb}v}7y8mMHV-yu~X3hRhCsujc+eZA@+F2->+~+)*8jy9B8f{bl`^eZTQx z$%7AXYSezMUM(MfWM$H9emJc1g&cN#5ax<;d+L2vvAbHzL6dxAPH(CJIWDa(>23WF zQueN&pv6`Hm{Ml_;bvw{R#w*1!azO_Tm{8A_^4N;k`DpfGWHki=Y7B|GO+btma!cu zJR-k)bQB!5y%DgT8(V#uI5AN$(BD6{u@UZcSb(X1=jz`4Aq&c1T!V4H#t!=`V_~Ja zj-`2IjtgoWSFZt-9fO#?g#Fd$`G>uSd}^}BwjYf49Jmt|hK7pLWpFzc?!{AHJ(>^s ztf$J{?nd({SPPU-C0y@r&g%IK-XMR4}f|ik#|Q}Nw`|#{g6qC zk}zVu(lRTS>8f=5w5dej&_#)!p*q%iz6H2`otyc8cslQRHuv|9w|Z1hinKknYDC9r ztCZ5(v*@%}VnB8etOQDVj3^Y`?e@9UR8{O1qy zc|Ol|-}iODucV}H6f&@6O7sI1e*MK^pTZ0?S8cEEA>X-==0#!_*u)O1bC^*~OR#Q3 zGrcmCXpZ}5$kiAMU~L_ix~Yg~mk2IxWqE$D+X-NKk%O-}Y8N60KA&`XiymzCmTxAe z?Sk(B&#%L#)75H+VnkT&Sj{c`kBE1J?fj)H#s3Rh@BA(BdSuw|@WdJ&(}q@>eA3Fj z6;Z;|WOSxW%npr4vtaG3QKmI?u`jvyVr7N2Qt@03tDxqn>cJ`lX!T%=ho`6K*52&w zY^?-23V2uD0V~&~2nDQ4Cbs}lDvjMGJxd2YcE{w!{OENfma{|M9f&;y$s%6kN-KI0 zo?TAoSaUHntDeo3CK{etp75ET>5Y)y&*wkXpmK1+ou3qYcJK7Cu7Sm%%~) zA{A4-#Kw_apyWKJeYqkcB4V2WfnU>J3Wg1xmd7ct|6-;XyY{9iq>z(JvV0Fq6*EOA zozolpg?4+Hui7m-X$Hp*3W=}flIfdYNxFLnBSi?pD&Lt@h%Rf-bZ&Fht-Jl?Ijre+ z1KZ4nOuKxpYS+62v!e1Je})~s?j;v)d^-%l%Y%9Kn}G-CUC`O-|4HfJ3h?K!k=gyr zBG5J?KJDJ!RML$MpsBU8;wfx2)lAT|x zzi84P_&XA3@+bDMo;H{5#s)azhumpILsMTgj%(}#W=Kc=hU+*XBkI2|}spOiZlIOKLIL4Q#&oK5weGr>DJ-vbyRO0%R53Al?mG{;3`U zE1Bvq)bP!kK7nTipyR;hpTue-C6X564f1949PL2XFq3j{R6MmY_{9blV^{}j;R|8@A zZBpf7Kr?Tv8RpB{B=*GSVF(4DjTw3ze&&?N@E~ZN{KG|gOd__>Pc0*bfv83blUPeg zH_N*j_@!Q|n~MnHI6d;eZ?SRp-$zLZ>;U|oXWqZk#|-CT%#4kVD__O5uyy>f=&~f0 zK1+CY$s#I?pbLsB^@=AN^Eeo8WL2LzzXE!~@>7cRHW7L>7L1kufk$MbmYR)^>DuM@ z(YxGzqrwL&sZypQ+Po!$amF3+HFey*Q0_OVa)rFD?TqgVa*~o~viGcUog__WQ%^AA zCX~;Zfn_bPaUJAKQ3yJA2OBh-4)2{g3U(zeX0mu?Ylj6!UJdGV#lV?R0pI=gKIS{r zbXQ+s;l_+0jTZF2cZ2+14cv&sLm;mt!WM(}j1kSZZNNuXE7YdLR2B_9Ho8j8dc^XW zwLng9ZsaGASh+AJ(aWgLnrH!ht?C!#kvrFWzS;f z&E0MU9T+lBx|Zs470Futf1UZS-r4U%TjJvi`w}(Sf~aWgh%6JbBhZruK6(YZIqvj; z4e{o{GNOBZU|G%^%+}2H8jMpCGCF%&po!8wD+U&Q)m8&~@- z?P7@rx zNQ0ohsDBqMC=SYgBo_nannTNtzLAT^+cZOv^1;`H@oagB(2ERzgL>!kV=stDF}Is4 z0Bh*qkyY#7Ta6%d{?qIOsO?ta_Vl9~StTXqUxwwkNTvyWV$P+7xpeQ*rckAmiafCZ zv~R!5?7bz&sMYwF4l@+ty4vjI*J;R?wVPAlr}}3fNLTg?2d&h76k-NFks}4d}cd!l>=s zP+-ey6X1F;!n#&zvlh7(DVuGUxpTo0`|H7E813|!9%8I5=E9`!{p@QX2ceFQUo}p? zUcrs2Kafbd-x|gEYAHD|*I}&Li1an12Xt zE{W*`o-P{DvMc+T?*3)0!T* z_kzg}bHyTLDXT_c{AQtRlDWLJ?=+O45>ctl88GT(B4-JdV6#Il79D;T zsc0~4q5CxN-l!F&{iHG0tLZq$^ve|%E-&q8-?B$7YS3nTH}`z*hR54b?|S|diT=X# zK`BF2J@>6-hTn}4{KsE|9xjCYH28nLqK{&~tIeOo%M}Uk5@IvISScpDn(8G3XF8V+ zl8H~`B7BU^S*bp)7Pi+%I87^NYgW@I^8|i^>YZaLvRb@fcx2ZS?(Li;hp{eSBtJ?r zt7(2M*F$s^QtLj8ch8TSR(+Yd7WI+wbqyAdahb}#>2e&~tEvVecN7Q~n9Mx6{1q63 z-9a6S3>DbVQjunn|ZKh|52-MjC=|ppM!JB!pYY9jmTLo99x=K1YV9 znLpPJTrPBAH+Lg2IKyhTwg?GYtSm$RV##^3**%#YZ&|NbZYy3L_uh995E zIXgN!PIixx-6flK>eX?G)wydJ?mU4`y8D6Yp5sfRwcI!I>W3`KKU#7lDm8>LE-xXD z5d+gV+Hzwy4wO5Mwc#0$;F*tX(Qn`WnVy>Zc1_)8-m$n2B*-0iiP|+uGqm>JP$;fQ zR8?Cax*a=p{6=Ps3s;F`GDZf&28eSjPK{Ed%T>zBiH7X5>8z-0Mk4JGjeT zu-x2u)&VK=bIWm;ExJcFyB1k_5)-Ai6|H3_?i#r>VXKt1{KTKB>)w!0Ke+Ow#h3h# z-wDq@fR%6N1N+)cR;GM=l1tkhrs=ce=m5{^xB5?^-S`5f=6-jNSw1H0u3&m$+ate& z7`3*g8`q0ke4S?^PMF(YJy8nOYzuU^?nT^dSBjs5T(aVMzpv%GP`5HeRnmXA!m{Z2 z7$@7Y9xmwcGBC#bnxB*4er!JX*cWHe^Vx^hq*wjbL;xpNeeTOSB(hLo)wJBI5{OZ| zJzB7pzS@g5(-5nx?g)o*lzL4ic}kOf!!4j8jcp$|ZhH9btT6XO6{HsxJA4j8;g?E7 zGJ)&iX(F4U%Y_neqvHY*F1*jE`Em(UjVihDH6Uqjh=Mh>BRj5YuN^y@y8iLFZ;V3hCIre zqH<1_dQuv3Gy&O*J@3@n%=N#n-a5%{ zl((ID7Wk2rnYNo^Mmk;>TK3~B!u~30Z@dv_>cw!Ma`0jP7v&aUzk)n@{Fq}J$*3P~ zK~MsIAkjqv4fTd)OZtD|-LB9dLc99A9Y*@7D5qeRnmhsH;#AVdRZ)?{yGIH=7w}qZ zjmKV-jMqdJ(-`oG2Wo1pHGYo#7jY0(;j9Z^)We*=+5ECH+E&kgmyOzWE{iKnjtFXQ z(FYBpfM`Lf=Y+94T{J&(`w5HHE3{y|-v$5yZbKye_Q!7mAq$`9%pmKluCW{;yw71C z-&$=fNH&Q*lQQ;pH>#268Jpc2jAYWC25EDGToKmKv+>ypLOwf8mP%tCjwo#&9jG0V zR8Do|NXb97n*`Wl@t=z6dhN``05CWAkNra0LUHrHcKU~msv4M_vM!T`6BrvB+b?b= z;<|OyGc!reyBCf+M??0kTmNEMS`uq&pJK(jr1<#6OwgQ+nGd3i=*2^Hh@j&5ZuQx? zZm;cNOCpgNFZjj9tvGEV7JJ~Q&pB+D)}GvW>M?F9L)|B z@JPu3m!1&K>`CsP(u%>0_*_!UxrnfRGNv&PzoCjOoUz01K)*s>~&T1SN?m-n{fhj+vvfU_|$B* zpzXk7`}dlG{7Nh=ajGQgR45rW9NM$3x*+#ZRaG&}1o4ECb3PD6oC^)cbufc=_$NQ9 z<5j1aRjT@(cH3p%KjNFq!Kjf{S3Nc6qODV~v?>j66EA++cbF#x$WI_W3#b~_+6s*G z8rfRe>P4UlM9rl0&`vqmF{y=oD8Y2BF~ zXfE2W3mqX4&^TGx`p)+#IZ+4qSG@d)YZAbze~fUid6uY|X=djhdV@68{Y^G)JmlH` zMpBJc|5iiN;ODc`B$DW0_ueYk`aoy+MLNt-@+0yW);H08A$ybRK9%rD&0R4_^0E4H zsDU{gPJ9#(X~U~X=X#z)XSUo0>ZeU0lb7-B+c)>8tqjkbxAGiEwzn!0r(@WTL57Xn zcvWfy58Rs5_eP%?G4c%hM;iR?9^3_k19KjA4XzAqidf}T454_fYIKBzX+ z=*iM|znf(_E-n^X$&{t#mqg7^r^dKdq&Feg`~5`9asfKmhK0~o;jU5E%F~$S;)8(? zv#y!x7*u{mYns3Jb&Q-Zt}to;20D zac1r2q&Wj$*{4uCr%yi#4K=b<$z%khi>LFcg$SBpUj`NBDj-esQRd<7uQW6R&Z5-t z?O#AF;AGxT@X_JH$j`jw(ltOnyS5Gw4=*bC4l94bgbp_SR`Weq@z4BObV99eZ(8`C z!ouc!$kx3K4sqYIt@PqU^FvW6+%3`=fQD96mQQ8Z$jT-RMT~2OltVoEOg1n$*w=d1 zh&m~GX}7f$3C6;H;j(wcDMkbOG$m5shMQB`?U`4*3+UoNxrct`wT0lXt@T^G9IB&e37+yLb!OMPvXSA>B{fYC% znPUlRE)^fKr<^%?-W#xAVCOLDZVT6f^qVdo#5&^n&xB_k+4`6-;E@){K_AoonZ zUGo~~(M(Y(+j)?yNWwCCDHKVXqNCIW!7(E1xgCz#q320>LH_<=UN?huRl>1>{egNO z_|4k`@i?|W>3R_f?j!Z~K)|Z674elIWYnXUqq2HQ9(S1eVThk2Sz12cv{u|b%Lqi>ZJi7!v_+dI$B)${X?+* zONv702phQ|g0_5t6El`n@byTV7l#ft9@n~AE`E7^sWsFy$ zEb<9vr&u9xV{Ryjy^u^T-g34?jy7Jj><)~9f%^g}xA67F3MEm=*or*VVlLj%`Y)BA z7z9;Htd|}4sCn(_ySVU8^)Bj8DPnM6?b`?Qtq%Ujt?sL;N70+QbtUCa@@f$A1x<+FJ!%BuRo6>$EkLD}u< zU6g%_GPTL+)`pUhrMPyN$pcZcKGV3)W!UpO;r?VkV!(4_&a6rNckHlx`f&J=>ygcC zo7>8@v%F)CcdmfCC3&!x4=!nogF1k@4+P|0o$LK=_sWw*zR0?O+33*i17Suwl8nGr zOz2UAyxbb}A9$3X?ZKYa#_ES<1zuWH;_a&M`l4lXL5#>Z7me?CUAuCAGK^!v^xDmv zqkNi5z5ATQTny#l)CZ?~Zm^PrQ^uD}+r+v;{|pRty;WM!u$Si@c-_hywe6@DgIMO@ zI4+Q#akzL#<;?pjm9F7nn@KT~KRx4VyYn@|H|{`2UVdqS4n}A1x;BLKq$Do5?=5;R z8bL9B)j-QiNr^#qW4*_yI5dd=aUj3su7CJ9FmbY+QkmaeIn^J4Bla(`N~MCU5T!yg zHYFP-8#@ArEc(zr{;b69YP}or%nCXmwbjLkUhw?lfBgdZ&3|1XX=7%;MbkQ!%YEea z);+2-((aesApu;kUViKq@rRg1CNIQ99@Jbq&+EX$Z(yJ?eiCV8=59T6oLgwi%TmcE z8E)xbH%+PBFUpQ55l1Y~h-Ty@ywbR2Z_(6po(_qS=czH6x&-cZP94R3vpl@HKHgF3 z&uElMZ@Gl&DL5A0a&GjnJEhbS*FpMDT3em!lH#d0RU%#h?r=Qys95dbrmEO`=qNWb zyx3c3p60oEFA>#jy8Jx_7p=@M2hO<3vo#(i+%Dy3Q3Nj@b9VD>BnW}30>qTT?oM@d zZuHCctqSIwQS8B>ps~k8JACN+F}h)kyQ-{3lqr5SaDI|y%VUj|Mg^hU@2eBYqIb36))||`53NTX32AOO) zjH;gj$~}NVYqi=sI2g4sn`ElXqRH(VcB>d;Y&A=gWjQ{`Uv`1FY7SbBIx@d|ChpVV zH}(d>a<#vvmivx>?j6T1o7dOy^~PIRzw9op{kFZg=t`88D=X|>3IcWjbFPE@ zvIWtY6JrQZ9L;C2$ltfF#AAr-bbE4~?GMm;iN%NVNf4oJD=NbDS=Ug7b)9Hnk<>a2 zUi9ide|$jq+iWgO)(4eRS&OoQQ?tpK)zUPR@9?GBOVC0iw0hll554)Fl1V9LS%_N7 zCO;S#X53*!t4Tg$C9+P^A0(AyWT3AEV-b=9;k!E6^W%i#mW&5UvWWQU2inJR({VU_ z`D;5Rhz;HV8k*yDt<3)Ols|ohhO?-qM0Y)@4>mOM+(}%YesjBkhSLqW;cjslVm$@6WQ!zdopeeTQ07M1Qw6JHOA`bS*YcFe&Oo4UUdN zVBLr3e`snJzZ>&es~jP#2fg-4So6qagMe<&dl zY!bbBGhi9WlS#(h{?%X;eC&6amNn-eGfay(j;c=HArtW#AV}kFuOb{%^gH=(spl>@ zWP5*L*Eb*5e4E2fTAyr97d~9(*rVW4yshGpD?6XSs54{6?&H%*;XHpEzL~$!y#0W4 zNYMTp!3|x_CI@oa2eKs4jNDK|%TK^>Mm5NNJQyE`M0{?1FoOyj!!B(p-XI`Bl_IgZ zeQ$`LI`nz_q$BxF!6#+~zXPpmUAz)!ggpvC=67s3>+otB%0Ec@AnjN)+Dg}El-aZVz^mwu)A-26QT;HO38Z#Y?ywt^vC7RmdcS`H>Fb`3A2H+(hk_q49F}*YDqbz$Cv?S3Mcb4h~HPn4Mx!UR`Es`$J}NTw;$Q zo{x#+^VORV%BRio=YgOd7r*oe5qcFn2zuaWv$>(cXHjIDysHlP0E}j-F((Q}%?-{|xYwobEJ8QQ$?!^lloo zo)=;#FS0-OYcQ64NM%$)C%?>?aYof$xzywyw%=MTwV!_mS~kiqEsa;$bMK*`-V@IB$zl6eEVBBV@eGFbe|z9EoFT8i>ssS;Cgr^KgD+|OXlgO$0fIA~l4Y4#-K*Gf0PXT>g(0te60%vuwri-j@>+kbhQvZ+^*1c(QZ7K!XjW>9pfw zKK+sxY!Yw{0(Cc<7xMg|?Zb(KE ztB;l^1rvEP{&%-F{>{{^+Mv=x=dZ`zRiL2tgCcuGgIV%p(|10KbG<$;jzm1+xwR?A&yPD*{hrB2YtFDDfE zwV{nhAN6zP5B7f}BC?WRHclkNiz`i!8M2mYLb^|Y51li7Mn#+ocJl1!YUjwKyGBbZ zm$EtjBwH7>+pHOZ4|MFxHfH2wQBV_gaU;oysRK+Y9@rHl`lXh0aHjLKT%@c=n7cI2BeRjOh|otA)H#8Y zV%fn{-b%W=aONZR^VXKUvi+VyLg{aK;~RagLeMr1ygp?ncI+i1y{yGCo73RM_T~PE zwW{4c@G8TIvm7UrCT^CmY&D^CjmU6js>Yp4acfd*riKjYjtXa2UFcd6Cp{J&7v;-K z(?x#8C`i}Jx|t76zx{f1{w2S^xr5i>`fZcpRl$jD%~P#P2T}0IUraJcUI`x65@K!+7ZnZlWmdM>))PzU^o5B=uGh_hm`;uA z)br-+5{9_+cf&?L8*e|B^HI@(jdC1Wd^*eSwq7Q%4yQa+9-c(B24SDhPLoOXYxk1J z3MlnmxN7MlKyV_zbJct0XmnA!ZA0-1uSg=sP8u&SMEp?$&$isH+k~oZB_lwmMSOA( zmL+3JH`hT{kiSKuZYd8H+)H3P6_o96;3~`uGL&!CJO_~;^dg^o*P{`wlKy}WqRb;hX1^pw!nZGhHG`H;>#tU1m3w&=7OD(h)ak@I1 zy!?bwimQ!RY^R@x-Wceu(E}gfSQS_w&Fa(CP!tZ~o^ynJ>EGJ5@V4xwBfD+T)u^RU zd}I1(?5hWG!cEx4KfR3@rIR_nPNAx!X(=c@=X7PhfI(67DLYsbYDosU861X$5?A?l z0}DKt{nF`vYD>{19JhYQAPy zkds7RqFJ)HE9;cx*7wN5W`7*k?MG~ZKKQd{KrMgLmv|~cEH123>UWL7-p#RCEWWGumnaq3I zogW2ud*-63Tr?3(fgq*6?Q{bo;8-+bq5k+q!kQRHk1*lq)+U>qUKwg#)^eU&+-X)R z3N3aDEWtxVwLV>m2wgp0U8$0poefecT^$VH8;jhK_&lG~qMKCXLJ1P*_S*l}8BGn> z){c?tEA&XSU~zH78anby1Z%fvY&nkxMWF&9hN|)%{@!cdjuC~Jm8EX2v!G7J6@OP- zWnQ4aUoz}2Gk>UCd9EiEy`$%?6!+;KLNXTV8H$KHIIYIX7+Qo;7!@vs=NZB<(Gw36 zzTS$=ofs2XskXg)3;CnH)&o=jn|uw<}oa&*BMqW_)ER$fKB4IrNX_!^cl={p1=W} z8nzu3HiWoL3$_~#HwO6wDP#K&IQK-#a(-$cJz9`M^ADRegh5Yc9yYrF-1FQxla9~d z3J4Z^tg=}jh^L7?oe)3AtJPl($2vx^@!VPNyib2L@_CTxq{K74lI#hoS@{`B{IS(6 z_}FVyRY!b{=iBz-R_xTb$K~4?a;}x<%Cz7O`)g-=^N1TCu>IDUDzAWswF`m zh!kce$0?wta~5Z#p4PnA&>SL$JxKO;p3VW^ zF0r4^*)|1=$?f+Utjl^}%Z-zi{h^LI6>N_YQ9m~X}E)Ymq-5D#)nBwZ+5zH+xo3~Y7N7}XS${A;O7lBQsE=8-Bh8#Sjfe4 zg!U%2VTrN+&5HAnQvUaB1{l5#`aE!MXrnhRxI|+q&KD@xT4^9Rw7yRJoT=JD+tHbD z>-zT33BTr|Rp3E9;X1$~PuTv!4OvCiv$VBo#uEKre#wX0rpoiG!b|kxqkaAH`ep>O zX#?&iCa>asIWmoB#%TUN)YBrsV{CD_$+=5A#`eeQar!p%fm!L^#WKH`_}0KdcLHL% zvoRD9J_P@!E!#3LUa^nR=wsy?4xoEC`K~z=&WXK+VhBrrXhIcSc2=G#tMTLnvCyuv zVJlT!+*~g5&yq?g1ZYDTlQ`0}p<8?Xc9b{HHVg>F*7p=1%rp3u|L8P*Q$XrgQD}dY z$fZKq%j$6{d6N)T_tz@{%NZ*}ZzmYc)}eY?Fflo_Fdo&S($@@CIT#P*^r_fcvKA~zarNIv3DZ=R{m9- zox;co{HTLpfvGg1jzaOPo6e!5yZVgcpYLAryFg1SBBQwWRX{2wY3NsfW8Cr`*|m9y zedwp`Je_h1U=7C1r@QP8HNqA|w!c8no!x?ZuOi|#%@7}GpES)bIR|o4j3;Sd! z7vx24>gQes{5?~lq40oCOM#oO{{}q|bk&V8k}d}Yi@YDecPgRuoP;d=-I@n8gH>%B zaV~}j&+kI?4|Mj*@xhG1^J+`cXIg%`x8a!)lW?L}-$1W#wyPqOy!#K!Kue<=fA0 zkgZ-4uVf@$uYg zlB0uPPmzO!a^0bm4Wlijzh12L6-l$ws6Ppd?{Kj~zkIkq@-0FkJ>x@FMAVku=_a7s z?nt11{_Mj!Gh|};QY$Gn86rMJMj|b5gsAziXbqc2q}7{N4$T?ZVqjPyO*rUp3MPrq zOw>XKHc_>%c+hi#=6&Jzk6(j2D`|^n*T#STz3F3iSGUo~M&Gx&vE-kc>T2GU+=_wW z;RnHMbfwC3W`c>!d((PDagx{l7l)%MAFDi6txwq%`??Ae=H7j;G`om_A5mc`rfw>+ z4{$I-t_Q-s*J>sXc|P1RruSmJ3F}_NWM$2^B&=&r2u!IFB_hONgZ)Inn#rJeEI<+S zUs2Oi%YIAlLz~^W?Te(krnBLJ`nrV1lJiikj9Z9{qLskyA)r@bW-d;A5}UsH%iGa^ zXO~f7@fOKK1d7V~3+usG=&!y=3BT3n(}O;ML=yKTypB zio9-yLmIi4+5b`w$n|(ZQdr-#1VP_ua84zMS6(w5(nF^5GJckoeRhPle?N4wX+(^p z7{Batrh;Q6aF1Ss*)bs(gQ2LEGyk+(*m8r(fiZ17pm8)8cQwg2wG}fofBb=~nlB~qIOjNlqU~c{Rjtv4I$rs+2 z7n3YrPR#QCCNsKQ*L$;D4O+M=n+?ZxTKaBAzU+KiQgu+y3sLPKrtr-KN7$_s@U3I` zz?_O0BBjHMi6I>&KVrkgnhIq{q3G>(W>X^xs+h)EFj@O|%P{)^~olil*EjFe9S~hT7`T*bv1nW}{cAj0-_dx^~6(q9#8C zLX^PL(NZ5M2k>Cpmi4!opYf)O>1j<*kygPsU@k5s%VgiGAylu}1RAaXdQyd7^B!jZ z`P2&^;83IwI(T@wC^q`tA^v3|;_B>qIm;4ct5P~Fy>Ajw91x^vg}cFC&(pX<4OYxI zNpdc(`%6rO^*XQ|2+6)dFps5GEfad45L9Ddnh{QMGx9*%uC@^8&@|(q8Gp>F!oXx- zx>N`ySS57TU#HSgpI)kZTRQbUgluP1?;iA2EV2dLK%?;SgC`592^cf#0&z0tGJcWp zfVUl}L|ca9BSR69-p{WsCSrnkFJtm>xoqZ`SDR%kZ^U1=F3CWG(9~*LfSI#$4U4`g z4JmdBKvm%%1Zxw71!Hqd%NUR<|JbZFhOL4{QPj#-J_{;S7VPhSA=}ZGL1=bCmr`Sc zoU)vcuQpa0B7KFvbLmG&tS=+_T$MRZhMx})k2tPWe%}pA zmvKL|5TAcw`8m+{w#tMqTnyvk{G<2u^+3t|{ScE;HluNnxbfwSh?LE4QWr^g65Fw+ zVGH5wA97N_oco11s1XEvi)>5rVm$>_Vxim(&Wok=_ZYp>q|6qnAg9Fu$;Y1R|rpfVh z2zj$lxsNpo#iNt)$8t!7br%~HcQEu$@xgjvq1}a6!4ZR)UPCb3?tItYSU39qkLwP&kGX}aB+p$QzK@H6&S|>+AJl z~sh9cVI98h<|1kGGFJb!-FQuf&6b&Lrr++ZFi@|m$Z8w~CWD0$2%m3+l^-Xg^m z4R|@qE~GB$yF0nJ`#KBw3=jkpf=5?oxEMl7?sU4}% zhOdY-`n+4<3zI?BnE8+={;CD~JZwp!d?tK>) z@YSzb?aOv@xLL2F-2Q&QCxwq>$NMg1t2QP4E?lAOgdM*PK4iU2)F(OsFpvgK(%*j}! zG>+wRE%4OyzyQsZLM{Gsdl&qL@OvF+I;-+P z$@F1#I|fl*d5&sZ+G0#LjPC_~77-+n2^6lsz)`+BpS3K4Mg2Eoj%FfJ)k}*t7zdfT z6K%{Aj1nPDDxpyJ1!F`~ugt^T*p+r13(NYWdChDCYnIuf7arc~xp-4YR!ZeBF(p>Y z;MWFdTU;mOk08F%zwkSmd~tLxmsHFn*aS^V-jU_rR#mJfFo(AIUQgWH(!nzLX-uuN zcfi@WK<3BEv%TJ-)I}*s1H=7^+~XwPuD$v{}x7mw*5CpnCgoj+h@H$6x*-P5j?gkDtzF6+TZG*A&27H)J5B@aQEQm zE&KqEI~OC99lDts3Q)o-^P(F8!)T>Y@~d-ny^(J;P1}nR7Xad|8gG(D{c#@f!|jZI zS65lGg|vEM3e*8dOlYgZPVO#aC8uqIB#w_4)RQ^oql?wWy zoKX~NB%{_XOb{ImT%Z)}^Oq^)y(q;znYA*0fgP6ZZ)KnA<8KNcznZ}8zV@t70or2X9-w=4Laux8FU zy0Z2qrP#Hk1xDv!#gsFD(=aDwOlcgf;&vmNwY8@5P*8HA^LAtD2QEr0pUFRb{my<~ zj6dVc2HPvtqL<@sE)3FHB0M368+mQ)5%$z)lv~hN{!Bj7hA#mOkNxz)EEm_mhQI6; z{4!Gv-Y5WK9op=pdmhchh#6UxKk8yOe^>TNSme;JUncTL7Pf*)4Z$`w{at?eU=TWU zT?X0wOczzC`laIeNVH?BY4~d8pB?wb*`WySq%Lk*NTyltT*>4(rD2?RUUzZ^vu4r;Lra0KSD=i(0?YsW`(k+59`=sA&_!~?oZc8MEZ$A z+KSCzLt$*@@WK+3^{Y^oNX87nBS$GA4m~PvsAo3+G5c0g^@7#A&_AtB-X8PptttW8)r4r%QV}u z+PqgnQemm2wBdJl1J!!%kzytDS<~5B55^55?#mqU++X0x7Jx{shtAVBTH~8Zq7)fZ zzyZ>~J-1u#TA>jilo?mdhG44mM;fS?y?aAFDHBl&5nU;bnx@kb=?z(8f_ts{t@etsgm=gttyaP#BQa0+6o|Xli=Wg|r zcOob%5nv}2$bEMy1mHu`!0@(rk>(Fd#`=q70}7HV|;#-!`P z5Qd+UVuQLERy_4K?#NIh0sc<=_}PdD%JP02bCm7AN~5G}V9VWLo|FBaWB7w-YJxQu z)^)F7NZ;bbLCd$8VT($eE?Ol`fyvC7PU*3WSYKaGPF2kI4P6Gsrriia?kKOWS#*7` zE1#OFpS;`xZ=k$sSyDMuq=5I{t$@xcmz7lf)58^^Ob9{XiC^(jiXFQwdHeytze;07 zQHe`xc83cW2Jb5*-(xWOM3l|k!EW3DEzM){XV_Kg){Vk*(>lq*+PT?Yr&b>X5^4!a z9pm2q->Igi&*UB451(-!mOyjq5)u;16`43)R~dh|=RRdO5|XxCqLpE!7GqBdWwLI) z!_w=zCNjBB08|FB2?#^~=yxGz1i4PrPcg&k7iImUmy=PbqQ<^Yi!iP=7u%67Zf^7f zyn0Z>Nc@CNd%)-JC!O|vEtN4$|F*Oz{7eI^b&c#WKF*|=LhSZHSf2LQ85d{KmcKZ; zk<8)o^yl21ob8e}D|PR}{9SqO4~!{GT}o;{X3<2uLmO3rh)`jcxY1NY}_FelCexvxEY0~n&Ow@3zwBp8&#uJI#B1_wUOyMo)5UBi?mi_bp=P<2xKIih-?iIBq`W+fr{)!85? z1$b^(yz5S`WP3bnJ%7kZhfy5(GikJwVa{FTq{RDcxg+L}s)CXr!#~26KTktz;SM4| zv88^UmB=OvGMWt#eOkN%9lDPbfktS(FtGQp75P{u zHTQtx+al&8bS2aj#w78gZ-cd!P4t?BXUb=CNvXJos+*@aIlET;@&%GsNM^G>c zR$(ub7S8gOm>>7Xhe!xJlo23^=9U;m8(_PnswS-d;Viu$KWApYNBF4B&98<|D*uzN z#%|-wq6Ey=?l{hv7n>VBiAj#YVDhi1f}SH_UatQ8-Z?h3KfwXlDqBphsSJ;4gUnSp zJ2b`r2!Rw0HN|#M;cSZLjsB9J|K1o}>){4Z8G*u?wz5_x<)0xtSB+xt?#p z&k-cV^})bSbQJ%_Lr(j`Cv6Wq+FO+`Mt50Sgz%t>nCuKXy=Lw9Y-DBDA5}ak0Y1KH z);`d|YX|AxkcEtZY=T2qP5LL0{t+`i8I19@A_^D$#$IIdo%tH2mE3u~75{H2e79Zj zH)Va6QfgVF6XM70>t51wu=o|$fFrUjfmhK%{q5&} z7hF-`CGFhP?And!3{v4*Lbr2ax<2L$u&V{SHxpUC`9SVsuSP`Kz~@#3Xlce3o!@c_ zq{K3P(utf2@+}HCd9r#eGPyV>+=&c2H50i?vk4kL#z{}+u`=^c$ShDH1lA4e|C7(7#ILw{D~SR5QpNO^ zXgUm$IGtG@O2?y2+eLxFe6(rHAZk{Sx?*6jCM2w~1Vt~D3CzMMrO+5Dn^6!gmDrX5 zvjMR_=V-ZGj(g92Z{_gYqv~R1<|?OhT153R1HAL5F?B)mC$HvT9XBhPvy(Yl41*=J zrqTG8O0%~~krIF%X&%|g%b1vz5$lS#V`xOYNN#mq8m>QkDC;`(Yp{Sa$EA(s)fSsw z^!2&A&qca8y^aRV6@%+K^1TlyR;+Do2!x7TILA=mAJYvF`L*^ekwxIw=^)UZNjT(9I0z7D&|T} z`nc^T#ucLFhLQMgJz{s$0>jU8&TKkV!K&Clm?tZrG;OV?o16Iun0b;-YMq__j(zlh zJq@#+V}b>$ z)Hu8A{CcJ}LvwqyC|?I+t6EbmswcTbsvk*}z_fo_dpW*{jJgP-x$k6dW7Be{#Z}R$ zhOl&i6@eRf!O?&4AZWErY!k_0fJc0$r|(9t_Y5C@g}drNqoY_&4#TBo3Nx!PEjCtK z5iMHMg!4!4CFXhS&;GQ52v?_S9)%NNH-=TLDzXbBM%9be{?X<775hgLE^oO#q&ju5 zC&7)IbE;!VkqX==_#ig<00S*{mYUFevkc6X+^NYmHi3=Ly)RRpWxp@GF*9vlrA7wP z5TDdKV5ILaF{xy2tA3I)#-A1$j*tr6BEB{-h5#$Szn{(@7fikC=&SmYvWGWSJjoMR za4GX#W$7K~0%c>xDzO0JBxECb7cLE%)%nv;>CE`H1 zG|VCkba7-b0nNo#;nE@{8RYD7=G!ll&^l6gS&fl6APTERKzOinDAGTrQ6{%KQjVnd z7J%}#tL)oPD&A-06V#__0#e#QCCFBv zU~BEC2+(OdJa8e7R84qq(mCU^*|2pu z@}|Tmcec=AuBFAFe2s#dx}vb~aq>53!5}?i1_}yL`KI^YO+AlFLb2T>SL|ZM(d41n zQHPls7IGwmQk%<7Oi9`|q%0au{=H%K`eNT^( zPtonH#j}Mjt#v>epX^{gRnI}Z^uxBy>B(+isC8JWPHo$Y-qUOh;%j^hL|uJ|Pl)f~ z;qFhDoFiey(9e^*@KuNU$u^Po%5*n&u zceGzJQ-5Qdg@|0d2uJl`BT+z3QJVz%eS7xl@!aWt2{bC5cbY{5agz7Zaw59#U0?$P zL(A+^;v~c7IE-=(Y7LnNiPTt2jRiyAvZRdg5tUW|F<``17}ZAk$U|T;!tbRgaVd%) z{=vA~&IdXo&u#-@54x_mZkEEhEtEn6eSo8pTB&gkQ-cLAC6s((DfED-32bMU>&YK3$ilpTG23UwO$z~pBZ11}U zn$BdcyI)Ug&~=Kn8@HSmGAjztAvM>HABFn{^-0Z&ohuRC{;)>@o>%=ONs=iNKIq9^ z#;@=ES>Et;`&Z~U9}?$4$@6uOlu#J zvhxf~@@g_-fPcCUrrd3!=kxpg1 z0OBuMpW9`ebmv|{gIi31Yzo&jnS10QvUFhzJsKRiNR1Ja8}3_x*e}xdF%YmRMh%sw zv3v$2wlmqP*!8CY#Us&*Iq8}-FiA(bR2b9%oNM9r>qNR4s-9xA-QX8nLpyqt&Smeew z2aBXBS3zY%gDGp50-{Khw>xZ_Hf^#y$fbZ}cB-w*IMS$cJLG4L&DdQm=AFO?l5w^1 zrRHVvfV%s(=x#{nvEI!C2n0A9gbGl|>n*~+&;J(Big%S2cYOTwtTP=Xsh+@e- zD(DrAZbTCSW6>~9(TMGw?+|0ZqtUwRb-pd6&k>QQ=^4AlZ;u3WnnmyYc4iN~UNpFx z7LF3$#`aMXMC2bJ--{5tO->)!hj*eIv1dgi7;6&W!7-o6#pzkrqspNvAOX$Fcy^Sy zcX0O2oXBBcil#M6T1FZo+-gj{YhMqZlSeyXk~uL*ou~P}hPlGZXS=dpoGjLbxZ}}~ z4D0HT{uLFw4%R&Re53p=*nRmaIJt#qvqW5rHlU#wSKaDf3Es0T33hT8c%vX1lltr* z<=HK(zh$eTKsA=zO#1+`_x{UKS zs}|f{1+r&RKd>Kd4U3W@w3G+6S(vznq1Nh#)xlvVZNX|8+1(#D=B~qEfboO&G=DG$ zKlg~w$CUI(u@EuW&oz@ZC_#5M0#U3v$1l7okq=H14L6%}TL%R!DK*^X*GZAox>ax) z@JA8Z-{`p}b#lph;7hLYKx@E%f%EpQMR-d+rn?G?Nc6m1iX|=4E*nf5 zOW2QE+B=_P`p&fxdBQ%81Siv~k~%&YE1CNqcrODZh)IWQRc&|2zfpS}X&=}IYdzbu zayB%OBbd#fIq+*3sqz4bhOD@@%-xHAOg*gbfVd21vwkwZkCWiZajm{>{K=Cu?pM2S z{FDM?c<>jXJDO>n9zLK$Y;O8)lC_u}4~}3s!!|T*)o+f|T_}#Qmst%|M||;<}wtH!)L3 z^|pMrJH>Z`?t~pD<$Qa|k{WHrK5pv*WYi6|x1-W=MMkY7#x=_*dKGT_0!i~LH}}U` z-~yXx+?997S?jZLRLQOUCFX8QzsZ)UNpJy3G>CfcJ zI~53NtLRoRWs%muy3+M6|J3?6ElD@nDtW$sa;~)tXiQiM)eSf%6$Kpin9fRra|f!H z%?hftP8~I4iYa4@f3Ja6i?o{bX5CqZ0+Tjo8Yt~1JrkrdpWgNsxi;rl=$O#TT5{}q z+9ONNO3euF81Pwc&<$e2Ij$eHCN4dh592hckuhR@mVwLLV0@h#bLGq})q`KI6vO8a z44ZtWffF@N+bpw1hN)-S57s@V=h^$kgV6{3OpyF_kIja|`5m$IJ@r5GGhwf9gLrIp z;nk)?Rc>uhQXxQc91(~&AR95nrB?Q;Oa1e!)6Hf|0y~R0E^m1xkh6WcR?%nTCXdd-6rsYlRE8G1;xQi1N~>-jzxr6k_cgT!9QGxm zwOoalLv<$kBB~cZ_8rfjE%G~nSeLQ|^8wRN_GOEH`pk>jWQ;r0z#p9w%it9kp@JZ+dk*KiCS@ zoHR_8GW@ds-0#U{{V9g#lAN{P@|%m2#hM1S!dRjs`D%u-coPE+7MqLn^H0P8#J~RS zhK&$S!^e#Fi70sQBWfH20BZy=V;sW0Kk(YQ^o;Fq)juv`bT!Q~(PIxx>j9N435`}Q zX960dfqi8!g?R$qI1Fm7E>~vH(Blj$*Nd}mM2o3=w2oBiy_EAy`7-c@d}*hT##@x< z1<0rXBFHCnFno&ap8GPlRrf^2X+NU6L{|>_>`pN8EA|@$ytj9Qf+45h@tk%KhKw!< z_yoB~SjCy^V_eFoan*5+Bf`b0MLfz|<~Li~C2AOqyr&t=ImSI{e8e?xpda8byjEmE zN@?)IJ?_u*nP{W1jUT0l<{B<{FHf4q!S1F&a4eKgj&rHAA z;e_Cc!<0t~OtcHJyZW1_+kF=;u2o^5zXHN17Zy~*_-b9l)*}pRUH#+t@$Z9tMDBod zkq4>UD!(jep~`6-*K2mgYa>#o-ynrC1JvTP#D_(R9WFY8c?#Q$c zja!I?)+2MYV%6SC0AiQ>+<|y|{bF%;cdU#^61K{Euc(d!ak`B+Xiwa0T{1z2vGR1c zBH|ZAaUl2#n`7FRG(apV(}HFXdBbF~$x%QOlR4|Yz!(A3s-&3Ak5#wR`_{lxcr2op z@EX;OZrBIiJlChA_J;4fyAAj|fC`o$+}q&&um=MF96YVGxm{lQU=n_XoN%8v?)49t z=>+HPd9cesa({jLs3_!mqKg`2{ji+rVtILf#;J8@g&O7P+2gNC&l>E|32yVSq#Cvz z>Lc@pT}!F@MistepH?-^O0;t5)6HWhg=4Y0FQOY432Rq}6hcRluQB;fs;k@2bF`pC zo5&syc<;lW*`4D#Vz|z9Fc;hB% zBGTKZr4kkdf3rW0nzX8zD%C=DTfX6G^jT+(iWf7lG!k;vZ3>>7amS{TOAhU?0dtt% z)wJBIN68kD20<&VAMMJlCceX*MP+M#HwUG#-+}$Ij4X&w=6eYcWs%hGkeukyG@)Nm4G5||FLp|$&yT{+kMY$aJUGR>hCAY8wc z*eZoOxl+pCxEpR6uQIRQCOs&U{e70#c%XGXc+-2+6EPoAUAS{iNl+F;7lYk?>mPSj z?Y~jQp#J^U%X@Y|89^;u_9-nYhRq#P_k13W9D?Dhv=S&loGL8c(OMKX#EQ@Coa?P# zlS@U~KZZ-_w$BRCJ*hlK-B`%Mwyjvebr@EN8!bu8soPcv90`lWJw{zw@8^1J6i8(T zU!j8w`=dtT+k;Z*6?C3TNN?@TcZgueb_rw|Q0DqLz_kLETO#q?Lyp3rqbJ1e{p6mq z?ePY-*~_zsy{{V#{b+y;Z1~%x;GV{_ovx!K0&hP?(vn1jnZz1Bp$hAF!zqnh9%X$_b!F_V#&l$E05=gZ0%{b3;NgCmQ337|59 zzgA*LiEjY4ZB%3C0o5~myV_lY0;}&4lgdYHDsDizh#sMk_qwrBMD!B33|dbt-urmr zb8Z34uyOGd4_bv))dCG{RH*{)vDNvc?fPX!Y5Ibb9zlZ)rqmo7ZD)Qg!cpxiYQt3Q z1s$rW(mTzNv5S@b;6^Wbd#VyT*$b#8cvhR}3hsFr7D$dFmy5tH8b&ksu_mnDbDFjU zTId6d1{1hq&S*g%qGKTJ0!XHRolf>#sKYVmB4uGs>tV$sIAnU&<>I%Klph7MNBy+^ z#5f8zn%~#kx{*CYgI&hc?@QZF6Trt+97(&ZogWJ~hi}|wi2%8Xl-xC?wm_5k7A<*L z^-biEr3TCK-?xxS_%=GiTeR7aQV!ul!0V}aU`ksmySA5q>!ng` z|A?{%+4eH^XCznD8JAFQ`f%%#h!Ybr;oLL4L$J;yknubyjf7)B(S7=sxO`i7^e_I4 zddCpS2a^QTue3Uv?LZqY*)w)4o_Ye)vWM4vZ!iPe7w~+ldbb&e-9zj9BVUzX;z6@- z;Ni+KQDH{v_k?bn?}1=aXyC+A&k}=Ez9&pz!Soi0O!RP`E5UaUmj$eVU`);m;1u;z z2d}aQZ&oVmmfj%2y>(nJ%9ORfw_-`bf1Bv+y-eo%*13kSV%IPqki5`6xV^_0GWh|> z>49&P+D`I|iWI1cX)eesH=xSjpk%QD2NNiX)jbRrUC1>gl}Q5^%hNQaATWwdpKaL9 zuU^p*e3Q2o8J7#mFfDiz-A4zv-4g*>sOgEr5AwYGrJR&81sHw&#iMagLTU7zlOsHN z71X&(jR6J;XFmUU`Ix7uYysCfz_BKk(w=mU2&;}6zF0aN@Y-_UjGe4JpS+x*9X7V` z;(?0+4nTp!1^{OzGnV4$soF5g`hM9uaMf7^De7UDsHHdf!WmRt6w^5Bb9^@HSudGX z^|EWIw!}SuHWofG!!W`Mb6;S9!rQyif-C~Lv+kzC1am81qnnim!E@ ztrO2tmzGve(5HTcy6oB&-df$SZ|7JeEjaj`ZQs6dG_y}4^->6*f?Hgxp4zI`BPTMr z6h5g$P`@IuIfB0@LSDeY$FCdmGFLCk#T#!dHnkgM=W1h|4e8QLRE&DI{h?p0*XkFh zuhTVI{rX7fz~$=G6r5TCpSPeMry#u(|ZA+@z&iT+WXw+?2PgR6gT zX?^GsrIkJ?N)bMC;qh6r>9F6Ir5eb*d>cBM;Ewn0MMTt0cCZhpM|vEL zFdXO0o{*^Z1ZaLsMXhPl?u6VTS+qL~admoejVjcd-zJRK${cdD6yGl*5loH zZ)ov&(vE!yjkjXnFjxDc!4ObV8X=#lcX#6dyy*9oh6b8njWfL3^-?h6Sk+=ecf)hD zfvgXY+S3SL^>R$KGNEf>DlasMEBd6iIN;Sm6_`AAv=}aXKqGK^Hr?R(&V+tT^Q&-$y7@ zhQ}z3dX#&vpBoxkpLW4OMgf3;`O4(n$O3V|UQv*tU_W}rT~-MjQe4bSxG7M z=FTYljlyOV;j=h4`#!O~d3oM6`@rGXJ-As8t~l@9)f;;{G)nUSNbS>$NF_}UkMgaCGL(u8Q%g{px=h48ga65XvI^bAOkyNF1omzaxbYV5naMO>_F7bWBwfsKO zh;%KZ!9&B5lBq1Y-Nt%5++B-l5SufF6d>5*y|b0nKGo{pfKAo%A4KeR%7Kd1$E}*= z_2>OYcT)Bad=}*2=~ipy?Hp%7im^VGMk9RI!aN@;X~d#BN#=PX0hyBpL_?Jn8iv>f zz)Sq>3gTq&GA;bK9;S8J+aGusgp@$HM$Mxd#}J3llX23#rktrn-yLV|cKd(Db+8hz zFN#So?z3jpzG%R?b}pj1zzi2!IL%!FQwRgTX`qaz*?;iGx%Lsc`|H{rUX*{N&&EoE zMR)y^%rt^WcZIF_l4`xbO;ufPow5Vc@?C(^n$x#v^hjdSIgL0)l>&x@5}MNEuyxJw z+jQl+q5(kc+@3`|g9Y@3;?32JMGl(~2yh6mPmLxeo(C)QueVF0;`oF{dmTNU^2{z5 zvtOPL#Cnw2{{e-dn!kb1u9EoHPWD%~XI%hLRQ%=Y<++fUIvt?vTk57q-{eqy=`im{ zHcfkoOJA3$U)t|X@08a+3B#eO z?~H0v?%KDHj!cJF?A+itZD3DnQ2-Te`6|~+YS!M)wu{!{-0>bb1Z8crXfK$qSBLut zphRdf;F`(?(2pH1_UhzadA`1)aA$49#Gfd`=KLMkqk}lTcz21ezT6o|*(`#sX|%Z1 zT_RT`y%SYLWwP^J)s8;hky1KPTI4zQv})|gGnWPG4<8$I8eak8Y@m+a&*7Zkre5@; zdJcQwHyf~@qcV$bxF_`cdz=OecsKFpd(EC59iJXxb_b*4dv|y0+2F15V|u2AG@a;7 zOp#Mxb@qFugI++oZEJgr_DitO*?3GmI1TODgFLSS15Vt?kBSQm@qMP^2R>9$YIF+V zJP&Mwr(xL4jJ4}=oqpq;74tff1&5qqheyIS1R$!beI&4Nc$#x~qSYv!j_uSoE%M== zl3icN8zH|jVPRHP_jRH*Hd0i-%ccg@J7iVEq0u|zcEqKzWo0j*FWl;-Qruz>4ii@} z^!rZTo2FDF0$uxkU=Jw4#)lqcBCSl~rC9;p@t#E#3!h51D&1fE>5oe-bEn61dTrAveh-qk{vK0!(ctFu7yw?j5?nR6jX`beH0@UD)cst){Q0D)oG{55cCzAk4AwcG_VVFspEgOQ(s+g|7_WxweVHPf9hGf;~@Zve)4cNT6t)q#ut>`{ln<$ zXf;+IMhpeakZe6?PS&(N2xmg&&qh@VNsz=HJHFGsDga8zW z$?W~^(f2P-7m(6!iY7lKd@(~dSXXZZG=17S5E};#-V9z1F-R&Hk1FL^V}y^u;uzu_ zpZpV1`_mCzc6xc8Qaxr))w*#kn#-Pm*MJ>dq-LQ&V5htQtx3}o>_W4;e`7*6t8K{> z*r^t%#8oqE$lBQ2a)?zz&2wJ>1x{}6#9K8&UnU9Q!5VvrQ=+vUo z4C7xo!fFmzBehRLR!mjt)_PxLK$~hSAV#7#=p8f0Ao|%v*aQK`*!uKD5pVC?Rh7SO zMn$u6<0gP`urta728m8&19ulndVb12ec%1OoAPdBrW2y2jBZ3d3FfDf@h%T zbx}S}#g=H;)J$^m!~#OzjlG#LgNC?WccvTix@2}1I=LTM1+z8F^Kcfg)2)u;7+vov z42>8*{&q#A0WRe~?6)!EoE>?93F!VyKDHPn;`SC0_T+k=o6f8PE{)5*7F%Soe2A;; z&cxccd!$0E5DO=62iO!w#nf4G%i@R66M$4lZ~+`y>aQ^i^pQO>bYcyFz}#t>eJW>L z4WJp;<%NS@+j3NuMC;tQT8760UdEM?XN`fZgD)hJR{dsoZ{Htn&dLjUnaaVqhC9rVV z#NORq@IuL`_-m{rc%B5l*TC{F7EK-+jMx(ktl2XJB9(_r*5^vsId0bh1W*%l1%lhH zL??HzU-Ko)X#x?KQ9Sq%r4LYlD9Nlw*M3(ahazLkH#h*r=jZhdXSIViT2LziWagG) z_SfJh<0JG)oNEV#}sCUHjwrq33@P1^uTKR!X6I&aM)Ajy z-8ABZkieQe%)ig17f&vPg!V-2h@boUkK344)g(^PY*w$Q+uEXAN0e+-3o=GymqX9@ zB7)AB9H&ba#{gT3sCe+N&3?^uZnpErHA7}Vh%i*k!_8M7c8{b*ODGM9;9yWG9(wo@Ou1H10UWd(n zS%{dj1%k6@`zBG-RH)E6zLt5=@C2Es#W7NS%pOvA4Fli>qTLSFobxq4`{a1VOwrC?yUge+l3t zyG2AAXgi9=N8mJ zutt-dE}2Z12AKyUD8Nep05C-;uM!?vl{4AEm1-G9`XpUVagIQu0FpJZ;w&?rIR%XG z4)hXLyj7dYLNSeg5jyZoQG?h}`IQTK-4P{rT4i|{@3i_GS{yhQ5j>ky%Kabm2Y)~i!EUw>mvf})4LzWzj zpCOM2hHcr9P(a3Idcn2ipn|&5@LpeRB?P8q3+VTY>h{?<(<1$>h-e~os{@VIDxb$p z`BwaVs_xDoKGF*$Ku*T#TY2zBf*56eDh=A;f$o0xS8oh)Fd*c;ZMxz)qS4p*VMD z(RO7fj56tZ6a72j!v2w-`!1LvuQif6EKM1r_yBBEkvOs9YI`{Hb=?8I0Rv!ZJvQ#F z)v(m9Z>d=xqe{gVL!&<()vL`uY6~b6=N0A1*=*~;^F|Bk`S=nhzrR&|pujG|sid*C zpXZs-)!BMgkeb0tMZMu{Us&osL!N`M`Kx#l0<=H5+U2nl3s(1o#F zeX(2IGv<$skk}v6l*hKeaD zrN@7olsWuLJnBE!zi86`6=M3ITY!K5pjLrjmLvLKepw(JApZRaI}O}x?_tkKbFG)@ z-;lze@3B7tb|PQys}SD*yz&YDcTfI5KfiYE{~-kSf8Qno*~A@BV*dI>{(R|QZgDTu ze-nDIrHK7&@c;9}|F1c-|NY?q2Momje%=2Y^6r1%^8ZbX6#cuJn9Ih+)6|;Ih3Sj; zv56_zDmUKgP0qmzJ}`PVEkkjk8e+t_jkPqndIH}^(*KV5?}=ZqW~pq|0JKAReWw*b zIlxj$Uc!{(+RP<&1foo?b$umoEXp%O$`gU+1kbWm6>Ms3HQ@|=grI%gkUz6Y!16!i zCFL=&i%0RjINee0+9<7D@5KNHgs`h!F2q_d5oTvzHQfpQmKZoDX26Rlu-o!ZNv%Sj zKVYOFmv3Cw#_KsUBy*liSQqi$48Sdvn81}aTez$L*q*;~wwr$Z(|9TJsZ%Cx#hQ#2 zFf&yWYZu`P9Bb-ti18|bsR{~&(*UBcbt8y~RFQg)3NzvQeAwY4B1=>02_9B9OE8t* z!2}yus1%nQvgkp2GpvYKOAZM7U|Y=u%t3aG07XMPJ4v@;@PQ+^FzL@sL0zF*{#?H5 zawMey5khZrZG0UC9F-^U8IjSkiL#dEx&~S)3J86kw2&e}u)J2G|3DYvM= z1zP>WvvL5zNv3;1Fw3(JP)Xxj4p;SR!Nq^lO9k~q`i5EyAMg`V7SRLArmWRJtSo^* za|v$=i{eHqpUJK5yc-d# z*Qg*^w031L1{j2$8&|gEwQEb_zyj&3WtbNH;pN1#cEv=c>o=%QTwCym)c2lxzjHxk zC-I3MIhGVOvGVj{V^&Ce^$d!pu|F%~mI*PR&IDi;tD&wyx2tVc-n&d9q`HV5F5K2A(ll1R#4Fy>jw=d)yu9xtYx!e@^c!6bNqD_xu_Lk|?dGki zf@hkF4+IWdn$pUwj!^ajIBE(u?`V->klF@8F!B&vTveBf2Cywr{6emF$VWFLKGf&$ z7E#mCxUiZvi0g&1i~ni+^Q%1iEqT@0V#nKTqQv#OMn=^O3kyfc8#*4EOvO|kY9#E; zT5Ttn6Q@Jij$ZDDM~zM|O=O@F(05;6tL4WIdQoC#A&pTnss{(= zRyG)kuww3|Dr~pLgE$;m&>M>AMkG26KHLdRRMU`u2r<1d4BPwmb=s%<@Ky@Us_j<6 zn^$jK<; z09Q}3_5iOO_~Qka(54?Cw$*lQF=?g8+l~#;x?5=9sK_pxP-#EtSQgUruOE zH~A!%F6y6GfTZ1ecrWYWE$#Gas~qCM_cX|Coiq0!r@nz@wR*P_3T%`Xru&P0z5Ota zHAOVj>#Y&aLAS_-CV&&y^7PTVwzc!?s50$t_LQa48N0S&?Wgw;3I3ckGBG10ycB;L z2@*a*dGWi29MXMwX9__mKNsM#Inz+%wrz5vqokSh_}$a>U%!99n?1*+18>;U$Ft8c zlCkFsUhx*Enm#mbeh#Ev9WGSo5*MeDiKLRlG9UXZ{`1A|AKOM;&rXu}kA8cIHr7vs zpR&Rd$XQXsoEjJyH}V;=AfHa;vMzHjBTu^x!Bj=0qSpL5nZkDaPpc_i+qRKhO)q<6 zc3a}8rX~&L4IL~NHUHss*}2|u&FNi>Q}^Fah8%?6)&W{8&67}t?(-A&Ie-aC^o}m6 zr3crTHhG-d2nk z>vsBveTE&N13SvgbcBL&;d<>hWE{JUszH^IhLKO= z#7<@E-Eb$=lg0cl)qND{KfPUH&DQY(7`OUT8DEG$I<~bmYhH+Sayo zGdx!@QnU3&ERhfGxQTc!!*7946@7^wo7*3D2wQHNUT?$1Qzp@r) z3SmW+%7)TzTaD%VmN|SXBiUZssxR#0=ExShOp26$$}wyAx?&_m8P5Mvd>2$2)uwDn zrQp=}PnUFWl+ktpBgzTSWwg7DbYr38Jqm*c;=>|`JK&2%$?PPcOZ8FZWc^pQRiB(CATIpkmv{WrIGxd@+o>!Wxyib39UsVZTFU{}Y?sB4HzFKCco_x7hs(m+`(1Lbc;#MG`wXreX zy@D7?d;a^6pmXin!vXaS5940x3b_1qUtJo=CViJEbrX|-h-ghw^W70%>{}knk7ioc z!7V5JMAvQ-$%RdHiivRN81Q>cE#KBj;kiBEHhjpalp<@*x|+_l`0KlusbY)(btJWL z+(xiAql=MAIG6GxeK&j5$%*Y#u{zDDuEQiQVb#dM3CFWv8oafKTTT;Q(Ogf1-FXEy zL$xBczK$0StmF}#FKTj^PT9Re4u;^pw|3aDpl=V2Vq;1B^zR!&{tn2gm;DbP5|?Vb zCrPHz65^%^xv<=(d#LoY$8?zX;@1(Qa@w0LRyERcgIY)WkRL$JT_WgS==?B!JT3(P zMK^1JHoxtZEaZwJYT3w@?z0_clykga@|l~|bhNJl~%hF9vH_Jx6 zbERZXcJ{Vl8Xxk@W%2X;XKtsAm%r107@dz8>3lZrzJ8O7Ju>yGuyBx~*BfVdA30{I zkOi)N_ZZSWlra*iZRaxIa&G43Q1NHc*Wyne*mzZcXu|H}Rzzx-QqsQm>Zb`Cq2(Sn(GAJM_2Sb1*s%ATb`orbA?j&~P(TD)IR!CtIBd`?wUCnR(SKyxG0CMiGv*>TC+AWz4*Sb=R zh72G1kWT4D4NZ`f79%wHo*;fv}@ zSyp)c*IUIa>X0;2-PYfAg)!CstF2~CxAR9IjNXNo2)ee3j8~%Dk~B7dnDbL?FRV)w z`S46WWX$Vy*wE%R6w(ZNG$a5ywz~y6NRIJo)MZ)JXYNg3`k}xWx?eFIBD0!$l$DVE zXvFa|7cpW~qsO}V|I!uwOcHElG_RiCy?K{s{+H9)(e|EfnT}uYxwIc$E+0TcR=)l$ z;K{8TW=>PE5K`o>4Ltm=(|z#@koh~vDyP!wzj%(^}#OArWBt=my?7GQ&@b*7bp7-*exBKZl_x){nK$m z+P^b*i9S5p_nHGkY={D~!+VM<&~y zURhRRT>}|)n~)5Pl>?&mfH9Nqd6RGP$n`DtJc!s&0A9YS5p!ez z%+Q914=gh|zqdTJ4WPU7e>JHmQ-hxEeEv@OjG_C~Ad)PH=b0!L$c&FpCjHpabg|kL z8E@!eiM~G85M!r3(QTocTwzN`>);-C`7)Y()!3| zQ2p?o3R$k?g82`#dX6uFopQ&$DT2|X4@>FqRvbV?C+up(=T4Np3lD*+>C_c@aeISd zq^b#9znYiawkPB(VqJphc-Jp#Wn`v`l9B1cb|MV85D&()7MEN`1<@{uyIDu~5 z|8D3n_Lq8T;$3!~tbufXPg0&UaYo(hUtnpq#?kql`$FNJlzp0Sx&GI?jZzzxU-A*z z=L==OiRFT$_4i&$7@Z4|cgx5&d%ei zem$mkaN>dCrsG`1R9r&z9_0lyiuM^2x1s3f?v8I|2ofU!SXH` z-w>Qo}q<~uLW!0j>Wkn04Gh_Y3UN(=8+Xpn8(+c~$8 zDn;Jan;`o_CCPKIr&^)WRY7EVe?SDv15d=}^q>9mCpfW<6VRh8n#8Z$#EMa5KIs$W zkSu}epLJK5!zMx$4t0O}<2D1CD12AqZYR_E*C{c4T*qg0+OOWmT;nlCt$!>B-C^Qip#St?{znKg zG2`<$go=Ju##wB){R_pF?%s+KjZ9Zw(h@{J7fXl|c|kUf1r8Xw6hF?t2)0iu;A#g5 zQHIz!a^cT|0#rVg)R1ob1Q3>ByV1r@F}v(+Y&lNlK>!s*l^Cm))T!Um*6wA#;pF|c z2TOIAQ-IM_;@AFq^(+GphZ?D);%EOfeG;Stet>*T6ht;44zCLT(cbNKsc(z(FG^vx zHE&vk@9EZ>t9VF-DRmpaURKemAT;I5my7RAnc?WZK6-QU{^4Z_rHB@V@~k!08$uIy zSQTbcjkT1GFFRan%Z)j*F9zbL5r|nJXVqIW?pCtkSwXlRD(TnlDV`j6Pt2#S{w}1( zD|ci-$#WTX|J=ocFy~q{jD)bZ)u$HmVqR^ZBJTC6|9gnR2RaJUP1p7^Xj769hmqh$ z>@BE6@{^kIju45ymzLZ&%3|a;=xYoJZE~j^F9eoUTyGfN!rt@QmX&AuxN&PDrqqg@ zXob*=;pl?RpsTxgqF3arFu@alARf-}@D9cgd$vLQ?R(-n#CWShYL&^FV@&XelD_yj z9}z_E*AM+42z)Nm(ieBVl~RRb)fVW=!4x8N%>-kr0eVDQ2+kI^QO8K;hng2+_r6!o zI8FaDFCD89{bKv&LG|m-c7&WYla|84!npp^hqk{80LMHqr|WKX6^io zeEej{%;7z?3;H`xq@?<4mw8g4mN+00?0`<2#b;=c-6+P^-DY6`Q+ z_qHiEL!@!A&xF}E9k_aU44H8~66P7I%i3!YjXsRL4&>8t@t?X{O z?^^+=f&>T^bHx>6mW5}WX-3lH-=GnDRynDpDDv67)v$=2h_5ef6^AyLLMtiY?_-xYEyJPZ^+2u5&Jc+{*Co0N<-UIFaAKAX- z=l&K%E5S4U&!fNP=}f#%vhr zu6MlB@YtA%<+e{!{>Xfi!~E|;a`>B|%^NgB8?>~X7aPmD798=cmI{&Hr)N61KGu$m z$T+Mn?(gl^?!u)j5Zf?K$t!DQ9jCvv%)t=oEYeKPa zsZaiFG8PZWM4mtK-Flpa9n_)F67yi zL`V3R6C~c_9R9Dpz z9d67-UT%Fj+jPJl0Lt=Uh?#5Ya{p-Kw1Cxv zT)f%#*~{t6=*jmUW_%YTO&8S;8w*-lHro)ISJv-sXG3ua;!GN9W|LAb4Jv@SSn^r#3H`okt}c5>*zxcc-0!2Ul~iua4Y8O}PY1&}_liTPDIby@7wUAWpfE8F4rWa$I@Y zEhk8C->*H{Q!~m{hy%@WD6Zk+B2(Jepq5eNW==s13ur&Z)47ZQ zmZUCy?A{ez!$&&|jL)P$yxF6B(EYq;@uDbMm z7EGO@o;rR2NsZlrox7kj{v=eztCBMRwV>JyL#D8kSKeYJ4|w=?t-p+BM=ZvdYUy_I z1e4sWN|}Q5T<8dV}ONa`gAuniq#v!|!9WLsww~P9$j4w4s|?*X5kTMy5BD*LR;; zTt=*shxE>-p6`x)Wz1k8393Wgg9ZHDden9DlTSRM3PuMt;_cU&g@D&BK-kH^12Rd< z=-S{B-ayRPmeVVH8LO3ExB<8yq|grd5v^xwT$hg5qU2$GB^RG(-xrp8D9?cEaL|!Q z4rQ0lHps_%KGd<+99Y4U`N7md3zzu|dEr_JJ86_%({s}NMOiIv{mo*}Q@t#3<>q9k zm42-=R~!vn+Z!&kx6ZgL=DP=7*@M+ zBE%KaVfR5V{DRJaarHr^?l)yiHkJjoHzV>6j(2drnQ3&TYQvnS97XgwHxfltfqmra zsQy2vfdsi9(+xZKBFWpa_$)CI68B9PYH$1)el7XYKh2SzfGM)&aWF)-`^gyh4d+`M z^uwyUGLwzVO$t&=X(Dn zX`Ca*UkFh+%_PIY@bY=HJ2TmO2C-D@D*;@>p^b2JT=^35?~Y3(?3YQt9nc&vl|>C zR6xT^%EXFE66zPHU=81$#x3VL6ePt2V$}F^vrX0rA`{OV*80Ye*X=$eclvfLMexta zZc>h>rAS%dg!rDw-F>2Z)8$=>Birb`9!$h2PG?s_?lhqYd!FxKlffRURmEEH_N!x4XNQN(^p$8-rKa5HifPEk?FjBk{U^^Ry`IjcF19m`ik$1Y0(M0uCT0791)c7GqAFw9#C>>39pT*!pU?66eL0 zH6_nz68fktI;LeI_T1+I_}F1~&y2n8Fl+9|C`Tly^*KJ^&VSaZ$+(EDER=OqS9g%8d6&4?uz1EE9dqzsam^5sFWgsP&cXl!9#HTxKJnTQv8| z>j8fkZ7aq8^~tjr!}t;}UYtiD?K`L2U=*r`@CDr+CZ~>My4Wo(?s@5PrvxT99w!NZ zS1y|U_HjfvprefDf@!d<2FTn!bIIiW!rG{w2LZ$jlwG{K&H7VqPdtN1KffAztY_jh zFf|O$$*O^!ZZ_yF8m<6xs z$6p6AJASyX4c?gv9;eDR#O|pBfuJTMsuto)8x|p(8j_5%eFN|A-GD_DEdFm+%oH^T zrSrh3G;>J4#)kUh9F2^JJQ)lU5{kpYR%WdVw?>D7b#wbZp~?BbqGQXH5hwh?pdN5@ z1_wCO*N>cFReTExwN`W+aL4`ze13BCciCmD_wx(3jj|_o{RHST*c1#0`(>P!qRE6G z@sZ(A*h0idB6zXkj6apSd2q2y2>1X63L9^{Mail;>hVCr3DKG18?1xOQRM}6J#r&M z#i4_SQ0L>4@eqMzI%Ox-?5PC8-}oxCJm;u$4#kW88_t#eyFKmh#0)*1fe{qM0hV8EArnF6VT$g6X4v)r2J@AsfK^ z;mqOOB}fY7+bFgCrqbmh9bxWtL`hbvUa}j*)BF`HpqOP+JP)HeMxr6MGkCBk;DLt( z&{mYwh<7hR4W#ibiNmQqu`yDF{O|>~gzLwVn@}=uj;@ckNeDe6P9C_@LM#=A(oQf5 zbL^c8RP5E}F_F<%ysDLqAaLII;VG*>D+Gxc!g2ZuN{_q|_LK<fd15J@lpWW*Cpv&fU4r+*LK*MMBSa9-^nqHkfrZnvs#}4)(B|Mqc z5&x3gJyh&DHXB)S+u!Y>kSNdBAv7l$t!eJ>blF}waHE-O0;e(K^kE3uP|j#NUs}Vw zES2M7s#2HWhx7dMU4u~%Dr`C?ADf2*axAKqZ<@r}xdbteC6S>U1Rf{t&Xe4G1hygD#}r7NTFY z*{I{uFtMjzd+H&jpvfA+JIv<^hfgSX=RH-6xRx@S3@t`bM;X}M-{VLqFq05xI#KDb zj*NLXUEYyi&aiso(EGhiL%BPRzhCcIugcrPt0+fW|NGWJpoul#C0Cjfj}B$x=hX7e zCc(8B!L<(pYJg5qFHFLdg?j(Vb-VdgIu0S6 zvP)&>?`my2vOBc&4Cz;1=)57|v(2N(P5?YBabl%8KSk<7gmr~v|(*Q~T_!c9@D1UZMF}}|i8;twH z3LFsn5fWLz0}>8RrIH%_@bz8koz{kvYwgFeGcmMJw1F2II9-IF(o=Qw4kFGg;@DZ9 zuEysWc`dDh6?(pe$_X)4QYjwxQ*GTrQ*r9>Uc~I~CjrptfzcZWx!!9EPMpB9oX>y) z)R;{=Lj4ZN1uu97ads4B#UCY;0!l4w$zfz(F#t7|T@zMT0kn*YFUmH!ZhtbPAEt3} z$l-Fvi3>%sGBNB-=12qzxU871hnyH2oW4}!M8`J#ZiE%A+ z-_AGKd&}sq-^r&+)U%ISla1wTlao2AV)S(4%D}hmc9`L~ai0B{jUm#WQB?Rbj-=zS zqA|U`BM$Es-zeWI49PCfkTeblftDSrl>kf`G_a+&fV%?f#foRWcx$mqw!iK(Cm0Jk zwiGHK8}o+gV;e@rAfScj_@Aw#T8=RyZ{>-+=eYIf4d+u_W$cKiJ5xwMWM)!2D-8>i9mk5tet2be}wHY~)0yn4- z8BI#J+|dC+a8k;ivV~wxX&`B8UV(NfA1pSf;*7W}sa$%t6JOk2ReCc^7!?|0(x&zc ztJKF@bfXjNgq6rs=ZlbFV4shzqo&|xd@c9a>}eu)PeyAw)=3|b2$OEpF)#scsXN*9 zD#17U#EzHdM)bbu+so_>LIuV8LHDIkZgg&ec>)yMOG7sVwocmxuvk-%q(=Xv6Q})FdRforVHQp(sxXd50zRAbxR3-d%XN77CV zWibwpPIv95IKOk=Oh4ylgw>Sqt`=R)mug=50aI5{QN#;Fn|}HIt(H^HUdFGL;M3kC zbINSO`sMS}5ght0oqlM|I>JrXZvqJl)BB~4BGoIo!xbx;gzvezsM%_pR>9hq#RSvJ z!RN1!H7@k2HS9F`JdqAhHajZULF_g~<~dvwf8h;``@X@E7nG*{JB z2m5EGKW1n`FuNe3tW{|v?seCc&YgQ#mz%ALG+d;C{?**B#jnb2IiyN=*{I7K#<)ULT8lU+7URuU`J%51ioP*GbsloSYaoaJ%Q=D!kHNcJnU%a97Kr znaygLjDmvyGtp4~cb++eaID)B15>U)f1(Z{6tbeW0;NkULq4#DZil)iVdx`W4V{#z z?G81ta3H;E_ObYkr@w`IWTc-#vomv8AezjiET=j5Y#c?;#xqvp6%v%u;bPYtVZ<*> zX)9vU^_Mc}$kA}ASg|tCZUxr^cpv_es%ls%+W`gII_N^~SA^>InH$U&)sLv^H8~sr zp&qPK);qAUDc-B>jc>^@c^LX+TAs^Wtg27KJ?^1{aZaTr7IF43 z53UtcVO=;cAI~zn~G-{*PjI6a6dA2=6e+w<4^H$fd%=ZUu>sO52Km- zo2%~??;6&?DNGwb8=>5WkaN<̨kR)K|xG0SKNa3RBBks3bR%NGgm%E%<&{yTH( zLQx>@Vb~gJr<&}7T3l|wty1bR8~dGh&}Fncb*@9UgIx3sG%OI--Um;CCeFY9ZQbb?BC z6F5)mtqhcm_vi|FSa)KO#6*z1^K?O{Hmn*HC#Q-3{qIm|v_mN3#eZ^^qN`a4yOFw& z>@{j(q1E>$+;d`M{;=bJ)eS=9JjhRngj)tzMF#DSG;ANh51*fwLboX74QE;)s+iEulP7$iTB^Kdg?+1qQoMj@T#17BErKUFc4c5ByW<0kW3)@* z4~3@tj6r3`z7zf)#%aIUNk_mY2nRdw`EsM939$=Wc=1apW9~d4>8)d?RxvXE@Xc1# ztmsxI6LiO=%GfSa(`IxBTvYhpqRY zg$-DYsSHj&E_O%pa(9r;J&@@qI_PqdoIeH;8e^Pq|2nmJ$Wu*hG7zGO(sx;liY4ue zuFBvZ*z8WgHtJ(D-~3c1OWGfZ*0>2kS=3_j`)ARMOu(`MW_c`(wq*>nk5-UMO|_&E7_ zajiY>Pi|?ut>!3)evM{1U`US&JMA2$+3TR!Q}K_*c(s;^4s6 zF<;l-4B!v@fffhsRV9D$S;iyZ&dfc$UrQm3m0dr|2}__*sU4xi*Mr(f;8NJP8QCbCn=h6lZ=%3QHe(~yQI~l_Jxi0m5M+WI zn+B1k)FIP}`O1`Tj&9LZ4>1@8@{oH&EQJr5-pc+;^b9{^6)lujf#mX36L(X*a6IVl zad>Gd&tQBYD^9mrxNpQAycCg%G5dGyt~KfKl{f7C8XZaVYl4Um!kbch;2|8!u4rhw93dg=mq zzS5159VIMonwP_qV-PMD)|4q!03H5SIEK}_N4gek7PGEV*V72g^3M`(d~@sScu+!| zX`r+i33h$ClYw3d$v=mO*6oG>8rjnG()@THDUIDo`U-pR!42E-28o}On4;};V)*?} zg9w>^`^-|St@8iD=5d_7$3BC0wJilce&o@?9~~ohfAU+q)CjSby4IO0Eq^3zLaAnd zch55$6?RMb%lEH_H3t*8S<)J!Q1} z8driRM|zB9jmSZnN}WJ<;(|nfHeR~sBEC|xiR=3sj81M$X@Kt6?1j34Tth*NQb8Sp zE54gEDJM?sWix?yeSlBV+oiHP;~Y~=p$ zK1`Wxk$_%>cdMqP)@{bK9~Pz{f4!($zAM(%9afHc$9RUktGiqwPJO0Rd3bbb2mifw z_(z)iv#{i{KJ4cfG|Q;w+pR+y6SRQdgz}{95uOvZ+qRF*do|m1IosSQxO-COR5Pg8 zbnCq^z1c~|Qgf&b7;Nr*ukAvLuHVFta47vD;@Fw_ljAa~$;PeIZg-#hzw8d|rN#dP zV4N_&II%wveA&?IzN7;w_=f`hr*k5?6CegW@M&R#CMJPM(HOedIYdG7DL+~i)@v+2 zf?uQ5fQt~%JRS8=j?jT9>+PMQK*&VY53{xPMz#?7(qRn=(_PIhB@-4bn9!Mr3?aFl zBbz#hE^BnUE33v2!=h4I?oT~JZIwKERIy;H9`fi78vKqA_!OtH{Z@*dj19OU4xypw zN!V-C5$LQf90tQ0LfbYyJgk@?g^^EHpGRsTia5CJud|md8O`SlewyixFhm=$z5uz7 zNIXe3mdfiRiE3+xs3tqXqrA#|b?@)#PrP)bJD_5|>Re17M+c=H``608I4f0oVy7-zVz^`tV!`#5b)X`%x#TUU>$U0#XHZjtVbyRP|n?MENKi-Y$@j% zrQigesczg+kz&%%i!bF{YTzCeL;D)wUIV`hU!@^6(Cmd2cZwEJ%dP>QcbkbO$p~(v zxYJOkb?3I|Me@k3ynV;Py@!7ogHH4n#4meGL$H)(#~W-hb6+SOLs-g7V@H`V+FG2r zZStP1K7+#oQNq+yPm%LP3&E)+YO>Hv_<(PfCd)F!E~*+E zJG9SbD$@GL3H(NKFRZ?7j>tOMz!eN;XZG7ixY4rPRvvv-n0-%#H%JQz8NaO3wQ%ep zVe!LN0y3{c135@eR#ke7_Ge}W+-zz^S8p=G8Z?tipT|W2v z@3-|dCDLhRtl|9Ku-0(y3w-(HzliApmy|;iOQ)}7;#yc8u(m^2u-3Lqqy^PF$H?cQ zXubgM&a;nS9ZbnSXC6(AYX{A`4JPEvqt$x^?gjcr+^4N^Uz&2u>}e`$tp-W{Flw|T zOs+X&$^cv#si1w0jvlV%&{q3lcoJQs<0lDKgzEWU8`{`#<;IeuY9o%zG6qCBUD?~y zVJ|kDmaeUq@bEwVmh`+l8$g*ErqYU>Txo?3W@T!PsMJ*TV+XPlen!tGyMITac_xSN zm3drZ!St?DZ*{pxB3p3W^Msg#PkYU}IH!ngPa--9po)K6_Xv)NP6s!O$7Ru3b?z2b7WWeNV8tX4lrjWm zi8QT&q(HcItuhjM1+>=pfBNulLnAuw4rteuHV*UezXzv>YN77|QYb_sLkGYNnX2~e zG?<%A7BfQ6Z`-Hwn3ZvJIgZrm66mNUsY1Ka$t4oVJMu7pzh+I#M)MpFnt`aIJBty_)<%ka~#|z(SLO1ZuDvBQ(Bm~ig10Mh2 zJtl?rU%c*d#_q-{J8$o-tpQ66YiYt=anWiVqtt?Km)HLq^Vmhk?{uP#;lyzyRq+%E z{W#fv(sznQr({&(kxIz%^@J=nijfTOXcQd#XF>Z|f`nFVD$Ms| zLKRgz_G zd{=wU|BRLKQr+~G`|DW|tu_TA%gPP|OUx60pWMHj9gr{g70>QrC+-bdKYh~oAk|9qS`SL{~ zf+s(+`v(7r4<95TtZ}mM^EIt@Yl3yP!S1VO>54m&1Ch;bDvwag;0~Hmv0N||*Ne1? z=>i#f-t|l7hy*e--Ow!sj_NxabRST{aZT`YykJpm}6iqa?CO&Ly0rzsK?e~hNCbhPGe4EU3YiX_( zd}9HwG~jH$`J9@#+U&QFLCYyB@2o7BNS!FS&h|ip;t`Xl&rAL9z#sE#qpFr3g(;sB zSk#Sdv>siAmvU4om__Wpw5?vW$Vr{YZy+5k!s9Z%6A(krh?jZ9Qfi#cUAtY||rgjIgfbvfYAdmW?63DRS=VAa2f zu=YbMwyyh~(d$1~-SNwUb>#xSH+LKg_%hudein7`O^Q#Sa5ZLcbWIaUW8Up6nKWWv zwtK0F(k?hioZZ&NBjQ|VhTt_8K zU}7S>-i>&HLfJwlUer3YWnS_Lb^}X6Cu_MCSJUrRNeA0?Vf&FLws43 zb!nq(=ljIv_P=xCj>La=Ud|!EF|#MTZ=)Ne^FCtY#y&I1!3q_;sNEzzjw%tOL2FsUOT z2ag`tf_X$vJ&BIXB3`5-2NR6lIPyx`eiQ2#0Tq*3SMF?Jn3L2^T3e)_t7A9->ziG6 zk1v4dDFYhXb(H9K+xLvqEL5qluI?=wP@q(O`Ub88$*|{z*u12t#E%-F5A$Mq!jZv? z)=;e2UM|!l1Z>fCVI~b<^C?uT#PkHvaa{opCeF?GPiJ*}N7%6~o}1G`r!?e{`nX2u z0R;Gaf#3r=SJhi}*?xkOI19(=1z&x>6XMMBG4N}lA$bovH%YE$tBDyCUqkjw^M?n} z*+tR?Pl)GI{7u|Xrr^YWdP1sc!RlweZSvuH#b9r%l?E8yoz-~3T5B$w=vD#Htlchv zSM`jzJa3#ZZxNVQxJt=o%UvRYeTit&K8kK^gjlIOq2qjTRjm`HLB z6;TUo$vhy5TTmDjtGEsO+lZL^>J@Q>B;uyd8n~9c7zurF*gF-j1 z-RQc+EBytDwQSB{K!z~oA;IB5ePbPz)qyd&rr*RdP87Q`joA>_pp8`Q$Rov!rAUJ_ z4GYqSD;alN4%u@g?ThIWzk0W?)qXEM(k|TD=*=J-mgHo?Lwfxpnx97^UDx7!t>qd$4=%pUMqrKEJ;PmP-P!tF*R70e0n~M) z{*o_F{-Rv zj6ovYW2-6J&MC>pS6tq~04nUlQR3#v2xEt7mS=~~{+H++gZ;l89nzPQa$ zMCcWmb%gGtS6$QPN)SF}3Yr&#N~KGyAH{B9YDnfy*fKUB%*31Q2B0gZ5ylboqc6h) zo+?K6A4YkwjuyB&aF%j>IcOiQ7Y_mcW&AnhFe{V5LYc;fcR&OIypa z)$J)+2uXq`Cj(bwxCGt+k?QMz_O!n#KYQbOY96DzDf;zs;INIo^E!s(>Ayu<8PrRzWWD7m``y8AL}3&3a(DT)XZ zs*5H^-MN?D%syTlJt_+ZR9hl`vf!nLjdW^b?)aN{MIS&yznqTw zLmylKM{5XFak25lXy8=z_H*70SnR|wbI+pemEx_NiUyA4ygyR4R~3OFKVwfgmu zRIVNOQhZZ>R6sEH;+|l%785b`j;mLuhN7T6s$BeG<_&eX_kB+Y zmN&Cet%=ys_LbHrPfIS0gQKwCU9;<~VV~gD1 z&_z}LEd+JU3;HkUYZ7oVm5(AWx6OG32};vAN_vlQ25U{awC^53>kZJU>Trzsy_2jRFVBbEQl_RjYW)AFuWiB zBy;-LkW&m^-|K+!&$j5rOseLz31O~LZ6zK6ncOr3*@^dIv~AzaT7^fYd6mhDjS@dvjHs6JW5e)uLen|eQlHhfbxMHU_xM*jI z{S{EL;+(sUWHc|CUiS(sG*14CG{Lp0{z6!yFk1vI^;B{o{#kyP*JM)bV_^AVVl}Ga zuhJ9foZFm^fw%xzZ22X&D!(_FR1sT~X?i3v+Z5ANB|SB1$)~EBYR<0`{8l!NCh4AO zVj1uEn2u95aRF9=N50hJCkIvV-hSaNKDNRaZ^d2UUQ+Y&*-x)V45!{xCW+k~k@ZSC z58}P|gG=(au9G7^Hl6DUbcR=6>kFv3l`o;FpA1xC7~g{;_I#T^z{nqMntu@;v3) zasT$u+a1KDXynd>UASmzmsPp$!Zz`|-}G>srYgd;ghpC>;L~2rwt)u1149L>1P1Gt zsbs~oEXBVQJUAT2h-)U}-Z?v3Ip)i=d&bu1`h@{*TJ@{j|4p{SX5GVz=Jz4&s+rfb zO$NtBFFU>F8rbisHI&%M-H@BVvbNNkPqw^NoFwkQH34@|n@%-Wxaa>IF2wlf`Fpo7 zNEr>r2z=yQNafIBgyzo8oVc1f;GKlC(8`7Hu48M#5zyu@seE zA0hSk%<&=FURRx<#baRS2*HKgY>=Iybc^M)+|UXuvaic#-A*30xzCG%g3jzw^;mn1 zBbpM%xO(j3J;N&m4z8&M1=*^WT_+PgZdgb37of4vTWBHg(cIAmnSi6uPLR#*E3b)N zrT7XP0|>n<0|8WYLord|?rcBUZPRn(u0-g#G{``7Y2HnZ^Bu2gb#Z=M?=N9k&usk|McJpu>yg zoX0>$KxIU~*p8XU^Fzhq6Fd}#2ABZ$!dC4(ZlPlZJ1C_a{Oz6` zx^i`Jb>M8QXBH2)ZPjXl+lWZfE4TOk=Blb6`@)~$s4IFjP5E(yaF3Ojm3}e{vMuRY zLcF&4v4!9}ur1ZP?5}tyrQ&QHyJq7Bxvadq)~hl;Jr6lr_XCq18M|>zu$O_8ZYcfH z>aWOi`MW04=esK=u(5(xukQ{5fvJ-iLN~`3NX8a`F(w?nU(ecz2N#g_X~i?zmocPj zC$`p_GR<=KkV)NSJUIR-R4HL|Euh}CSrTDV?a+vKp3lQkXoec;o#5IsH_*IqaISpA zG$&zsBgp!4{a;UNe}jhgmw&@xi+$T~zh?!p(64$;=e={OBswm+6)f$ToAlqm6$z}^ z`S5@df~uaAZLTQe`&Dpxg^nJgRKo!3l>Y(k^kxoO;8L?fOvx$?2T-Oylky;|8?6UAk}oe2C+;R6HV7uH~V<;I$sDsJ??(r_s`= zo1e*^&d@vi8LBjl*CZTO*jQ6uh&Vzn96YFeZ#G%6yqf=5s>}4oyj)OlSz$=n zWaW|DKWfWVhhAZ{?70)mNR}EChp7YB^iN9&K>t<>&5S zkZ<0sCHi|ilLmLMDOSGTKPRcgk3GLcFr+%@RnFbEzpEh{kDAajqQ#GFYOlok(oWV9 ze>qK>e%}E3p9Sha@gnZ>z9wBfow@*n-+dYw(!@W2oNUx-OVNWX$?&&Pi0MdpCtmmK zvE^v3Vb;QRY$O5M!0UWJ`(sqNKg@6#|@efO1NqD0t;uUdC)I;TyXg#?8V+GTZfl zbCsn`+s0?TNmpzB+-p8t@c;p9t^^W#{+fd(k3H>GV@x640~R+QYq zJ!F@}kxuA-yg!-gkgdPTUcY0WyBZXKa5HcxP&!7BzK{==&(~mCdifjAdV@duGmxcQ z%4gIlJu@L_hksrZ`uSA2mgs`#(#Ciz(9z%Rxu4Ee;DLLMaoB{L-8l9u1irFiRBj*Kr2VvGZ+A9*mnrY|i1~n;^#Tu(-vA|#FKsZmD-EiHd zs!pE>;-`n}RE%%6e@*DPvw5@&Kj|z=E7n<29V32Jo^5mb;Ic_K``SCMIZMLc+sb3H@h z3lO1LE2MoCL?STEQaFFBL2qRxrP%HF)TD!@z2i+d=nje57Ii_%R^+6?OaFv2yvphQ{c z+)b9;>WUakrHW1ntbVIXe!&JaK1r>R>oLv6}b?ELknD2&~JN(?3%)*>JpvY>sXD~_6)LBpwfK}ymlWl=tS+B9K zZdNR*TWhv&FD!)7alNlVV`=&zMw>^TufnxxGhHI2;V9Ziv(@u2!OmA)$ zualcc#P8xQc0h{OM!yet)47od`}_?`^~6;3s`9AJ_PMm$y>(%#kWISgNC!xMG4h2D z#k07`Mlq6LQidR|+I7pO)9vXrL~`uoHMKoad6})nx75T8=aC<^L;I(swzwCjX}Q0< zl|2s@4i(^eD?|=5?OM9ibenCTe|}BTBxt!EOmnSDT`JE0`tBrbLY2@q-S4n=ZF!i_ z`@*m?r`dyc`2gzZ#IUq2W{FaCt-N^>+7JBaDOxQKeUd`pz*DkL= zTPWYdUTBjtD+x@7F=5 ztpIG176ZyE7&TLaBPrDl#7lMkb>zuF*>pxl0fgc|^Rs9#cxpAVcwFWF)kq>IbfNwP zuAED`1){fKT?oDg+`uye8!&!8`V)ZI0||P?r1i(_VHA=C0!SQlWdv}*A3yNt31z7% z6~?lVGgl)SF{YT)*(HE9Anq$C*x7u-9`{{W=3o@@FUY&pn=z!iN=WAfk^S&<3?`~b z=!Bb|H)Prgzlo=gBc9okCZwR&Qdp+e8|zahCDvP@7`#;3z*2Vq6XRd^;|$9<^t4ae zX*)cda(UlzSzj%x{_K2qSQ+j^m2sIPmi#A10Di5=25TFAhlE!!5wwmlo3G7Cb@$7_8b z@8I^Oa{KIl>W@0)ueB3BKF>61Dkpjla9UYOVQIM>%j16UJ5NX2-+STQcix$?K?#4W zN+U&9D$C?S!n|5k?S!#c2-&dPdkZmCJ3CuYey{@F;fym)N6DJPRrWJY@_rK(@}cK@ zQP0gMnHlDarGdMfEq~QM2S8(FTF;tRCUw7cogznWdBG&Q)Yt+Gi_?+CiyxU6*flZB zxVNlu2W}OoJ48ujUl+fL?r95lhs=*0+ApHA(l13jUv;Wk5T+ zP|!<8P;Y_ZYs>KUsU49FJKOKbieUVwbY66HYM+ScC(84Kve1yNW%%f&rq}{RKXwBZ zGb-FQ%JT1zY}v@-SwA&YnO&H*QH!3LKs_ptgl~R`24;7#1f@j10{Ho*voeug_CmuT z*5Er(K*+D-Fg!7NwpUCgj8WqJ_&&*p=~Pkj?((ZXwEJvn6AI_J$(Xw&GviMpDyigdi=tKgVas+m~w21`+Nn99=ce7lD$A0-KZT?d#NSk)IpFKH>1- zx_>#1FiV8qp@KNdE~VY`c!ovhsOCjY5qZXmcx67c^<1A@z zwqmNy>%H8r>s92^$CWm(CcQ})SRWiy;o2QJwg?pK#z#SkW&Dq411_70T*HA@z33T~ z`1kw;OR8CfC19ptZJoHJ$)U+`c)x< zCWme3N}161&02RxLbvOg$LXDO4(rGasn6wdt@nw}L37x_h1CmD5<(|A!mDln_kHU# z`;Rj!-DH$UgzKK(OB*Fpc(1TBrumz%+d~|DIyDlP1zngI>St_CkLL9KuaH3Z_xFg7 z@zF+w#Z(PlZ%$7|we(7({W55s3UTmB4RNq-4e8a)!28zb<~(UjtP?*eF6`!!&W6yc z59KuuMuKoF-so1p(t|*oq_xC7cJ_O}fgdO%O=&gd`}O_D)t`2r82We%%lkLlHB@rU z?E4J|kk9}B>X3bB`xm1UCv-9NT4Iw!4Dx}_L!1gEi~SUBgk6p(G-g(2pJepes7Ml!??{SN^cKS>$|u(3o}0D^v#b#M_x zlD$;MCGv2*Skkf9%@P}t5+b$&U^P-}^aWw&%r$>f5B>43$5Q$g$qvlIaa0P_t+O9`1Htl37@L zW8-1#zDSYMA661YFtsn zV<{Jby}otFg^f8=djiCC-}6G>QJ3AK8@srl7NVZYFDQS;d30metta&gTbO0;H}`R~ zVzCQxhqC#fwu>x^Y0AE2VMeKXL}VOsAZ$$d-f*T>juzrL3e?g*@y$l-dH9T?V70)E zZ$zmxO&af3eM9lB19YFwld)GxXvR5wr)4@rk?!j);dds13te8GE(CKdpLg%+)&DUKbJJ_)YpMZ2E7JT;!z_cF}E17!LzfzR{gtIPy-`F zR^CH~E1^+5l9|k{`?L|guS$r%|F4k$I3|i=eq|f=9|HK1s!Bh+fRivLR7Dvl(-8`ozQWD zE;pw?BY@K-4e41vy#0$W)S!VE`n_)`7m&AeUDAPdn8}NN{Dr%bF~l{;?o7fb&wzs# z@8}U_NV!^g5$^7YZbhK-(<7c1 z^Zdsg^k=~D3L8a`%*hvEl~k>1lNC7NOfo6NF2<|q9)g?fj}=|ZCjeN2_^20&z*x2* zmwCHDpVOfRa+bK<&Ju>QYG|slVpt>SWB0gE`js?PUgyfOk^Fen)AjA~`b?&9cEh4y z^pGL0lcJ$Qr_ZteT04(e+ViyL`|GX5+e%*3z{|O=_S76K^6bV(S?dYo;j_k3!zNJ& zLMO>KwRyRnN`&xsf9VZJ&r|D_I&El9Y}apt$y0k4q*_Ay%?icyE4wkLzmr=Qb8=8ca^weZ!9JrkLi; zE`0#>-?sZFJ4zrq?1?6N;db#W`v2H_%djZBw_RKj1w@IVM7kS9a!5f090a601f+B5 zRzkX@B?ShgyQHLM=JkM{> z$K4SSN1LitTb-#cy8O+lk(pn@vlsKK^MgzssR)tnz7NB(L!Wrll23edb{$eOrC*{K zj(A7tk>`iyxHVp7o*?0)EV*gq_?}J|V@EYEi<>)NMlJB#G%C&k`*BXMTcE9&fKvoslP%S+nBZ+_M@K6B-m+Z?sAljE^hkLEuV!u?G?$CyX|I zrB`I~1jtYLN!{2KSqJ40ZWhA$E>Id$28MYFugLfGD{G9$c}yU74tbJAzo|_X@?PT1B}|_?B2T;Q6{hbhI8#^YPk zQ}#~KOyrM0=-j+2X!DB+tAh2fuy-WH41_oS=bq=M?I6>q=*`Q<(p2U00I$7VEv~Sl?5v4jAZJvU9$ZHlgp*H%FFel^iZF++A zJ~}amtJjkiA|>Se--jSw4T`3Ze(BcI?ul&BeT^C`YM2T5DI%5YXoeZyziWzzA@Raq z!QwzyLjU5UghmCdZEYl=G@n<^{qe)?2mVF((CT-PGw z=M1YJ@TDM!fzyDAp%QTwt2*QIHhA42F`1mDy^Va!38#fppqb!X%uFsdIXg#t;U zMvVgf=sFA)-gW>kJmAEG9cUSIdZuDl8fVNAB@56f|4V~ z=RE|$LR4J_FMq1At10HyN&6HO{fP2Z^!yUms1-ZJ1>LD#{-t^;9GTo~E?>j;c*t9x zb4N`roQ~)l!@Lq?P3qA1N2pi&n@WQ;$WdqIcg0ID1JWF^o#>6u2A0~D$35>c(#+5( z38C%xOU!mRD@WjpXi8i)l$7+BczWYvJP*@0`>dIUmP}SKf zx~fr^CG_mw#R+DkEj1OaoQGlR5qVA#IFGxXL}>=Rb2^p=P004+U&R-;R-joq9Q)fh z^y4y{z2RV=ld)i4DoPn*mea6t(%B+Z6KH?3L(55sL^ zC>z3%b!RF4n-P~n-0@FLHjIA$+Rv8@BLi(lb|m!+ z#a64!NcIS{b&O_8a~w@a5E=mU(0)u}=!>xAU z6#a5o(u|7lytx zZMlf53kPOKE90fWI*WjdVd)|)pUd!(&G3UA;&M?KDq*CD@#w^-i}5RH?Q=f;RhnQ& zu9fR!#+Qx9LYwYiwNY~{D6eGFZoygBpLR^W8SoysdUNvTOVi?^3RM7QFb(E3m3r*iWc7TLa&>RRiF944D$zXQZaz=6 zm@rjxgxDD0zoTfgEGP%ncRt=u&v_;KZG;)d|6Cy)Z@B%`Qi2r#;p}TwJwF?<+`?$% z42;zESYOGAF#i0^)GhnTCNjEVQ=h>B+_ud)TX#5@X&37!*1&VwF^Fv6g%5KY+t#Rb z$--?a32(a*#Gk@@GXo-mk@yzYg-MX2;UI=<>vYRd;=WM=r^U!oJQ;!%!MYij!9qj`uqsNi#_CfnZv@}>S$(J&*H?Y_5d{Ty_Z|;Faw^uE-Lz*?+I~w<+6g!ca^VKU!Z{)v zIJLHzRL>8NXJppbkeCckJq~&`a|%Tgr%E@$>``_R`CMlJkl#R9o2BSpyI3|{hEs4*R?rPy zB<~O0a>Bijf3O(OBa?ol`oy3DU6{DiRHiKf_3p1<@|xpos}f^;>k@nM@vv2#9sblo zW{Ua!bh==#Zd8Kr3nuFas5=;uc!vzLDL&N@(fc6#XT~v3Z&ua)%yv34x*kz_& zihZ}Aqw$m)jJcP5Ckv(9bs${C3mL{YR6ChQ$-P4N^CUX$lD2_9o?GNJz9#YjZ)^FI z-|n#h*A{-)Q7gsuGqPPENf_EM3P1&@w{iM7A?u+Pk;4(%2vh7@G$#G;TC_bc?lkY= z%)a%;u)*)hmR(osk^-A*oyX{J7=Ju_=Oc!y;e9pkodUnVebdR1mUZX(`>%Q{EtNX> z`V12+oP<}olw}g~zTwn|`HE^Al{su(pS7MEiWy$twt_!JK&dlqOj&#`K_neyk~=Dg zQScV)jdV&uD~=p+Uu!AGr<+^cg7>c5mE($ON?72$XMBukD~l%gM-HGJDuJbv?%hX$ zQnghBdLx+5L~#Ex8~}riY4_zS;_P4zX(D=|0eSpsm<(YOdOfOlrQ#-Q1%FDZCIOC8 z(9@;Rrm6BRa=yjq0%^XrUPZMEsM(QzIHFSBK)!ZWk`iO&{;`ZJ+v}um{^%7J`b0uJ zNOmDtlUa5Ubo`%AwYfa=^-L58^4D(@dUH6$NiBTcLA-2^~J&%-$} z4n++^pVJH>Jx45x-$$PY8Y+^HJt`uupg}4lLk>)}2aMBCwp3Wb5QoP~!Fp3bCHuJ& zsC+OkYsP5j+syaA^(mS)rKuB*njq#Yw)g49-beX*u+lucPRWPrFx0WB)_S#b5%{oI zObXIY8%{T_IF)!Xp zfiU1$(DYAVx4F5w;f1kEJQ@r zqcivxJC$Yu40aL-Cmi1&Ht8q0h&42K!R@eEsOK8`qEV@s^*5?9N^^AQ=eHcfUTa6R$bw zJj$Ql365Yy>w8msCzd<3xc*u)xxKV#G$=Rq!l;r_YUgw8d6{XB_gV3c)p_E151BA; zg@;tQ!wtQl=du5?z^Hk(cvqc+UUb`>p;@Ld{qWQ{n-a8xWh|QFCb}uqshoxOrR}Ey z^8vb$vZfCUjp|y0ubO1#WOEuvuR(5wTF|N1oE%`~v69%5;m_zF}tvjV$-6oK=xU1t^B$ zUeYi!jz5|DlK#|CoLyt9J75vY9rZ+-U8`0GrCA!I?i-8ffjdm=vkdlK`HlWke%`xv z0CqgnC?~eWbIG%Yu3u}SH?Bc<1%{M^>$r#!pcI!FbbmeI>>#xa@fxEq%-n#jBp(*lYRRS{x2a zM*W#(L`-=CsR51@r<5t)Ds-nO=Zw8tc#IyNKx*Yg(_tWs6sm{CpE680UfeO!D4!DV4N(N|p1v@DD?+4S)yg8K5N5JQ5&c+&GY0 z%R;fMARy$W`hE7Db;d=lF3L)V-pA+VJ9fe`E-@!u?8;s`t+a$N3S7#^-%TG2IPR#< zJS5f1mC?X^5?_E8vxg>nDh5(AOSN$b>rT06?<}^1%};L;uHD1PXZ5xArJjP+m~3C` z*?uie!e*$6-%rwr%lgmb0m+Y3uS52EWdm$X(_imFM& zq1qs(Kx~^nhxN-8$9J)Q9yG5iKP(-kW&Dzm9A3`Rn4DaaolWol%0s&dx;HmOc#?wd zjU!6+bDb1C$qo%VM+52<~&9 zwUD{jr-trlYri2teyk{nJ;rskB2mrZZq=Ybj7ugPp>i8BDcL=C%w3B+;(v97EMlco znA$@g=meDhjAODXuisJ{OQ-@1z{0 zg>ZmBM5K*s^Ml7^@1II&EP>y-4Oi>85Rj*ZZnLup|ElvtQeg}H8fx^mOh5HZoaaJp zPzGY3(1*IieZF^ln@<$HH$v|Cy{J}H?!+*BcAB3)rrN>tk&@Df0cIyoDHbVfHc`QG zm&7L&diHKh`VplweQcsY8Fn)mj(=AeCObqQ^#!G9MBZA&mGUzZ?@bmfG@1G|IYbNJ zPiczfe%4CC#}`I4&C^a%hNx>VCemXp4q2L2+vtFM3%&o^1?T*EBGzn3J~LOq0VZEV z&VIGkERVSqNJUi3=fSpS9A`W<`Pbj2=7RIL!BE zjGs7zhHxv!CX@uEQ<9M7gX0yjE^6E!07Vg2I3sP!+o6BOXO3N~L`6+Ga0xJRaAN?S z>Ou^Rh0t8R7fjht?p45M@{dt=b0RQ2xZ-0pKkqg3_fAVQ@vaylF3_*B<6dqfO4X~? z-B;9GExsT?FZRE{`PexsLg2M;lN1PR{Wt@_QX=t=m)b_T&lTig@zWR}PCN4RPhpNh zZhNNy`@`|Dh;@b6s=XF9u|#;m*V<@(kqElRiTt-OisUO8R$|eu2VifLlF`>b`?k3- zGsy9v%PpUm6UBH%R(?Qoj{TH^Jcfy>G*4My@_qlBbu~$5?G&# zyRG&{509^FXU)rr*RJ-?TtU?}y1%|14}xQ$PAq#-PaIliir%`>{Co*d^yB@GrO3O{ z;$qAy9ZIi|IU~Vy9T2t+Kqk?hrh97~%zNKlRj}vY*s*n=j8-POJ(K%Z?KdkB(Fe2G za=Lg=LtF8FR#8(%LIY$bJ&+ut5KtMgW52&iwE6TX#y4f>6?f4$7JHuy{5JK_@i2*L z&>Em)uQ?y6ZxRoO?q1GC9!tKZQ*hFi3-j&udkf9WvYa_6*gm>6c$+I@)8o=(H%Kvpl$psLSUL!0e;r<0{z15S<8SI$v*6K-MMJt7Gh?ZoX z?_2AQHaWYKJ(D#Y=aU)3-!EdT-h4f&;i$gef9=&X%l*3AI(B=zI;_lMJ|!!gZM~$9 zyiO--l2LS1-O6EUu2ogLepWj7m3&_ua%&J$@03wV1SH zUtbX1W%tpj;l>H1>4?pt9|A8AaL2*0Cm%JWU16X=+2n3>Ouc

!6wGtfWa{ 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


|TR}H3O_*B!p}X zh0Dh$Nuf|bLDZ3gK2FH(R)C3;h`Y;rLV*HPg@k*v_m_E_`1@<)XTVeu{!y_)7(>t> z=-9v2XsEmah^I$Oh(ndiJ?QV<-420;{7Jt_XMDnwZMCaH$MNrkiY(`>ewGu`u9=T{-+lIPOjhoG}M2y vhWhJB(p Date: Wed, 27 Nov 2024 13:24:16 -0800 Subject: [PATCH 59/92] Release article: 4.60.0 (#24135) Co-authored-by: Rachael Shaw Co-authored-by: Luke Heath --- articles/fleet-4.60.0.md | 83 ++++++++++++++++++ .../articles/fleet-4.60.0-1600x900@2x.png | Bin 0 -> 53782 bytes 2 files changed, 83 insertions(+) create mode 100644 articles/fleet-4.60.0.md create mode 100644 website/assets/images/articles/fleet-4.60.0-1600x900@2x.png diff --git a/articles/fleet-4.60.0.md b/articles/fleet-4.60.0.md new file mode 100644 index 0000000000..a54b3dca62 --- /dev/null +++ b/articles/fleet-4.60.0.md @@ -0,0 +1,83 @@ +# Fleet 4.60.0 | Escrow Linux disk encryption keys, custom targets for OS settings, scripts preview + +![Fleet 4.60.0](../website/assets/images/articles/fleet-4.60.0-1600x900@2x.png) + +Fleet 4.60.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.60.0) or continue reading to get the highlights. +For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. + +## Highlights +- Escrow Linux disk encryption keys +- Custom targets for OS settings +- Preview scripts before run + +### Escrow Linux disk encryption keys + +Fleet now supports escrowing the disk encryption keys for Linux (Ubuntu and Fedora) workstations. This means teams can access encrypted data without needing the local password when an employee leaves, simplifying handoffs and ensuring critical data remains accessible while protected. Learn more in the guide [here](https://fleetdm.com/guides/enforce-disk-encryption). + +### Custom targets for OS settings + +With Fleet, you can now use a new "include any" label option to target OS settings (configuration profiles) to specific hosts within a team. This added flexibility allows for finer control over which OS settings apply to which hosts, making it easier to tweak configurations without disrupting broader baselines (Fleet [teams](https://fleetdm.com/guides/teams)). + +### Preview scripts before run + +Fleet now provides the ability to preview scripts directly on the **Host details** or **Scripts** page. This quick-view feature reduces the risk of errors by letting you verify the script is correct before running it, saving time and ensuring smoother operations. + +## Changes + +### Endpoint operations +- Added support for `labels_include_any` to gitops. +- Added major improvements to keyboard accessibility throughout app (e.g. checkboxes, dropdowns, table navigation). +- Added activity item for `fleetd` enrollment with host serial and display name. +- Added capability for Fleet to serve YARA rules to agents over HTTPS authenticated via node key (requires osquery 5.14+). +- Added a query to allow users to turn on/off automations while being transparent of the current log destination. +- Updated UI to allow users to view scripts (from both the scripts page and host details page) without downloading them. +- Updated activity feed to generate an activity when activity automations are enabled, edited, or disabled. +- Cancelled pending script executions when a script is edited or deleted. + +### Device management (MDM) +- Added better handling of timeout and insufficient permissions errors in NDES SCEP proxy. +- Added info banner for cloud customers to help with their windows autoenrollment setup. +- Added DB support for "include any" label profile deployment. +- Added support for "include any" label/profile relationships to the profile reconciliation machinery. +- Added `team_identifier` signature information to Apple macOS applications to the `/api/latest/fleet/hosts/:id/software` API endpoint. +- Added indicator of how fresh a software title's host and version counts are on the title's details page. +- Added UI for allowing users to install custom profiles on hosts that include any of the defined labels. +- Added UI features supporting disk encryption for Ubuntu and Fedora Linux. +- Added support for deb packages compressed with zstd. + +### Vulnerability management +- Allowed skipping computationally heavy population of vulnerability details when populating host software on hosts list endpoint (`GET /api/latest/fleet/hosts`) when using Fleet Premium (`populate_software=without_vulnerability_descriptions`). + +### Bug fixes and improvements +- Improved memory usage of the Fleet server when uploading a large software installer file. Note that the installer will now use (temporary) disk space and sufficient storage space is required. +- Improved performance of adding and removing profiles to large teams by an order of magnitude. +- Disabled accessibility via keyboard for forms that are disabled via a slider. +- Updated software batch endpoint status code from 200 (OK) to 202 (Accepted). +- Updated a package used for testing (msw) to improve security. +- Updated to reboot linux machine on unlock to work around GDM bug on Ubuntu 24.04. +- Updated GitOps to return an error if the deprecated `apple_bm_default_team` key is used and there are more than 1 ABM tokens in Fleet. +- Dismissed error flash on the my device page when navigating to another URL. +- Modified the Fleet setup experience feature to not run if there is no software or script configured for the setup experience. +- Set a more accurate minimum height for the Add hosts > ChromeOS > Policy for extension field, avoiding a scrollbar. +- Added UI prompt for user to reenter the password if SCEP/NDES url or username has changed. +- Updated ABM public key to download as as PEM format instead of CRT. +- Fixed issue with uploading macOS software packages that do not have a top level `Distribution.xml`, but do have a top level `PackageInfo.xml`. For example, Okta Verify.app. +- Fixed some cases where Fleet Maintained Apps generated incorrect uninstall scripts. +- Fixed a bug where a device that was removed from ABM and then added back wouldn't properly re-enroll in Fleet MDM. +- Fixed name/version parsing issue with PE (EXE) installer self-extracting archives such as Opera. +- Fixed a bug where the create and update label endpoints could return outdated information in a deployment using a mysql replica. +- Fixed the MDM configuration profiles deployment when based on excluded labels. +- Fixed gitops path resolution for installer queries and scripts to always be relative to where the query file or script is referenced. This change breaks existing YAML files that had to account for previous inconsistent behavior (e.g. installers in a subdirectory referencing scripts elsewhere). +- Fixed issue where minimum OS version enforcement was not being applied during Apple ADE if MDM IdP integration was enabled. +- Fixed a bug where users would be allowed to attempt an install of an App Store app on a host that was not MDM enrolled. + +## Ready to upgrade? + +Visit our [Upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs for instructions on updating to Fleet 4.60.0. + + + + + + + \ No newline at end of file diff --git a/website/assets/images/articles/fleet-4.60.0-1600x900@2x.png b/website/assets/images/articles/fleet-4.60.0-1600x900@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..01feb42ba2a21a4913baa21bd097e7b18f9c3de8 GIT binary patch literal 53782 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

Run this query in Fleet to find old versions of 1Password across all your computers:

-
SELECT 1 FROM apps WHERE bundle_identifier = '<%= thisApp.bundleIdentifier %>';
+
SELECT 1 FROM apps WHERE bundle_identifier = '<%= thisApp.bundleIdentifier %>' AND bundle_short_version <= '<%- thisApp.version %>';

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 Date: Wed, 27 Nov 2024 14:50:16 -0800 Subject: [PATCH 60/92] Linux disk encryption guides: add disclaimers (#24230) --- articles/enforce-disk-encryption.md | 2 +- articles/linux-disk-encryption-end-user.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/articles/enforce-disk-encryption.md b/articles/enforce-disk-encryption.md index 564bf71a91..5500d45286 100644 --- a/articles/enforce-disk-encryption.md +++ b/articles/enforce-disk-encryption.md @@ -48,7 +48,7 @@ You can click each status to view the list of hosts for that status. ## Enforce disk encryption on Linux -To enforce disk encryption on Ubuntu Linux and Fedora Linux devices, Fleet supports Linux Unified Key Setup (LUKS) for encrypting volumes. +To enforce disk encryption on Ubuntu Linux and Fedora Linux devices, Fleet supports Linux Unified Key Setup (LUKS) for encrypting volumes. Support for Ubuntu 20.04 is coming soon. 1. Share [this step-by-step guide](https://fleetdm.com/learn-more-about/encrypt-linux-device) with end users setting up a work computer running Ubuntu Linux or Fedora Linux. diff --git a/articles/linux-disk-encryption-end-user.md b/articles/linux-disk-encryption-end-user.md index 03afad69e8..0fd8ffb913 100644 --- a/articles/linux-disk-encryption-end-user.md +++ b/articles/linux-disk-encryption-end-user.md @@ -41,8 +41,8 @@ Follow the steps below to get set up. ## 3. Escrow your key with Fleet - Open Fleet Desktop. If your device is encrypted, you'll see a banner prompting you to escrow the key. - - Click **Create key**. Enter your existing encryption passphrase when prompted. - - Fleet will generate and securely store a new passphrase for recovery. + - Click **Create key**. Enter your existing encryption passphrase when prompted. + - Fleet will generate and securely store a new passphrase for recovery. This may take several minutes. A popup will appear when Fleet is done. Now, your encryption status will update to "verified" in Fleet Desktop, meaning that your recovery key has been successfully stored. From 700b901561645991575b05847596595ee7548350 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 27 Nov 2024 17:45:06 -0600 Subject: [PATCH 61/92] Website: Add link to /app-library to header navigation (#24232) Changes: - Added a link to the App library page to the website header navigation. - Updated the app details page meta description. - Updated the policy on the app-details page - Updated the headline on the app library page --- website/assets/styles/pages/app-library.less | 20 ++++++++++++++++++++ website/config/routes.js | 2 +- website/views/layouts/layout.ejs | 2 ++ website/views/pages/app-details.ejs | 2 +- website/views/pages/app-library.ejs | 4 +--- 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/website/assets/styles/pages/app-library.less b/website/assets/styles/pages/app-library.less index 5b2f66861d..4373356385 100644 --- a/website/assets/styles/pages/app-library.less +++ b/website/assets/styles/pages/app-library.less @@ -14,6 +14,15 @@ [purpose='page-title'] { max-width: 662px; margin-right: 16px; + h1 { + margin-bottom: 16px; + font-size: 32px; + font-weight: 800; + line-height: 38.4px; /* 120% */ + } + p { + margin-bottom: 0px; + } } [purpose='app-search'] { @@ -144,6 +153,10 @@ color: unset; box-shadow: 0px 0px 0px 2px rgba(0, 0, 0, 0.03); } + &.invisible { + height: 0px; + border: none; + } h4 { color: var(--text-text-brand, #192147); @@ -179,6 +192,13 @@ } [purpose='app-card'] { min-width: 40%; + &.invisible { + display: none; + border: none; + height: 0px; + padding: 0; + margin: 0; + } } } diff --git a/website/config/routes.js b/website/config/routes.js index ecc6e4b747..622cfa298c 100644 --- a/website/config/routes.js +++ b/website/config/routes.js @@ -305,7 +305,7 @@ module.exports.routes = { action: 'view-app-library', locals: { pageTitleForMeta: 'App library', - pageDescriptionForMeta: 'Discover what is available for install in Fleet\'s maintained app library.', + pageDescriptionForMeta: 'Install Fleet-maintained apps on your hosts without the need for additional configuration. Activate self-service for your end users.', } }, diff --git a/website/views/layouts/layout.ejs b/website/views/layouts/layout.ejs index 7aefb13152..1ec367716a 100644 --- a/website/views/layouts/layout.ejs +++ b/website/views/layouts/layout.ejs @@ -169,6 +169,7 @@ Get started Built-in checks Raw data + App library Tutorials & guides API SUPPORT @@ -230,6 +231,7 @@ Get started Built-in checks Raw data + App library Tutorials & guides API SUPPORT diff --git a/website/views/pages/app-details.ejs b/website/views/pages/app-details.ejs index c5e384b948..3e0d3cd1f4 100644 --- a/website/views/pages/app-details.ejs +++ b/website/views/pages/app-details.ejs @@ -61,7 +61,7 @@

diff --git a/website/views/pages/app-library.ejs b/website/views/pages/app-library.ejs index 8ea942f115..cf67da5a9b 100644 --- a/website/views/pages/app-library.ejs +++ b/website/views/pages/app-library.ejs @@ -4,7 +4,7 @@

App library

- +

Install Fleet-maintained apps on your hosts without the need for additional configuration. Activate self-service for your end users.

- -
From b22b6c0e66b2dab389e14d75becaf9da0af8307e Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Wed, 27 Nov 2024 21:17:44 -0600 Subject: [PATCH 62/92] Website: Add redirect and broaden URL route (#24235) --- website/config/routes.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/website/config/routes.js b/website/config/routes.js index 622cfa298c..b0f301d1e7 100644 --- a/website/config/routes.js +++ b/website/config/routes.js @@ -293,10 +293,10 @@ module.exports.routes = { } }, - 'GET /customer-stories': { + 'GET /testimonials': { action: 'view-testimonials', locals: { - pageTitleForMeta: 'Customer stories', + pageTitleForMeta: 'What people are saying', pageDescriptionForMeta: 'See what people are saying about Fleet.' } }, @@ -331,6 +331,7 @@ module.exports.routes = { // ``` // 'GET /docs/using-fleet/learn-how-to-use-fleet': '/docs/using-fleet/fleet-for-beginners', // ``` + 'GET /customer-stories': '/testimonials', 'GET /try': '/get-started', 'GET /docs/deploying/fleet-public-load-testing': '/docs/deploying/load-testing', 'GET /handbook/customer-experience': '/handbook/customers', From f9854b4e73edf12f883472db2bb3071d1689446e Mon Sep 17 00:00:00 2001 From: Mike Thomas <78363703+mike-j-thomas@users.noreply.github.com> Date: Thu, 28 Nov 2024 12:19:29 +0900 Subject: [PATCH 63/92] Update testimonials.ejs (#24234) Changed the kicker to read "Is it any good?" --- website/views/pages/testimonials.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/views/pages/testimonials.ejs b/website/views/pages/testimonials.ejs index 7cb0a2016e..f487d6a4fb 100644 --- a/website/views/pages/testimonials.ejs +++ b/website/views/pages/testimonials.ejs @@ -2,7 +2,7 @@
-

Customer stories

+

Is it any good?

What people are saying

From f670aff855ab59f289d1bb27b2813536c362c02f Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Wed, 27 Nov 2024 21:25:03 -0600 Subject: [PATCH 64/92] Change auto-approvees: Update custom.js ("maintainers") (#24237) --- website/config/custom.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/website/config/custom.js b/website/config/custom.js index 4e0331a1ff..6b04fd379d 100644 --- a/website/config/custom.js +++ b/website/config/custom.js @@ -220,18 +220,17 @@ module.exports.custom = { // Articles and release notes 'CHANGELOG.md': ['mikermcneil', 'noahtalerman', 'lukeheath'], - 'articles': ['mike-j-thomas', 'mike-j-thomas', 'eashaw', 'mikermcneil', 'rachaelshaw'], - 'website/assets/images/articles': ['mike-j-thomas', 'mike-j-thomas', 'eashaw', 'mikermcneil'], + 'articles': ['mike-j-thomas', 'eashaw', 'mikermcneil', 'rachaelshaw'], + 'website/assets/images/articles': ['mike-j-thomas', 'eashaw', 'mikermcneil'], // Website (fleetdm.com) 'website': ['mikermcneil', 'eashaw'],// (default for website) - 'website/views': 'eashaw', + 'website/views': ['eashaw', 'mike-j-thomas'], 'website/generators': 'eashaw', 'website/assets': 'eashaw', 'website/package.json': 'eashaw', 'website/config/routes.js': ['eashaw', 'mike-j-thomas'],// (for managing website URLs) 'website/config/policies.js': ['eashaw', 'mikermcneil'],// (for adding new pages and managing permissions) - 'website/api/controllers/imagine': ['eashaw', 'mike-j-thomas'],// landing pages // 🫧 Vulnerability dashboard 'ee/vulnerability-dashboard': ['eashaw', 'mikermcneil'],// (catch-all) From fbeb179b06327f30fb33abf0c8fd84a1d1fd4405 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Wed, 27 Nov 2024 21:53:46 -0600 Subject: [PATCH 65/92] Fix the build (Sorry) (#24238) --- website/config/routes.js | 1 - 1 file changed, 1 deletion(-) diff --git a/website/config/routes.js b/website/config/routes.js index b0f301d1e7..ab38d48a1d 100644 --- a/website/config/routes.js +++ b/website/config/routes.js @@ -522,7 +522,6 @@ module.exports.routes = { // // For example, a clever user might try to visit fleetdm.com/documentation, not knowing that Fleet's website // puts this kind of thing under /docs, NOT /documentation. These "convenience" redirects are to help them out. - 'GET /testimonials': '/customer-stories', 'GET /admin': '/admin/email-preview', 'GET /renew': 'https://calendly.com/zayhanlon/fleet-renewal-discussion', 'GET /documentation': '/docs', From 67c2698ae00564320740ef782422bbd68f4c697b Mon Sep 17 00:00:00 2001 From: Isabell Reedy <113355639+ireedy@users.noreply.github.com> Date: Thu, 28 Nov 2024 01:55:09 -0500 Subject: [PATCH 66/92] Update date for US contractor payroll (#24156) This should actually happen on the 27th, not the 28th. --- handbook/finance/finance.rituals.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handbook/finance/finance.rituals.yml b/handbook/finance/finance.rituals.yml index 1d4ddeb946..84e1706f5b 100644 --- a/handbook/finance/finance.rituals.yml +++ b/handbook/finance/finance.rituals.yml @@ -90,7 +90,7 @@ repo: "confidential" - task: "Run US contractor payroll" - startedOn: "2024-02-28" + startedOn: "2024-02-27" 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" From edcbc639e98a19b478daf8e9c4c7a032451b7590 Mon Sep 17 00:00:00 2001 From: Drew Baker <89049099+Drew-P-drawers@users.noreply.github.com> Date: Thu, 28 Nov 2024 02:00:23 -0500 Subject: [PATCH 67/92] Update README.md (#24142) --- handbook/demand/README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/handbook/demand/README.md b/handbook/demand/README.md index 21751984a6..826eedeca5 100644 --- a/handbook/demand/README.md +++ b/handbook/demand/README.md @@ -234,9 +234,15 @@ To do this: 6. Repeat this process until all remaining draft orders show "Waiting for Fulfillment" +### Request swag +There are many times in which community members, customers, and contributors are in need of some cool Fleet swag. To request swag: +1. [Create an issue](https://app.zenhub.com/workspaces/g-demand-64e6c8e2d35c7f001a457b7f/issues/gh/fleetdm/confidential/new?issueType=issue) on the #g-demand board. +2. Provide order details (e.g. expected shirt size, name, and shipping details). +3. Decide if you'd like to include a personalized message and attach it to the issue. + ### Fulfill a swag request -There are many times in which community members, customers, and contributors are in need of some cool Fleet swag. Swag requests are received in the form of issues and will be fulfilled based on availability. To process a swag request: +Swag requests are received in the form of issues and will be fulfilled based on availability. To process a swag request: 1. Check and communicate availability to the requestor. 2. Use the appropriate shipping method that best serves the company. 3. Attach tracking number to the issue and tag the requestor. From c27c859b3aa48eb81f0cf0befa70df67ef32a802 Mon Sep 17 00:00:00 2001 From: Martin Angers Date: Mon, 2 Dec 2024 09:14:10 -0500 Subject: [PATCH 68/92] Windows MDM migration: implement fleetd notification and migration (#24185) --- ee/server/service/devices.go | 4 +- .../22898-support-windows-mdm-migration | 1 + orbit/pkg/update/execwinapi_windows.go | 3 +- orbit/pkg/update/notifications.go | 32 ++-- orbit/pkg/update/notifications_test.go | 28 ++-- server/fleet/hosts.go | 2 +- server/fleet/mdm.go | 5 + server/fleet/orbit.go | 7 +- server/service/integration_mdm_test.go | 137 +++++++++++++----- server/service/microsoft_mdm.go | 14 +- server/service/orbit.go | 15 +- server/service/osquery.go | 3 +- server/service/osquery_test.go | 10 +- server/service/osquery_utils/queries.go | 14 +- tools/mdm/windows/poc-mdm-server/README.md | 17 ++- 15 files changed, 218 insertions(+), 74 deletions(-) create mode 100644 orbit/changes/22898-support-windows-mdm-migration diff --git a/ee/server/service/devices.go b/ee/server/service/devices.go index 77a6c7ce46..b0752ef0ec 100644 --- a/ee/server/service/devices.go +++ b/ee/server/service/devices.go @@ -17,8 +17,6 @@ func (svc *Service) ListDevicePolicies(ctx context.Context, host *fleet.Host) ([ return svc.ds.ListPoliciesForHost(ctx, host) } -const refetchMDMUnenrollCriticalQueryDuration = 3 * time.Minute - // TriggerMigrateMDMDevice triggers the webhook associated with the MDM // migration to Fleet configuration. It is located in the ee package instead of // the server/webhooks one because it is a Fleet Premium only feature and for @@ -88,7 +86,7 @@ func (svc *Service) TriggerMigrateMDMDevice(ctx context.Context, host *fleet.Hos // if the webhook was successfully triggered, we update the host to // constantly run the query to check if it has been unenrolled from its // existing third-party MDM. - refetchUntil := svc.clock.Now().Add(refetchMDMUnenrollCriticalQueryDuration) + refetchUntil := svc.clock.Now().Add(fleet.RefetchMDMUnenrollCriticalQueryDuration) host.RefetchCriticalQueriesUntil = &refetchUntil if err := svc.ds.UpdateHostRefetchCriticalQueriesUntil(ctx, host.ID, &refetchUntil); err != nil { return ctxerr.Wrap(ctx, err, "save host with refetch critical queries timestamp") diff --git a/orbit/changes/22898-support-windows-mdm-migration b/orbit/changes/22898-support-windows-mdm-migration new file mode 100644 index 0000000000..e4e0401d2b --- /dev/null +++ b/orbit/changes/22898-support-windows-mdm-migration @@ -0,0 +1 @@ +* Added support to migrate the MDM provider of Windows devices to Fleet. diff --git a/orbit/pkg/update/execwinapi_windows.go b/orbit/pkg/update/execwinapi_windows.go index 3c0988a0fc..d07e50192f 100644 --- a/orbit/pkg/update/execwinapi_windows.go +++ b/orbit/pkg/update/execwinapi_windows.go @@ -175,7 +175,8 @@ func generateWindowsMDMAccessTokenPayload(args WindowsMDMEnrollmentArgs) ([]byte return json.Marshal(pld) } -// IsRunningOnWindowsServer determines if the process is running on a Windows server. Exported so it can be used across packages. +// IsRunningOnWindowsServer determines if the process is running on a Windows +// server. Exported so it can be used across packages. func IsRunningOnWindowsServer() (bool, error) { installType, err := readInstallationType() if err != nil { diff --git a/orbit/pkg/update/notifications.go b/orbit/pkg/update/notifications.go index cd5f256458..8e1b1003a6 100644 --- a/orbit/pkg/update/notifications.go +++ b/orbit/pkg/update/notifications.go @@ -165,14 +165,22 @@ func ApplyWindowsMDMEnrollmentFetcherMiddleware( var errIsWindowsServer = errors.New("device is a Windows Server") -// GetConfig calls the wrapped Fetcher's GetConfig method, and if the fleet -// server set the "needs windows enrollment" flag to true, executes the command -// to enroll into Windows MDM (or not, if the device is a Windows Server). +// Run checks if the fleet server set the "needs windows {un}enrollment" flag +// to true, and executes the command to {un}enroll into Windows MDM (or not, if +// the device is a Windows Server). It also unenrolls the device if the flag +// "needs MDM migration" is set to true, so that the device can then be +// enrolled in Fleet MDM. func (w *windowsMDMEnrollmentConfigReceiver) Run(cfg *fleet.OrbitConfig) error { - if cfg.Notifications.NeedsProgrammaticWindowsMDMEnrollment { + switch { + case cfg.Notifications.NeedsProgrammaticWindowsMDMEnrollment: w.attemptEnrollment(cfg.Notifications) - } else if cfg.Notifications.NeedsProgrammaticWindowsMDMUnenrollment { - w.attemptUnenrollment() + case cfg.Notifications.NeedsProgrammaticWindowsMDMUnenrollment, + cfg.Notifications.NeedsMDMMigration: + label := "unenroll" + if cfg.Notifications.NeedsMDMMigration { + label = "migrate" + } + w.attemptUnenrollment(label) } return nil } @@ -227,18 +235,18 @@ func (w *windowsMDMEnrollmentConfigReceiver) attemptEnrollment(notifs fleet.Orbi } } -func (w *windowsMDMEnrollmentConfigReceiver) attemptUnenrollment() { +func (w *windowsMDMEnrollmentConfigReceiver) attemptUnenrollment(actionLabel string) { if w.mu.TryLock() { defer w.mu.Unlock() // do not unenroll Windows Servers, and do not attempt unenrollment if the // last run is not at least Frequency ago. if w.isWindowsServer { - log.Debug().Msg("skipped calling UnregisterDeviceWithManagement to unenroll Windows device, device is a server") + log.Debug().Msgf("skipped calling UnregisterDeviceWithManagement to %s Windows device, device is a server", actionLabel) return } if time.Since(w.lastUnenrollRun) <= w.Frequency { - log.Debug().Msg("skipped calling UnregisterDeviceWithManagement to unenroll Windows device, last run was too recent") + log.Debug().Msgf("skipped calling UnregisterDeviceWithManagement to %s Windows device, last run was too recent", actionLabel) return } @@ -252,15 +260,15 @@ func (w *windowsMDMEnrollmentConfigReceiver) attemptUnenrollment() { if err := fn(args); err != nil { if errors.Is(err, errIsWindowsServer) { w.isWindowsServer = true - log.Info().Msg("device is a Windows Server, skipping unenrollment") + log.Info().Msgf("device is a Windows Server, skipping %s", actionLabel) } else { - log.Info().Err(err).Msg("calling UnregisterDeviceWithManagement to unenroll Windows device failed") + log.Info().Err(err).Msgf("calling UnregisterDeviceWithManagement to %s Windows device failed", actionLabel) } return } w.lastUnenrollRun = time.Now() - log.Info().Msg("successfully called UnregisterDeviceWithManagement to unenroll Windows device") + log.Info().Msgf("successfully called UnregisterDeviceWithManagement to %s Windows device", actionLabel) } } diff --git a/orbit/pkg/update/notifications_test.go b/orbit/pkg/update/notifications_test.go index 1455d8fc7e..a16b9dfbf1 100644 --- a/orbit/pkg/update/notifications_test.go +++ b/orbit/pkg/update/notifications_test.go @@ -191,21 +191,27 @@ func TestWindowsMDMEnrollment(t *testing.T) { desc string enrollFlag *bool unenrollFlag *bool + migrateFlag *bool discoveryURL string apiErr error wantAPICalled bool wantLog string }{ - {"enroll=false", ptr.Bool(false), nil, "", nil, false, ""}, - {"enroll=true,discovery=''", ptr.Bool(true), nil, "", nil, false, "discovery endpoint is empty"}, - {"enroll=true,discovery!='',success", ptr.Bool(true), nil, "http://example.com", nil, true, "successfully called RegisterDeviceWithManagement"}, - {"enroll=true,discovery!='',fail", ptr.Bool(true), nil, "http://example.com", io.ErrUnexpectedEOF, true, "enroll Windows device failed"}, - {"enroll=true,discovery!='',server", ptr.Bool(true), nil, "http://example.com", errIsWindowsServer, true, "device is a Windows Server, skipping enrollment"}, + {"enroll=false", ptr.Bool(false), nil, nil, "", nil, false, ""}, + {"enroll=true,discovery=''", ptr.Bool(true), nil, nil, "", nil, false, "discovery endpoint is empty"}, + {"enroll=true,discovery!='',success", ptr.Bool(true), nil, nil, "http://example.com", nil, true, "successfully called RegisterDeviceWithManagement"}, + {"enroll=true,discovery!='',fail", ptr.Bool(true), nil, nil, "http://example.com", io.ErrUnexpectedEOF, true, "enroll Windows device failed"}, + {"enroll=true,discovery!='',server", ptr.Bool(true), nil, nil, "http://example.com", errIsWindowsServer, true, "device is a Windows Server, skipping enrollment"}, - {"unenroll=false", nil, ptr.Bool(false), "", nil, false, ""}, - {"unenroll=true,success", nil, ptr.Bool(true), "", nil, true, "successfully called UnregisterDeviceWithManagement"}, - {"unenroll=true,fail", nil, ptr.Bool(true), "", io.ErrUnexpectedEOF, true, "unenroll Windows device failed"}, - {"unenroll=true,server", nil, ptr.Bool(true), "", errIsWindowsServer, true, "device is a Windows Server, skipping unenrollment"}, + {"unenroll=false", nil, ptr.Bool(false), nil, "", nil, false, ""}, + {"unenroll=true,success", nil, ptr.Bool(true), nil, "", nil, true, "successfully called UnregisterDeviceWithManagement to unenroll"}, + {"unenroll=true,fail", nil, ptr.Bool(true), nil, "", io.ErrUnexpectedEOF, true, "unenroll Windows device failed"}, + {"unenroll=true,server", nil, ptr.Bool(true), nil, "", errIsWindowsServer, true, "device is a Windows Server, skipping unenroll"}, + + {"migrate=false", nil, nil, ptr.Bool(false), "", nil, false, ""}, + {"migrate=true,success", nil, nil, ptr.Bool(true), "", nil, true, "successfully called UnregisterDeviceWithManagement to migrate"}, + {"migrate=true,fail", nil, nil, ptr.Bool(true), "", io.ErrUnexpectedEOF, true, "migrate Windows device failed"}, + {"migrate=true,server", nil, nil, ptr.Bool(true), "", errIsWindowsServer, true, "device is a Windows Server, skipping migrate"}, } for _, c := range cases { @@ -215,12 +221,14 @@ func TestWindowsMDMEnrollment(t *testing.T) { var ( enroll = c.enrollFlag != nil && *c.enrollFlag unenroll = c.unenrollFlag != nil && *c.unenrollFlag + migrate = c.migrateFlag != nil && *c.migrateFlag isUnenroll = c.unenrollFlag != nil ) testConfig := &fleet.OrbitConfig{Notifications: fleet.OrbitConfigNotifications{ NeedsProgrammaticWindowsMDMEnrollment: enroll, NeedsProgrammaticWindowsMDMUnenrollment: unenroll, + NeedsMDMMigration: migrate, WindowsMDMDiscoveryEndpoint: c.discoveryURL, }} @@ -241,7 +249,7 @@ func TestWindowsMDMEnrollment(t *testing.T) { err := enrollReceiver.Run(testConfig) require.NoError(t, err) // the dummy receiver never returns an error - if isUnenroll { + if isUnenroll || migrate { require.Equal(t, c.wantAPICalled, unenrollGotCalled) require.False(t, enrollGotCalled) } else { diff --git a/server/fleet/hosts.go b/server/fleet/hosts.go index 95ce9ee268..d55e4ff3ce 100644 --- a/server/fleet/hosts.go +++ b/server/fleet/hosts.go @@ -344,7 +344,7 @@ type Host struct { // is that the latter is a one-time request, while this one is a persistent // until the timestamp expires. The initial use-case is to check for a host // to be unenrolled from its old MDM solution, in the "migrate to Fleet MDM" - // workflow. + // workflow (both Apple and Windows). // // In the future, if we want to use it for more than one use-case, we could // add a "reason" field with well-known labels so we know what condition(s) diff --git a/server/fleet/mdm.go b/server/fleet/mdm.go index 4d3e09f68e..b441067398 100644 --- a/server/fleet/mdm.go +++ b/server/fleet/mdm.go @@ -18,6 +18,11 @@ const ( MDMAppleDeclarationUUIDPrefix = "d" MDMAppleProfileUUIDPrefix = "a" MDMWindowsProfileUUIDPrefix = "w" + + // RefetchMDMUnenrollCriticalQueryDuration is the duration to set the + // RefetchCriticalQueriesUntil field when migrating a device from a + // third-party MDM solution to Fleet. + RefetchMDMUnenrollCriticalQueryDuration = 3 * time.Minute ) type AppleMDM struct { diff --git a/server/fleet/orbit.go b/server/fleet/orbit.go index 6c06a963e2..357af033a6 100644 --- a/server/fleet/orbit.go +++ b/server/fleet/orbit.go @@ -8,7 +8,12 @@ import "encoding/json" type OrbitConfigNotifications struct { RenewEnrollmentProfile bool `json:"renew_enrollment_profile,omitempty"` RotateDiskEncryptionKey bool `json:"rotate_disk_encryption_key,omitempty"` - NeedsMDMMigration bool `json:"needs_mdm_migration,omitempty"` + + // NeedsMDMMigration is set to true if MDM is enabled for the host's + // platform, MDM migration is enabled for that platform, and the host is + // eligible for such a migration (e.g. it is enrolled in a third-party MDM + // solution). + NeedsMDMMigration bool `json:"needs_mdm_migration,omitempty"` // NeedsProgrammaticWindowsMDMEnrollment is sent as true if Windows MDM is // enabled and the device should be enrolled as far as the server knows (e.g. diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go index 14b26700d0..4a4230cd31 100644 --- a/server/service/integration_mdm_test.go +++ b/server/service/integration_mdm_test.go @@ -5926,7 +5926,6 @@ func (s *integrationMDMTestSuite) TestAppConfigWindowsMDM() { err = s.ds.SaveAppConfig(context.Background(), appConf) require.NoError(s.T(), err) - // the feature flag is enabled for the MDM test suite var acResp appConfigResponse s.DoJSON("GET", "/api/latest/fleet/config", nil, http.StatusOK, &acResp) assert.False(t, acResp.MDM.WindowsEnabledAndConfigured) @@ -5937,47 +5936,66 @@ func (s *integrationMDMTestSuite) TestAppConfigWindowsMDM() { tm2, err := s.ds.NewTeam(ctx, &fleet.Team{Name: t.Name() + "2"}) require.NoError(t, err) - // create some hosts - a Windows workstation in each team and no-team, - // Windows server in no team, Windows workstation enrolled in a 3rd-party in - // team 2, Windows workstation already enrolled in Fleet in no team, and a - // macOS host in no team. - metadataHosts := []struct { - os string - suffix string - isServer bool - teamID *uint - enrolledName string - shouldEnroll bool - }{ - {"windows", "win-no-team", false, nil, "", true}, - {"windows", "win-team-1", false, &tm1.ID, "", true}, - {"windows", "win-team-2", false, &tm2.ID, "", true}, - {"windows", "win-server", true, nil, "", false}, // is a server - {"windows", "win-third-party", false, &tm2.ID, fleet.WellKnownMDMSimpleMDM, false}, // is enrolled in 3rd-party - {"windows", "win-fleet", false, nil, fleet.WellKnownMDMFleet, false}, // is already Fleet-enrolled - {"darwin", "macos-no-team", false, nil, "", false}, // is not Windows - } - hostsBySuffix := make(map[string]*fleet.Host, len(metadataHosts)) - for _, meta := range metadataHosts { - h := createOrbitEnrolledHost(t, meta.os, meta.suffix, s.ds) - createDeviceTokenForHost(t, s.ds, h.ID, meta.suffix) - err := s.ds.SetOrUpdateMDMData(ctx, h.ID, meta.isServer, meta.enrolledName != "", "https://example.com", false, meta.enrolledName, "") - require.NoError(t, err) - if meta.teamID != nil { - err = s.ds.AddHostsToTeam(ctx, meta.teamID, []uint{h.ID}) - require.NoError(t, err) - } - hostsBySuffix[meta.suffix] = h - } - // enable Windows MDM acResp = appConfigResponse{} s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{ "mdm": { "windows_enabled_and_configured": true } }`), http.StatusOK, &acResp) assert.True(t, acResp.MDM.WindowsEnabledAndConfigured) + assert.False(t, acResp.MDM.WindowsMigrationEnabled) s.lastActivityOfTypeMatches(fleet.ActivityTypeEnabledWindowsMDM{}.ActivityName(), `{}`, 0) + // create some hosts - a Windows workstation in each team and no-team, + // Windows server in no team, Windows workstation enrolled in a 3rd-party in + // team 2, Windows workstation already enrolled in Fleet in no team, and a + // macOS host in no team. + metadataHosts := []struct { + os string + suffix string + isServer bool + teamID *uint + enrolledName string + shouldEnroll bool + shouldMigrate bool + }{ + {"windows", "win-no-team", false, nil, "", true, false}, + {"windows", "win-team-1", false, &tm1.ID, "", true, false}, + {"windows", "win-team-2", false, &tm2.ID, "", true, false}, + {"windows", "win-server", true, nil, "", false, false}, // is a server + {"windows", "win-third-party", false, &tm2.ID, fleet.WellKnownMDMSimpleMDM, false, true}, // is enrolled in 3rd-party + {"windows", "win-fleet", false, nil, fleet.WellKnownMDMFleet, false, false}, // is already Fleet-enrolled + {"darwin", "macos-no-team", false, nil, "", false, false}, // is not Windows + {"windows", "win-server-third-party", true, nil, fleet.WellKnownMDMSimpleMDM, false, false}, // is enrolled in 3rd-party, but is a server + } + hostsBySuffix := make(map[string]*fleet.Host, len(metadataHosts)) + for _, meta := range metadataHosts { + var host *fleet.Host + if meta.os == "windows" && meta.enrolledName == fleet.WellKnownMDMFleet { + // special-case to create a properly MDM-enrolled into Fleet host + host = createOrbitEnrolledHost(t, meta.os, meta.suffix, s.ds) + mdmDevice := mdmtest.NewTestMDMClientWindowsProgramatic(s.server.URL, *host.OrbitNodeKey) + err := mdmDevice.Enroll() + require.NoError(t, err) + err = s.ds.UpdateMDMWindowsEnrollmentsHostUUID(ctx, host.UUID, mdmDevice.DeviceID) + require.NoError(t, err) + err = s.ds.SetOrUpdateMDMData(ctx, host.ID, meta.isServer, true, s.server.URL, false, fleet.WellKnownMDMFleet, "") + require.NoError(t, err) + } else { + host = createOrbitEnrolledHost(t, meta.os, meta.suffix, s.ds) + createDeviceTokenForHost(t, s.ds, host.ID, meta.suffix) + + serverURL := "https://example.com" + err := s.ds.SetOrUpdateMDMData(ctx, host.ID, meta.isServer, meta.enrolledName != "", serverURL, false, meta.enrolledName, "") + require.NoError(t, err) + } + + if meta.teamID != nil { + err = s.ds.AddHostsToTeam(ctx, meta.teamID, []uint{host.ID}) + require.NoError(t, err) + } + hostsBySuffix[meta.suffix] = host + } + // get the orbit config for each host, verify that only the expected ones // receive the "needs enrollment to Windows MDM" notification. for _, meta := range metadataHosts { @@ -5987,6 +6005,7 @@ func (s *integrationMDMTestSuite) TestAppConfigWindowsMDM() { http.StatusOK, &resp) require.Equal(t, meta.shouldEnroll, resp.Notifications.NeedsProgrammaticWindowsMDMEnrollment) require.False(t, resp.Notifications.NeedsProgrammaticWindowsMDMUnenrollment) + require.False(t, resp.Notifications.NeedsMDMMigration) if meta.shouldEnroll { require.Contains(t, resp.Notifications.WindowsMDMDiscoveryEndpoint, microsoft_mdm.MDE2DiscoveryPath) } else { @@ -5994,7 +6013,34 @@ func (s *integrationMDMTestSuite) TestAppConfigWindowsMDM() { } } - // turn on MDM for a host + // enable Windows MDM migration + acResp = appConfigResponse{} + s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{ + "mdm": { "windows_migration_enabled": true } + }`), http.StatusOK, &acResp) + assert.True(t, acResp.MDM.WindowsEnabledAndConfigured) + assert.True(t, acResp.MDM.WindowsMigrationEnabled) + s.lastActivityMatches(fleet.ActivityTypeEnabledWindowsMDMMigration{}.ActivityName(), `{}`, 0) + + // get the orbit config for each host, verify that only the expected ones + // receive the "needs enrollment to Windows MDM" and "needs migration" notifications. + // They still get enrollment notifications as we have not proceeded with enrollment. + for _, meta := range metadataHosts { + var resp orbitGetConfigResponse + s.DoJSON("POST", "/api/fleet/orbit/config", + json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *hostsBySuffix[meta.suffix].OrbitNodeKey)), + http.StatusOK, &resp) + require.Equal(t, meta.shouldEnroll, resp.Notifications.NeedsProgrammaticWindowsMDMEnrollment) + require.Equal(t, meta.shouldMigrate, resp.Notifications.NeedsMDMMigration) + require.False(t, resp.Notifications.NeedsProgrammaticWindowsMDMUnenrollment) + if meta.shouldEnroll { + require.Contains(t, resp.Notifications.WindowsMDMDiscoveryEndpoint, microsoft_mdm.MDE2DiscoveryPath) + } else { + require.Empty(t, resp.Notifications.WindowsMDMDiscoveryEndpoint) + } + } + + // turn on MDM for another host orbitHost, _ := createWindowsHostThenEnrollMDM(s.ds, s.server.URL, t) // disable Microsoft MDM @@ -6002,9 +6048,10 @@ func (s *integrationMDMTestSuite) TestAppConfigWindowsMDM() { "mdm": { "windows_enabled_and_configured": false } }`), http.StatusOK, &acResp) assert.False(t, acResp.MDM.WindowsEnabledAndConfigured) + assert.False(t, acResp.MDM.WindowsMigrationEnabled) s.lastActivityOfTypeMatches(fleet.ActivityTypeDisabledWindowsMDM{}.ActivityName(), `{}`, 0) - // get the orbit config for win-no-team should return true for the + // get the orbit config for that MDM-enrolled host returns true for the // unenrollment notification var resp orbitGetConfigResponse s.DoJSON("POST", "/api/fleet/orbit/config", @@ -6012,7 +6059,25 @@ func (s *integrationMDMTestSuite) TestAppConfigWindowsMDM() { http.StatusOK, &resp) require.True(t, resp.Notifications.NeedsProgrammaticWindowsMDMUnenrollment) require.False(t, resp.Notifications.NeedsProgrammaticWindowsMDMEnrollment) + require.False(t, resp.Notifications.NeedsMDMMigration) require.Empty(t, resp.Notifications.WindowsMDMDiscoveryEndpoint) + + // get the orbit config for each host, only the fleet-enrolled ones get the unenrollment, + // and none get enrollment/migration (because MDM is now off). + for _, meta := range metadataHosts { + var resp orbitGetConfigResponse + s.DoJSON("POST", "/api/fleet/orbit/config", + json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *hostsBySuffix[meta.suffix].OrbitNodeKey)), + http.StatusOK, &resp) + require.False(t, resp.Notifications.NeedsProgrammaticWindowsMDMEnrollment) + require.False(t, resp.Notifications.NeedsMDMMigration) + if meta.enrolledName == fleet.WellKnownMDMFleet { + require.True(t, resp.Notifications.NeedsProgrammaticWindowsMDMUnenrollment) + } else { + require.False(t, resp.Notifications.NeedsProgrammaticWindowsMDMUnenrollment) + } + require.Empty(t, resp.Notifications.WindowsMDMDiscoveryEndpoint) + } } func (s *integrationMDMTestSuite) TestOrbitConfigNudgeSettings() { diff --git a/server/service/microsoft_mdm.go b/server/service/microsoft_mdm.go index 78fde975ff..4d79d45ce8 100644 --- a/server/service/microsoft_mdm.go +++ b/server/service/microsoft_mdm.go @@ -615,14 +615,22 @@ func NewCertStoreProvisioningData(enrollmentType string, identityFingerprint str return certStore } -// IsEligibleForWindowsMDMEnrollment returns true if the host can be enrolled +// isEligibleForWindowsMDMEnrollment returns true if the host can be enrolled // in Fleet's Windows MDM (if it was enabled). -func IsEligibleForWindowsMDMEnrollment(host *fleet.Host, mdmInfo *fleet.HostMDM) bool { +func isEligibleForWindowsMDMEnrollment(host *fleet.Host, mdmInfo *fleet.HostMDM) bool { return host.FleetPlatform() == "windows" && host.IsOsqueryEnrolled() && (mdmInfo == nil || (!mdmInfo.IsServer && !mdmInfo.Enrolled)) } +// isEligibleForWindowsMDMMigration returns true if the host can be migrated to +// Fleet's Windows MDM (if it was enabled). +func isEligibleForWindowsMDMMigration(host *fleet.Host, mdmInfo *fleet.HostMDM) bool { + return host.FleetPlatform() == "windows" && + host.IsOsqueryEnrolled() && + (mdmInfo != nil && !mdmInfo.IsServer && mdmInfo.Enrolled && mdmInfo.Name != fleet.WellKnownMDMFleet) +} + // NewApplicationProvisioningData returns a new ApplicationProvisioningData Characteristic // The Application Provisioning configuration is used for bootstrapping a device with an OMA DM account // The paramenters here maps to the W7 application CSP @@ -976,7 +984,7 @@ func (svc *Service) authBinarySecurityToken(ctx context.Context, authToken *flee } // This ensures that only hosts that are eligible for Windows enrollment can be enrolled - if !IsEligibleForWindowsMDMEnrollment(host, mdmInfo) { + if !isEligibleForWindowsMDMEnrollment(host, mdmInfo) { return "", "", errors.New("host is not elegible for Windows MDM enrollment") } diff --git a/server/service/orbit.go b/server/service/orbit.go index df75ce21c5..1be699ad47 100644 --- a/server/service/orbit.go +++ b/server/service/orbit.go @@ -268,13 +268,26 @@ func (svc *Service) GetOrbitConfig(ctx context.Context) (fleet.OrbitConfig, erro // set the host's orbit notifications for Windows MDM if appConfig.MDM.WindowsEnabledAndConfigured { - if IsEligibleForWindowsMDMEnrollment(host, mdmInfo) { + if isEligibleForWindowsMDMEnrollment(host, mdmInfo) { discoURL, err := microsoft_mdm.ResolveWindowsMDMDiscovery(appConfig.ServerSettings.ServerURL) if err != nil { return fleet.OrbitConfig{}, err } notifs.WindowsMDMDiscoveryEndpoint = discoURL notifs.NeedsProgrammaticWindowsMDMEnrollment = true + } else if appConfig.MDM.WindowsMigrationEnabled && isEligibleForWindowsMDMMigration(host, mdmInfo) { + notifs.NeedsMDMMigration = true + + // Set the host to refetch the "critical queries" quickly for some time, + // to improve ingestion time of the unenroll and make the host eligible to + // enroll into Fleet faster. + if host.RefetchCriticalQueriesUntil == nil { + refetchUntil := svc.clock.Now().Add(fleet.RefetchMDMUnenrollCriticalQueryDuration) + host.RefetchCriticalQueriesUntil = &refetchUntil + if err := svc.ds.UpdateHostRefetchCriticalQueriesUntil(ctx, host.ID, &refetchUntil); err != nil { + return fleet.OrbitConfig{}, err + } + } } } if !appConfig.MDM.WindowsEnabledAndConfigured { diff --git a/server/service/osquery.go b/server/service/osquery.go index 21555df372..ce88aa6250 100644 --- a/server/service/osquery.go +++ b/server/service/osquery.go @@ -691,7 +691,8 @@ const alwaysTrueQuery = "SELECT 1" // list of detail queries that are returned when only the critical queries // should be returned (due to RefetchCriticalQueriesUntil timestamp being set). var criticalDetailQueries = map[string]bool{ - "mdm": true, + "mdm": true, + "mdm_windows": true, } // detailQueriesForHost returns the map of detail+additional queries that should be executed by diff --git a/server/service/osquery_test.go b/server/service/osquery_test.go index 5be2974a12..d0f37e1e90 100644 --- a/server/service/osquery_test.go +++ b/server/service/osquery_test.go @@ -179,7 +179,7 @@ func TestGetClientConfig(t *testing.T) { // Check scheduled queries are loaded properly conf, err = svc.GetClientConfig(ctx3) require.NoError(t, err) - assert.JSONEq(t, `{ + assert.JSONEq(t, `{ "pack_by_label": { "queries":{ "time":{"query":"select * from time","interval":30,"removed":false} @@ -208,7 +208,7 @@ func TestGetClientConfig(t *testing.T) { "version": "" } } - } + } }`, string(conf["packs"].(json.RawMessage)), ) @@ -1165,8 +1165,12 @@ func TestHostDetailQueries(t *testing.T) { host.RefetchCriticalQueriesUntil = ptr.Time(mockClock.Now().Add(1 * time.Minute)) queries, discovery, err = svc.detailQueriesForHost(ctx, &host) require.NoError(t, err) - require.Equal(t, len(criticalDetailQueries), len(queries), distQueriesMapKeys(queries)) + // host is darwin so it gets only the darwin critical query + require.Equal(t, 1, len(queries), distQueriesMapKeys(queries)) for name := range criticalDetailQueries { + if strings.HasSuffix(name, "_windows") { + continue + } assert.Contains(t, queries, hostDetailQueryPrefix+name) } verifyDiscovery(t, queries, discovery) diff --git a/server/service/osquery_utils/queries.go b/server/service/osquery_utils/queries.go index da62dfef17..8f7600c16c 100644 --- a/server/service/osquery_utils/queries.go +++ b/server/service/osquery_utils/queries.go @@ -1885,6 +1885,11 @@ func directIngestMDMWindows(ctx context.Context, logger log.Logger, host *fleet. return nil } + if host.RefetchCriticalQueriesUntil != nil { + level.Debug(logger).Log("msg", "ingesting Windows mdm data during refetch critical queries window", "host_id", host.ID, + "data", fmt.Sprintf("%+v", rows)) + } + data := rows[0] var enrolled bool var automatic bool @@ -1900,13 +1905,20 @@ func directIngestMDMWindows(ctx context.Context, logger log.Logger, host *fleet. } isServer := strings.Contains(strings.ToLower(data["installation_type"]), "server") + mdmSolutionName := deduceMDMNameWindows(data) + if !enrolled && mdmSolutionName != fleet.WellKnownMDMFleet && host.RefetchCriticalQueriesUntil != nil { + // the host was unenrolled from a non-Fleet MDM solution, and the refetch + // critical queries timestamp was set, so clear it. + host.RefetchCriticalQueriesUntil = nil + } + return ds.SetOrUpdateMDMData(ctx, host.ID, isServer, enrolled, serverURL, automatic, - deduceMDMNameWindows(data), + mdmSolutionName, "", ) } diff --git a/tools/mdm/windows/poc-mdm-server/README.md b/tools/mdm/windows/poc-mdm-server/README.md index 74760b2de2..eebc49061a 100644 --- a/tools/mdm/windows/poc-mdm-server/README.md +++ b/tools/mdm/windows/poc-mdm-server/README.md @@ -23,6 +23,17 @@ This code is MIT licensed and it was forked from [here](https://github.com/oscar ## Usage On the server side, you just need to run the project using the already provided cert and keys. The certificate is in `.pfx` file format, so you need to extract the certificate and key first, see https://stackoverflow.com/a/59120388/1094941. +The "Import password" is "testpassword", and the names of the output files matter, on Linux something like this works (assuming you are in the certs/ directory): + +``` +# for the cert +$ openssl pkcs12 -in dev_cert_mdmwindows_com.pfx -clcerts -nokeys -out dev_cert_mdmwindows_com_cert.pem + +# for the key +$ openssl pkcs12 -in dev_cert_mdmwindows_com.pfx -out dev_cert_mdmwindows_com.key -nocerts -nodes +``` + +Note that an asn1 error might occur when running the server, if that's the case you need to patch your local Go toolchain by running `$ go run ./patch/patch.go` (`GOROOT` env var must be set to point to your `go env GOROOT` directory). It may require `sudo` depending on where your `go` installation is (due to https://github.com/golang/go/issues/14017). Next go to the project folder and run. @@ -30,7 +41,9 @@ Next go to the project folder and run. go run . ``` -On the Windows client side, you need to import a custom CA certificate to the certificate store, and populate the `hosts` file before running the Windows Enrollment. The certificate to import is on the certs directory and it is called `dev_cert_mdmwindows_com.pfx`. You need to copy this certificate to the client machine and run the powershell command below. This is required because the project uses a local dev https endpoint. +Note that the server binds to the standard and usually firewall-protected `443` port, so you may need to configure your firewall to allow connections to it for the duration of your test. + +On the Windows client side, you need to import the custom CA certificate to the certificate store, and populate the `hosts` file before running the Windows Enrollment. The certificate to import is on the certs directory and it is called `dev_cert_mdmwindows_com.pfx`. You need to copy this certificate to the client machine and run the powershell command below (in the console, not in a powershell terminal). This is required because the project uses a local dev https endpoint. 1) Import certificate to Trusted CAs repository (be sure to update the path to the pfx certificate) @@ -42,6 +55,8 @@ On the Windows client side, you need to import a custom CA certificate to the ce echo autodiscovery.mdmwindows.com >> %SystemRoot%\System32\drivers\etc\hosts echo enterpriseenrollment.mdmwindows.com >> %SystemRoot%\System32\drivers\etc\hosts +To enroll the device into this MDM server, go to `Settings > Accounts > Access work or school` and click the connect button, enter the email provided to the server when you ran `go run .` (default: `demo@mdmwindows.com`) and it should automatically detect the server and proceed with enrollment. This is why the server must run on port `:443`, because it uses automatic discovery and will not attempt a custom port. + ## Protocol Details Below is the raw https exchange of the MS-MDE and MS-MDM protocols when run using the -verbose mode: From f09d6fd7976dfe6bb6fb30f5c445be7afdfa77fa Mon Sep 17 00:00:00 2001 From: Brock Walters <153771548+nonpunctual@users.noreply.github.com> Date: Mon, 2 Dec 2024 11:53:10 -0500 Subject: [PATCH 69/92] Update rest-api.md (#24021) Added note to get Setup Experience section warning against setting custom URL values in custom profile. This was tested by CSA & customer-starchik. --------- Co-authored-by: Rachael Shaw --- docs/REST API/rest-api.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/REST API/rest-api.md b/docs/REST API/rest-api.md index 0f55c9c135..0e25be5561 100644 --- a/docs/REST API/rest-api.md +++ b/docs/REST API/rest-api.md @@ -5779,6 +5779,8 @@ Sets the custom MDM setup enrollment profile for a team or no team. } ``` +> NOTE: The `ConfigurationWebURL` and `URL` values in the custom MDM setup enrollment profile are automatically populated. Attempting to populate them with custom values may generate server response errors. + ### Get custom MDM setup enrollment profile _Available in Fleet Premium_ From 1677a0ae29862f9fc5f9ddab42739bd22151d306 Mon Sep 17 00:00:00 2001 From: Martin Angers Date: Mon, 2 Dec 2024 12:24:28 -0500 Subject: [PATCH 70/92] Windows Migration: leave checkbox disabled if non premium (#24272) --- .../cards/MdmSettings/WindowsMdmPage/WindowsMdmPage.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/WindowsMdmPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/WindowsMdmPage.tsx index e7b5b814a4..27dbdddc15 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/WindowsMdmPage.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/WindowsMdmPage.tsx @@ -72,7 +72,7 @@ interface IWindowsMdmPageProps { } const WindowsMdmPage = ({ router }: IWindowsMdmPageProps) => { - const { config } = useContext(AppContext); + const { config, isPremiumTier } = useContext(AppContext); const [mdmOn, setMdmOn] = useState( config?.mdm?.windows_enabled_and_configured ?? false @@ -124,9 +124,12 @@ const WindowsMdmPage = ({ router }: IWindowsMdmPageProps) => { />

{descriptionText}

Automatically migrate hosts connected to another MDM solution From e8ae793f9b7d114693051dd7b8d5238f9ed4bdd7 Mon Sep 17 00:00:00 2001 From: Sam Pfluger <108141731+Sampfluger88@users.noreply.github.com> Date: Mon, 2 Dec 2024 12:30:58 -0600 Subject: [PATCH 71/92] Update responsibility (#24276) --- handbook/digital-experience/README.md | 8 +++++--- .../digital-experience/digital-experience.rituals.yml | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/handbook/digital-experience/README.md b/handbook/digital-experience/README.md index d76516ca43..bdcd9da292 100644 --- a/handbook/digital-experience/README.md +++ b/handbook/digital-experience/README.md @@ -673,7 +673,9 @@ It's not enough to just "delete" a recording of a meeting in Gong. Instead, use ### Communicate Fleet's potential energy to stakeholders On the first business day of every month, the Head of Digital Experience will send an update to the stakeholders of Fleet using the following steps: -1. Copy the following template into an outgoing email with the subject line: "[Investor update] Fleet, YYYY-MM". +1. Navigate to the "[🪴🌧️🦉 Investor updates](https://docs.google.com/spreadsheets/d/10T7Q9iuHA4vpfV7qZCm6oMd5U1bLftBSobYD0RR8RkM/edit?gid=0#gid=0)" spreadsheet and confirm the data in each column matches the header of that column (e.g. the "Headcount" column actually has headcount values in it). Do this by confirming the "Remote column" value corresponds to the correct column "letter" in the "Weekly updates" tab of the "[📈 OKRs (quarterly goals) + KPIs (everyday metrics)](https://docs.google.com/spreadsheets/d/1Hso0LxqwrRVINCyW_n436bNHmoqhoLhC8bcbvLPOs9A/edit?gid=0#gid=0)" spreadsheet. +2. Confirm KPI's are up-to-date. If any KPI's aren't completed, at mention the e-group member responsible and ask that the KPI's be completed ASAP in order to send the investor update. +3. Copy the following template into an outgoing email with the subject line: "[Investor update] Fleet, YYYY-MM". ``` Hi investors and friends, @@ -687,8 +689,8 @@ Mike and the Fleet team ``` -2. Address the email to the executive team's Gmail. -3. Using the [🌧️🦉 Investors + advisors](https://docs.google.com/spreadsheets/d/15knBE2-PrQ1Ad-QcIk0mxCN-xFsATKK9hcifqrm0qFQ/edit#gid=1068113636) spreadsheet, bcc the correct individuals and send the email. +4. Address the email to the executive team's Gmail. +5. Using the [🌧️🦉 Investors + advisors](https://docs.google.com/spreadsheets/d/15knBE2-PrQ1Ad-QcIk0mxCN-xFsATKK9hcifqrm0qFQ/edit#gid=1068113636) spreadsheet, bcc the correct individuals and send the email. ### Schedule press release diff --git a/handbook/digital-experience/digital-experience.rituals.yml b/handbook/digital-experience/digital-experience.rituals.yml index d7f42066ca..9e33fe31b9 100644 --- a/handbook/digital-experience/digital-experience.rituals.yml +++ b/handbook/digital-experience/digital-experience.rituals.yml @@ -183,7 +183,7 @@ 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" + dri: "SFriendLee" autoIssue: labels: [ "#g-digital-experience" ] repo: "confidential" From 4b5de2646a53bea06f75f06462fa20c9dbf6c660 Mon Sep 17 00:00:00 2001 From: Luke Heath Date: Mon, 2 Dec 2024 12:35:05 -0600 Subject: [PATCH 72/92] Remove fleetctl check from release script (#24275) --- tools/release/publish_release.sh | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tools/release/publish_release.sh b/tools/release/publish_release.sh index cea7e6fccb..3757432b47 100755 --- a/tools/release/publish_release.sh +++ b/tools/release/publish_release.sh @@ -626,15 +626,6 @@ fi start_ver_tag=fleet-$start_version -# Check if there are updates to fleetctl dependencies (only when doing security updates to base images). -if [[ $(git diff $start_ver_tag ./tools/wix-docker ./tools/bomutils-docker) ]]; then - echo "⚠️ Changes in fleetctl dependencies detected, please run the following before continuing the release:" - echo "1. git tag fleetctl-docker-deps-$next_ver && git push origin fleetctl-docker-deps-$next_ver" - echo "2. Wait for the triggered https://github.com/fleetdm/fleet/actions/workflows/release-fleetctl-docker-deps.yaml build to finish." - echo "3. Smoke test the pushed images by manually running the following action: https://github.com/fleetdm/fleet/actions/workflows/test-packaging.yml" - exit 1 -fi - if [[ "$minor" == "true" ]]; then echo "Minor release from $start_version to $next_ver" # For scheduled minor releases, we want to branch off of main From 4d732113e0ccf3a4e8f0628c840ff67915511b5d Mon Sep 17 00:00:00 2001 From: Martin Angers Date: Mon, 2 Dec 2024 15:30:51 -0500 Subject: [PATCH 73/92] Windows Migration: support the new activities in the UI (#24279) --- frontend/interfaces/activity.ts | 2 ++ .../ActivityItem/ActivityItem.tsx | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/frontend/interfaces/activity.ts b/frontend/interfaces/activity.ts index d6fcafc8c7..14618ad9e2 100644 --- a/frontend/interfaces/activity.ts +++ b/frontend/interfaces/activity.ts @@ -69,6 +69,8 @@ export enum ActivityType { TransferredHosts = "transferred_hosts", EnabledWindowsMdm = "enabled_windows_mdm", DisabledWindowsMdm = "disabled_windows_mdm", + EnabledWindowsMdmMigration = "enabled_windows_mdm_migration", + DisabledWindowsMdmMigration = "disabled_windows_mdm_migration", RanScript = "ran_script", AddedScript = "added_script", DeletedScript = "deleted_script", diff --git a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx index b162a22fcc..5e724059bf 100644 --- a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx +++ b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx @@ -37,6 +37,8 @@ const PREMIUM_ACTIVITIES = new Set([ "enabled_macos_setup_end_user_auth", "disabled_macos_setup_end_user_auth", "tranferred_hosts", + "enabled_windows_mdm_migration", + "disabled_windows_mdm_migration", ]); const getProfileMessageSuffix = ( @@ -663,6 +665,24 @@ const TAGGED_TEMPLATES = { disabledWindowsMdm: () => { return <> told Fleet to turn off Windows MDM features.; }, + enabledWindowsMdmMigration: () => { + return ( + <> + {" "} + told Fleet to automatically migrate Windows hosts connected to another + MDM solution. + + ); + }, + disabledWindowsMdmMigration: () => { + return ( + <> + {" "} + told Fleet to stop migrating Windows hosts connected to another MDM + solution. + + ); + }, // TODO: Combine ranScript template with host details page templates // frontend/pages/hosts/details/cards/Activity/PastActivity/PastActivity.tsx and // frontend/pages/hosts/details/cards/Activity/UpcomingActivity/UpcomingActivity.tsx @@ -1262,6 +1282,12 @@ const getDetail = ( case ActivityType.DisabledWindowsMdm: { return TAGGED_TEMPLATES.disabledWindowsMdm(); } + case ActivityType.EnabledWindowsMdmMigration: { + return TAGGED_TEMPLATES.enabledWindowsMdmMigration(); + } + case ActivityType.DisabledWindowsMdmMigration: { + return TAGGED_TEMPLATES.disabledWindowsMdmMigration(); + } case ActivityType.RanScript: { return TAGGED_TEMPLATES.ranScript(activity, onDetailsClick); } From 6d468cada4d8bae0e8b91fb1380c4178d801005f Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 2 Dec 2024 14:46:07 -0600 Subject: [PATCH 74/92] Msp dashboard: Update error handling in software related actions (#24283) Changes: - Updated the edit-software endpoint to: - log a detailed warning when a request to get a stream of an existing software installer returns a non-200 repsonse - Use the modify package API endpoint when a software installer is replaced. - include the nested response from the Fleet instance in error messages - Updated the delete-software endpoint to return a 'softwareDeletionFailed` exit when a specified software installer is included in the macOS setup experience and cannot be deleted via API requests. - Updated the software page to show an error message with a link to the controls page on the connected Fleet instance if the delete-software endpoint returns a `softwareDeletionFailed` exit --- .../controllers/software/delete-software.js | 10 +++++++++- .../api/controllers/software/edit-software.js | 18 ++++++------------ .../controllers/software/upload-software.js | 2 +- .../api/controllers/software/view-software.js | 2 +- .../views/pages/software/software.ejs | 3 ++- 5 files changed, 19 insertions(+), 16 deletions(-) diff --git a/ee/bulk-operations-dashboard/api/controllers/software/delete-software.js b/ee/bulk-operations-dashboard/api/controllers/software/delete-software.js index c60f71e7ec..539d8c4676 100644 --- a/ee/bulk-operations-dashboard/api/controllers/software/delete-software.js +++ b/ee/bulk-operations-dashboard/api/controllers/software/delete-software.js @@ -16,7 +16,10 @@ module.exports = { exits: { - + softwareDeletionFailed: { + description: 'The specified software could not be deleted from the Fleet instance.', + statusCode: 409, + } }, @@ -34,6 +37,11 @@ module.exports = { headers: { Authorization: `Bearer ${sails.config.custom.fleetApiToken}`, } + }) + .intercept({raw:{statusCode: 409}}, (error)=>{ + // If the Fleet instance's returns a 409 response, then the software is configured to be installed as + // part of the macOS setup experience, and must be removed before it can be deleted via API requests. + return {softwareDeletionFailed: error}; }); } } diff --git a/ee/bulk-operations-dashboard/api/controllers/software/edit-software.js b/ee/bulk-operations-dashboard/api/controllers/software/edit-software.js index abe14930ea..983387100d 100644 --- a/ee/bulk-operations-dashboard/api/controllers/software/edit-software.js +++ b/ee/bulk-operations-dashboard/api/controllers/software/edit-software.js @@ -84,6 +84,9 @@ module.exports = { headers: { Authorization: `Bearer ${sails.config.custom.fleetApiToken}`, } + }) + .intercept('non200Response', (error)=>{ + return new Error(`When attempting to transfer the installer for ${software.name} to a new team on the Fleet instance, the Fleet isntance returned a non-200 response when a request was sent to get a download stream of the installer on team_id ${teamIdToGetInstallerFrom}. Full Error: ${require('util').inspect(error, {depth: 1})}`); }); let tempUploadedSoftware = await sails.uploadOne(softwareStream, {bucket: sails.config.uploads.bucketWithPostfix}); softwareFd = tempUploadedSoftware.fd; @@ -173,7 +176,7 @@ module.exports = { await sails.rm(sails.config.uploads.prefixForFileDeletion+softwareFd); } // Log a warning containing an error - sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 0})}`); + sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, Full error: ${require('util').inspect(error, {depth: 2})}`); return {'softwareUploadFailed': error}; }); // console.timeEnd(`transfering ${software.name} to fleet instance for team id ${team}`); @@ -183,15 +186,6 @@ module.exports = { // If a new installer package was provided, send patch requests to update the installer package on teams that it is already deployed to. await sails.helpers.flow.simultaneouslyForEach(unchangedTeamIds, async (teamApid)=>{ // console.log(`Adding new version of ${softwareName} to teamId ${teamApid}`); - await sails.helpers.http.sendHttpRequest.with({ - method: 'DELETE', - baseUrl: sails.config.custom.fleetBaseUrl, - url: `/api/v1/fleet/software/titles/${software.fleetApid}/available_for_install?team_id=${teamApid}`, - headers: { - Authorization: `Bearer ${sails.config.custom.fleetApiToken}`, - } - }); - // console.log(`transfering the changed installer ${software.name} to fleet instance for team id ${teamApid}`); // console.time(`transfering ${software.name} to fleet instance for team id ${teamApid}`); await sails.cp(softwareFd, {bucket: sails.config.uploads.bucketWithPostfix}, { @@ -220,7 +214,7 @@ module.exports = { contentType: 'application/octet-stream' }); (async ()=>{ - await axios.post(`${sails.config.custom.fleetBaseUrl}/api/v1/fleet/software/package`, form, { + await axios.patch(`${sails.config.custom.fleetBaseUrl}/api/v1/fleet/software/titles/${software.fleetApid}/package`, form, { headers: { Authorization: `Bearer ${sails.config.custom.fleetApiToken}`, ...form.getHeaders() @@ -248,7 +242,7 @@ module.exports = { await sails.rm(sails.config.uploads.prefixForFileDeletion+softwareFd); } // Log a warning containing an error - sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 0})}`); + sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 2})}`); return {'softwareUploadFailed': error}; }); // console.timeEnd(`transfering ${software.name} to fleet instance for team id ${teamApid}`); diff --git a/ee/bulk-operations-dashboard/api/controllers/software/upload-software.js b/ee/bulk-operations-dashboard/api/controllers/software/upload-software.js index 29b4817b3b..8a1ea99ed5 100644 --- a/ee/bulk-operations-dashboard/api/controllers/software/upload-software.js +++ b/ee/bulk-operations-dashboard/api/controllers/software/upload-software.js @@ -106,7 +106,7 @@ module.exports = { }) .intercept({name: 'AxiosError'}, async (error)=>{ await sails.rm(sails.config.uploads.prefixForFileDeletion+uploadedSoftware.fd); - sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 0})}`); + sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 2})}`); return {'softwareUploadFailed': error}; }); } diff --git a/ee/bulk-operations-dashboard/api/controllers/software/view-software.js b/ee/bulk-operations-dashboard/api/controllers/software/view-software.js index 8a8a8ccb5e..6b06dabeb2 100644 --- a/ee/bulk-operations-dashboard/api/controllers/software/view-software.js +++ b/ee/bulk-operations-dashboard/api/controllers/software/view-software.js @@ -104,7 +104,7 @@ module.exports = { let undeployedSoftware = await UndeployedSoftware.find(); allSoftware = allSoftware.concat(undeployedSoftware); - return {software: allSoftware, teams}; + return {software: allSoftware, teams, fleetBaseUrl: sails.config.custom.fleetBaseUrl}; } diff --git a/ee/bulk-operations-dashboard/views/pages/software/software.ejs b/ee/bulk-operations-dashboard/views/pages/software/software.ejs index 0467e7f606..eff9c8e989 100644 --- a/ee/bulk-operations-dashboard/views/pages/software/software.ejs +++ b/ee/bulk-operations-dashboard/views/pages/software/software.ejs @@ -163,7 +163,8 @@

{{formData.software.name}} will be removed from your library.

- + This software has been configured to be installed as part of the macOS setup experience and cannot be deleted. Please remove this software from all teams the "Setup experience" tab of the Controls page on your Fleet instance and try again +
Cancel Delete From 54455e9958b26c670f82fd1827d6fb8e2e730a8d Mon Sep 17 00:00:00 2001 From: RachelElysia <71795832+RachelElysia@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:18:42 -0500 Subject: [PATCH 75/92] Fleet UI: Ability to clear webhook address and still disable policy automation (#24163) --- changes/24093-clear-policy-automation | 1 + .../OtherWorkflowsModal.tsx | 33 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) create mode 100644 changes/24093-clear-policy-automation diff --git a/changes/24093-clear-policy-automation b/changes/24093-clear-policy-automation new file mode 100644 index 0000000000..4d77791615 --- /dev/null +++ b/changes/24093-clear-policy-automation @@ -0,0 +1 @@ +- Fleet UI: Fix ability to clear policy automation that empties webhook URL diff --git a/frontend/pages/policies/ManagePoliciesPage/components/OtherWorkflowsModal/OtherWorkflowsModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/OtherWorkflowsModal/OtherWorkflowsModal.tsx index 859b45fff5..3ddbec66b4 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/OtherWorkflowsModal/OtherWorkflowsModal.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/OtherWorkflowsModal/OtherWorkflowsModal.tsx @@ -185,24 +185,23 @@ const OtherWorkflowsModal = ({ const newErrors = { ...errors }; - if ( - isPolicyAutomationsEnabled && - newPolicyIds.length && - !isWebhookEnabled && - !selectedIntegration - ) { - newErrors.integration = "Please enable at least one integration:"; - } else { - delete newErrors.integration; - } - - if (isWebhookEnabled) { - if (!destinationUrl) { - newErrors.url = "Please add a destination URL"; - } else if (!validUrl({ url: destinationUrl })) { - newErrors.url = `${destinationUrl} is not a valid URL`; + if (isPolicyAutomationsEnabled) { + // Ticket workflow validation + if (newPolicyIds.length && !isWebhookEnabled && !selectedIntegration) { + newErrors.integration = "Please enable at least one integration:"; } else { - delete newErrors.url; + delete newErrors.integration; + } + + // Webhook workflow validation + if (isWebhookEnabled) { + if (!destinationUrl) { + newErrors.url = "Please add a destination URL"; + } else if (!validUrl({ url: destinationUrl })) { + newErrors.url = `${destinationUrl} is not a valid URL`; + } else { + delete newErrors.url; + } } } From 47a43850f09ad2b3ebd8afa8a6bfc75a9ecba0ef Mon Sep 17 00:00:00 2001 From: Tim Lee Date: Mon, 2 Dec 2024 16:33:03 -0500 Subject: [PATCH 76/92] Skip python vulnerabilities test (#24287) --- server/vulnerabilities/nvd/cve_test.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/server/vulnerabilities/nvd/cve_test.go b/server/vulnerabilities/nvd/cve_test.go index 2b2d796198..1169b93b96 100644 --- a/server/vulnerabilities/nvd/cve_test.go +++ b/server/vulnerabilities/nvd/cve_test.go @@ -343,12 +343,14 @@ func TestTranslateCPEToCVE(t *testing.T) { excludedCVEs: []string{"CVE-2024-4030"}, continuesToUpdate: true, }, - "cpe:2.3:a:python:python:3.9.6:*:*:*:*:windows:*:*": { - includedCVEs: []cve{ - {ID: "CVE-2024-4030", resolvedInVersion: "3.9.20"}, - }, - continuesToUpdate: true, - }, + // Skipping test while troubleshooting https://github.com/fleetdm/fleet/issues/24286 + // + // "cpe:2.3:a:python:python:3.9.6:*:*:*:*:windows:*:*": { + // includedCVEs: []cve{ + // {ID: "CVE-2024-4030", resolvedInVersion: "3.9.20"}, + // }, + // continuesToUpdate: true, + // }, // Tests the expandCPEAliases rule for virtualbox on macOS "cpe:2.3:a:oracle:virtualbox:7.0.6:*:*:*:*:macos:*:*": { includedCVEs: []cve{ From 5ff65462ca1384e2bcc086a0c224ccc057ed0cc9 Mon Sep 17 00:00:00 2001 From: Luke Heath Date: Mon, 2 Dec 2024 16:24:20 -0600 Subject: [PATCH 77/92] Remove kubequery as no longer usable reference (#24291) --- infrastructure/kubequery/.fossa.yml | 18 - infrastructure/kubequery/.gitignore | 14 - infrastructure/kubequery/CHANGELOG.md | 158 - infrastructure/kubequery/CODE_OF_CONDUCT.md | 133 - infrastructure/kubequery/CONTRIBUTING.md | 51 - infrastructure/kubequery/Dockerfile | 45 - infrastructure/kubequery/LICENSE | 9 - infrastructure/kubequery/LICENSE-Apache-2.0 | 202 - infrastructure/kubequery/LICENSE-GPL-2.0 | 340 - infrastructure/kubequery/Makefile | 55 - infrastructure/kubequery/README.md | 180 - infrastructure/kubequery/SECURITY.md | 15 - infrastructure/kubequery/bin/entrypoint.sh | 28 - infrastructure/kubequery/bin/kubequeryi | 13 - .../kubequery/charts/kubequery/.helmignore | 34 - .../kubequery/charts/kubequery/Chart.yaml | 16 - .../charts/kubequery/templates/Chart.yaml | 0 .../charts/kubequery/templates/_helpers.tpl | 55 - .../kubequery/templates/clusterrole.yaml | 18 - .../templates/clusterrolebinding.yaml | 22 - .../charts/kubequery/templates/configmap.yaml | 20 - .../kubequery/templates/deployment.yaml | 64 - .../charts/kubequery/templates/namespace.yaml | 14 - .../kubequery/templates/serviceaccount.yaml | 15 - .../kubequery/charts/kubequery/values.yaml | 247 - .../kubequery/cmd/genschema/main.go | 30 - .../kubequery/cmd/gentables/main.go | 39 - .../kubequery/cmd/kubequery/main.go | 98 - infrastructure/kubequery/cmd/uuidgen/main.go | 33 - infrastructure/kubequery/docs/deployment.svg | 3 - infrastructure/kubequery/docs/kubequery.png | Bin 6443 -> 0 bytes infrastructure/kubequery/docs/schema.md | 2083 ----- infrastructure/kubequery/docs/tables.json | 7970 ----------------- infrastructure/kubequery/etc/kubequery.conf | 206 - infrastructure/kubequery/etc/kubequery.flags | 2 - .../kubequery/etc/kubequery.flags.tls | 9 - infrastructure/kubequery/go.mod | 58 - infrastructure/kubequery/go.sum | 673 -- infrastructure/kubequery/integration/index.js | 94 - .../admissionregistration/mutating_webhook.go | 62 - .../mutating_webhook_test.go | 56 - .../validating_webhook.go | 62 - .../validating_webhook_test.go | 56 - .../kubequery/internal/k8s/apps/daemon_set.go | 175 - .../internal/k8s/apps/daemon_set_test.go | 324 - .../kubequery/internal/k8s/apps/deployment.go | 181 - .../internal/k8s/apps/deployment_test.go | 53 - .../kubequery/internal/k8s/apps/init_test.go | 47 - .../internal/k8s/apps/replica_set.go | 173 - .../internal/k8s/apps/replica_set_test.go | 84 - .../internal/k8s/apps/stateful_set.go | 182 - .../internal/k8s/apps/stateful_set_test.go | 153 - .../k8s/apps/testdata/daemon_set_test.json | 910 -- .../k8s/apps/testdata/deployment_test.json | 739 -- .../k8s/apps/testdata/replica_set_test.json | 235 - .../k8s/apps/testdata/stateful_set_test.json | 403 - .../autoscaling/horizontal_pod_autoscaler.go | 59 - .../horizontal_pod_autoscaler_test.go | 73 - .../kubequery/internal/k8s/batch/cron_job.go | 85 - .../internal/k8s/batch/cron_job_test.go | 93 - .../kubequery/internal/k8s/batch/job.go | 73 - .../kubequery/internal/k8s/batch/job_test.go | 88 - .../kubequery/internal/k8s/client.go | 125 - .../kubequery/internal/k8s/client_test.go | 33 - .../kubequery/internal/k8s/common.go | 694 -- .../kubequery/internal/k8s/common_test.go | 428 - .../internal/k8s/core/component_status.go | 64 - .../k8s/core/component_status_test.go | 46 - .../kubequery/internal/k8s/core/config_map.go | 56 - .../internal/k8s/core/config_map_test.go | 32 - .../internal/k8s/core/endpoint_subset.go | 59 - .../internal/k8s/core/endpoint_subset_test.go | 36 - .../kubequery/internal/k8s/core/init_test.go | 81 - .../internal/k8s/core/limit_range.go | 59 - .../internal/k8s/core/limit_range_test.go | 39 - .../kubequery/internal/k8s/core/namespace.go | 57 - .../internal/k8s/core/namespace_test.go | 78 - .../kubequery/internal/k8s/core/node.go | 59 - .../kubequery/internal/k8s/core/node_test.go | 41 - .../internal/k8s/core/persistent_volume.go | 330 - .../k8s/core/persistent_volume_claim.go | 63 - .../kubequery/internal/k8s/core/pod.go | 233 - .../internal/k8s/core/pod_template.go | 169 - .../kubequery/internal/k8s/core/pod_test.go | 118 - .../internal/k8s/core/resource_quota.go | 61 - .../kubequery/internal/k8s/core/secret.go | 59 - .../internal/k8s/core/secret_test.go | 34 - .../kubequery/internal/k8s/core/service.go | 59 - .../internal/k8s/core/service_account.go | 61 - .../internal/k8s/core/service_account_test.go | 35 - .../internal/k8s/core/service_test.go | 42 - .../core/testdata/component_status_test.json | 58 - .../k8s/core/testdata/config_map_test.json | 43 - .../core/testdata/endpoint_subset_test.json | 64 - .../k8s/core/testdata/namespaces_test.json | 275 - .../internal/k8s/core/testdata/node_test.json | 527 -- .../internal/k8s/core/testdata/pod_test.json | 344 - .../k8s/core/testdata/secret_test.json | 44 - .../core/testdata/service_account_test.json | 66 - .../k8s/core/testdata/services_test.json | 94 - .../internal/k8s/discovery/api_resource.go | 55 - .../k8s/discovery/api_resource_test.go | 30 - .../kubequery/internal/k8s/discovery/info.go | 49 - .../internal/k8s/discovery/info_test.go | 55 - .../kubequery/internal/k8s/event/watcher.go | 159 - .../internal/k8s/networking/ingress.go | 59 - .../internal/k8s/networking/ingress_class.go | 57 - .../k8s/networking/ingress_class_test.go | 33 - .../internal/k8s/networking/ingress_test.go | 35 - .../internal/k8s/networking/init_test.go | 45 - .../internal/k8s/networking/network_policy.go | 78 - .../k8s/networking/network_policy_test.go | 51 - .../testdata/ingress_class_test.json | 40 - .../k8s/networking/testdata/ingress_test.json | 83 - .../testdata/network_policy_test.json | 111 - .../internal/k8s/policy/init_test.go | 44 - .../k8s/policy/pod_disruption_budget.go | 59 - .../k8s/policy/pod_disruption_budget_test.go | 41 - .../k8s/policy/pod_security_policy.go | 57 - .../k8s/policy/pod_security_policy_test.go | 44 - .../testdata/pod_disruption_budget_test.json | 89 - .../testdata/pod_security_policy_test.json | 96 - .../k8s/rbac/cluster_role_binding_subject.go | 68 - .../rbac/cluster_role_binding_subject_test.go | 51 - .../k8s/rbac/cluster_role_policy_rule.go | 61 - .../k8s/rbac/cluster_role_policy_rule_test.go | 102 - .../kubequery/internal/k8s/rbac/init_test.go | 47 - .../internal/k8s/rbac/role_binding_subject.go | 66 - .../k8s/rbac/role_binding_subject_test.go | 39 - .../internal/k8s/rbac/role_policy_rule.go | 59 - .../k8s/rbac/role_policy_rule_test.go | 77 - .../cluster_role_binding_subject_test.json | 108 - .../cluster_role_policy_rule_test.json | 204 - .../testdata/role_binding_subject_test.json | 57 - .../rbac/testdata/role_policy_rule_test.json | 108 - .../internal/k8s/storage/csi_driver.go | 57 - .../internal/k8s/storage/csi_driver_test.go | 35 - .../internal/k8s/storage/csi_node_driver.go | 62 - .../k8s/storage/csi_node_driver_test.go | 24 - .../k8s/storage/csi_storage_capacity.go | 61 - .../internal/k8s/storage/init_test.go | 45 - .../internal/k8s/storage/storage_class.go | 70 - .../k8s/storage/storage_class_test.go | 36 - .../k8s/storage/testdata/csi_driver_test.json | 21 - .../testdata/csi_node_driver_test.json | 54 - .../storage/testdata/storage_class_test.json | 22 - .../internal/k8s/storage/volume_attachment.go | 59 - .../kubequery/internal/k8s/tables/tables.go | 111 - .../internal/k8s/tables/tables_test.go | 22 - .../kubequery/internal/k8s/utils.go | 181 - .../kubequery/internal/k8s/utils_test.go | 190 - infrastructure/kubequery/internal/tools.go | 7 - .../kubequery/kubequery-template.yaml | 123 - 153 files changed, 25793 deletions(-) delete mode 100644 infrastructure/kubequery/.fossa.yml delete mode 100644 infrastructure/kubequery/.gitignore delete mode 100644 infrastructure/kubequery/CHANGELOG.md delete mode 100644 infrastructure/kubequery/CODE_OF_CONDUCT.md delete mode 100644 infrastructure/kubequery/CONTRIBUTING.md delete mode 100644 infrastructure/kubequery/Dockerfile delete mode 100644 infrastructure/kubequery/LICENSE delete mode 100644 infrastructure/kubequery/LICENSE-Apache-2.0 delete mode 100644 infrastructure/kubequery/LICENSE-GPL-2.0 delete mode 100644 infrastructure/kubequery/Makefile delete mode 100644 infrastructure/kubequery/README.md delete mode 100644 infrastructure/kubequery/SECURITY.md delete mode 100755 infrastructure/kubequery/bin/entrypoint.sh delete mode 100755 infrastructure/kubequery/bin/kubequeryi delete mode 100644 infrastructure/kubequery/charts/kubequery/.helmignore delete mode 100644 infrastructure/kubequery/charts/kubequery/Chart.yaml delete mode 100644 infrastructure/kubequery/charts/kubequery/templates/Chart.yaml delete mode 100644 infrastructure/kubequery/charts/kubequery/templates/_helpers.tpl delete mode 100644 infrastructure/kubequery/charts/kubequery/templates/clusterrole.yaml delete mode 100644 infrastructure/kubequery/charts/kubequery/templates/clusterrolebinding.yaml delete mode 100644 infrastructure/kubequery/charts/kubequery/templates/configmap.yaml delete mode 100644 infrastructure/kubequery/charts/kubequery/templates/deployment.yaml delete mode 100644 infrastructure/kubequery/charts/kubequery/templates/namespace.yaml delete mode 100644 infrastructure/kubequery/charts/kubequery/templates/serviceaccount.yaml delete mode 100644 infrastructure/kubequery/charts/kubequery/values.yaml delete mode 100644 infrastructure/kubequery/cmd/genschema/main.go delete mode 100644 infrastructure/kubequery/cmd/gentables/main.go delete mode 100644 infrastructure/kubequery/cmd/kubequery/main.go delete mode 100644 infrastructure/kubequery/cmd/uuidgen/main.go delete mode 100644 infrastructure/kubequery/docs/deployment.svg delete mode 100644 infrastructure/kubequery/docs/kubequery.png delete mode 100644 infrastructure/kubequery/docs/schema.md delete mode 100644 infrastructure/kubequery/docs/tables.json delete mode 100644 infrastructure/kubequery/etc/kubequery.conf delete mode 100644 infrastructure/kubequery/etc/kubequery.flags delete mode 100644 infrastructure/kubequery/etc/kubequery.flags.tls delete mode 100644 infrastructure/kubequery/go.mod delete mode 100644 infrastructure/kubequery/go.sum delete mode 100644 infrastructure/kubequery/integration/index.js delete mode 100644 infrastructure/kubequery/internal/k8s/admissionregistration/mutating_webhook.go delete mode 100644 infrastructure/kubequery/internal/k8s/admissionregistration/mutating_webhook_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/admissionregistration/validating_webhook.go delete mode 100644 infrastructure/kubequery/internal/k8s/admissionregistration/validating_webhook_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/apps/daemon_set.go delete mode 100644 infrastructure/kubequery/internal/k8s/apps/daemon_set_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/apps/deployment.go delete mode 100644 infrastructure/kubequery/internal/k8s/apps/deployment_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/apps/init_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/apps/replica_set.go delete mode 100644 infrastructure/kubequery/internal/k8s/apps/replica_set_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/apps/stateful_set.go delete mode 100644 infrastructure/kubequery/internal/k8s/apps/stateful_set_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/apps/testdata/daemon_set_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/apps/testdata/deployment_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/apps/testdata/replica_set_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/apps/testdata/stateful_set_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/autoscaling/horizontal_pod_autoscaler.go delete mode 100644 infrastructure/kubequery/internal/k8s/autoscaling/horizontal_pod_autoscaler_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/batch/cron_job.go delete mode 100644 infrastructure/kubequery/internal/k8s/batch/cron_job_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/batch/job.go delete mode 100644 infrastructure/kubequery/internal/k8s/batch/job_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/client.go delete mode 100644 infrastructure/kubequery/internal/k8s/client_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/common.go delete mode 100644 infrastructure/kubequery/internal/k8s/common_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/component_status.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/component_status_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/config_map.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/config_map_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/endpoint_subset.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/endpoint_subset_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/init_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/limit_range.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/limit_range_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/namespace.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/namespace_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/node.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/node_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/persistent_volume.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/persistent_volume_claim.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/pod.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/pod_template.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/pod_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/resource_quota.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/secret.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/secret_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/service.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/service_account.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/service_account_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/service_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/core/testdata/component_status_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/core/testdata/config_map_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/core/testdata/endpoint_subset_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/core/testdata/namespaces_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/core/testdata/node_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/core/testdata/pod_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/core/testdata/secret_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/core/testdata/service_account_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/core/testdata/services_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/discovery/api_resource.go delete mode 100644 infrastructure/kubequery/internal/k8s/discovery/api_resource_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/discovery/info.go delete mode 100644 infrastructure/kubequery/internal/k8s/discovery/info_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/event/watcher.go delete mode 100644 infrastructure/kubequery/internal/k8s/networking/ingress.go delete mode 100644 infrastructure/kubequery/internal/k8s/networking/ingress_class.go delete mode 100644 infrastructure/kubequery/internal/k8s/networking/ingress_class_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/networking/ingress_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/networking/init_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/networking/network_policy.go delete mode 100644 infrastructure/kubequery/internal/k8s/networking/network_policy_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/networking/testdata/ingress_class_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/networking/testdata/ingress_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/networking/testdata/network_policy_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/policy/init_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/policy/pod_disruption_budget.go delete mode 100644 infrastructure/kubequery/internal/k8s/policy/pod_disruption_budget_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/policy/pod_security_policy.go delete mode 100644 infrastructure/kubequery/internal/k8s/policy/pod_security_policy_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/policy/testdata/pod_disruption_budget_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/policy/testdata/pod_security_policy_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/rbac/cluster_role_binding_subject.go delete mode 100644 infrastructure/kubequery/internal/k8s/rbac/cluster_role_binding_subject_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/rbac/cluster_role_policy_rule.go delete mode 100644 infrastructure/kubequery/internal/k8s/rbac/cluster_role_policy_rule_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/rbac/init_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/rbac/role_binding_subject.go delete mode 100644 infrastructure/kubequery/internal/k8s/rbac/role_binding_subject_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/rbac/role_policy_rule.go delete mode 100644 infrastructure/kubequery/internal/k8s/rbac/role_policy_rule_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/rbac/testdata/cluster_role_binding_subject_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/rbac/testdata/cluster_role_policy_rule_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/rbac/testdata/role_binding_subject_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/rbac/testdata/role_policy_rule_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/storage/csi_driver.go delete mode 100644 infrastructure/kubequery/internal/k8s/storage/csi_driver_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/storage/csi_node_driver.go delete mode 100644 infrastructure/kubequery/internal/k8s/storage/csi_node_driver_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/storage/csi_storage_capacity.go delete mode 100644 infrastructure/kubequery/internal/k8s/storage/init_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/storage/storage_class.go delete mode 100644 infrastructure/kubequery/internal/k8s/storage/storage_class_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/storage/testdata/csi_driver_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/storage/testdata/csi_node_driver_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/storage/testdata/storage_class_test.json delete mode 100644 infrastructure/kubequery/internal/k8s/storage/volume_attachment.go delete mode 100644 infrastructure/kubequery/internal/k8s/tables/tables.go delete mode 100644 infrastructure/kubequery/internal/k8s/tables/tables_test.go delete mode 100644 infrastructure/kubequery/internal/k8s/utils.go delete mode 100644 infrastructure/kubequery/internal/k8s/utils_test.go delete mode 100644 infrastructure/kubequery/internal/tools.go delete mode 100644 infrastructure/kubequery/kubequery-template.yaml diff --git a/infrastructure/kubequery/.fossa.yml b/infrastructure/kubequery/.fossa.yml deleted file mode 100644 index 841e42cc48..0000000000 --- a/infrastructure/kubequery/.fossa.yml +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2020-present, The kubequery authors -# -# This source code is licensed as defined by the LICENSE file found in the -# root directory of this source tree. -# -# SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - -version: 2 -cli: - server: https://app.fossa.com - fetcher: custom - project: git@github.com:Uptycs/kubequery.git -analyze: - modules: - - name: github.com/Uptycs/kubequery/cmd/kubequery - type: go - target: github.com/Uptycs/kubequery/cmd/kubequery - path: cmd/kubequery diff --git a/infrastructure/kubequery/.gitignore b/infrastructure/kubequery/.gitignore deleted file mode 100644 index 4077dd3103..0000000000 --- a/infrastructure/kubequery/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2020-present, The kubequery authors -# -# This source code is licensed as defined by the LICENSE file found in the -# root directory of this source tree. -# -# SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - -.vscode -vendor -kubequery.yaml -/bin/genschema -/bin/gentables -/bin/kubequery -/bin/uuidgen diff --git a/infrastructure/kubequery/CHANGELOG.md b/infrastructure/kubequery/CHANGELOG.md deleted file mode 100644 index b5ac95b995..0000000000 --- a/infrastructure/kubequery/CHANGELOG.md +++ /dev/null @@ -1,158 +0,0 @@ -# kubequery change log - - - -## [1.1.1](https://github.com/Uptycs/kubequery/releases/tag/1.1.1) - -[Git Commits](https://github.com/Uptycs/kubequery/compare/1.1.0...1.1.1) - -### New Features - -### Under the Hood improvements - -* Upgrade to basequery 5.0.2 -* Upgraded to Go 1.17 - -### Table Changes - -### Bug Fixes - -### Documentation - -### Build - -### Security Issues - -### Packs - - - -## [1.1.0](https://github.com/Uptycs/kubequery/releases/tag/1.1.0) - -[Git Commits](https://github.com/Uptycs/kubequery/compare/1.0.0...1.1.0) - -### New Features - -* Helm chart to install kubequery -* Support for Kubernetes 1.22 - -### Under the Hood improvements - -* Upgrade to basequery 4.9.0 -* Upgraded to client go version 0.22 - -### Table Changes - -* k8s 1.22 caused few table [schemas changes](https://github.com/Uptycs/kubequery/commit/a70e9a42f6f85ca1a0ebd23575590c73562fab83#diff-79f5d80ee02a931b2bf12fd018b6edeb447abd58e1fb85ae155ae932ec29ad9d): - * kubernetes_stateful_sets - * kubernetes_jobs - * kubernetes_persistent_volume_claims - * kubernetes_services - -### Bug Fixes - -* Check container status before iterating over contents. [Issue 16](https://github.com/Uptycs/kubequery/issues/16) - -### Documentation - -* Added helm related details in README.md - -### Build - -### Security Issues - -### Packs - - - -## [1.0.0](https://github.com/Uptycs/kubequery/releases/tag/1.0.0) - -[Git Commits](https://github.com/Uptycs/kubequery/compare/0.3.0...1.0.0) - -### New Features - -* New `kubequeryi` command line to easily invoke shell -* Easy to use with [query-tls](https://github.com/Uptycs/query-tls) - -### Under the Hood improvements - -* Upgrade to basequery 4.8.0 -* Switch to light weight busybox docker image -* Simple NodeJS based integration test - -### Table Changes - -* Added `cluster_name` and `cluster_uid` to tables missing those columns -* Break up `resources` in `*_containers` tables to `resource_limits` and `resource_requests` -* Added new table `kubernetes_component_statuses` -* Removed table `kubernetes_storage_capacities` - -### Bug Fixes - -### Documentation - -### Build - -* Upgrade to Go 1.16 - -### Security Issues - -### Packs - -* Added default query pack for all kubernetes tables - - - -## [0.3.0](https://github.com/Uptycs/kubequery/releases/tag/0.3.0) - -[Git Commits](https://github.com/Uptycs/kubequery/compare/0.2.0...0.3.0) - -### New Features - -### Under the Hood improvements - -* Upgrade to basequery 4.7.0 - -### Table Changes - -### Bug Fixes - -### Documentation - -* Validate the installation was successful [PR-12](https://github.com/Uptycs/kubequery/pull/12) - -### Build - -### Security Issues - -### Packs - - - -## [0.2.0](https://github.com/Uptycs/kubequery/releases/tag/0.2.0) - -[Git Commits](https://github.com/Uptycs/kubequery/compare/0.1.0...0.2.0) - -### New Features - -* Added `kubernetes_events` table. - -### Under the Hood improvements - -* Switch to [basequery](https://github.com/Uptycs/basequery). This is stripped download version of Osquery with support for extension events and other features. - -### Table Changes - -* kubernetes_events - -### Bug Fixes - -### Documentation - -* Validate the installation was successful [PR-12](https://github.com/Uptycs/kubequery/pull/12) - -### Build - -### Security Issues - -### Packs diff --git a/infrastructure/kubequery/CODE_OF_CONDUCT.md b/infrastructure/kubequery/CODE_OF_CONDUCT.md deleted file mode 100644 index acdbb0b4d6..0000000000 --- a/infrastructure/kubequery/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,133 +0,0 @@ - -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -[kubequery@uptycs.com](mailto:kubequery@uptycs.com). -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. - -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. - -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available -at [https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations diff --git a/infrastructure/kubequery/CONTRIBUTING.md b/infrastructure/kubequery/CONTRIBUTING.md deleted file mode 100644 index 6a43e8d398..0000000000 --- a/infrastructure/kubequery/CONTRIBUTING.md +++ /dev/null @@ -1,51 +0,0 @@ -# Contributing to kubequery - -Welcome and thank you for considering contributing to kubequery open source. - -Reading and following these guidelines will help us make the contribution process easy and effective for everyone involved. It also communicates that you agree to respect the time of the developers managing and developing these open source projects. In return, we will reciprocate that respect by addressing your issue, assessing changes, and helping you finalize your pull requests. - -## Quicklinks - -* [Code of Conduct](#code-of-conduct) -* [Getting Started](#getting-started) - * [Issues](#issues) - * [Pull Requests](#pull-requests) - -## Code of Conduct - -By participating and contributing to this project, you agree to uphold our [Code of Conduct](https://github.com/Uptycs/kubequery/blob/master/CODE_OF_CONDUCT.md). - -## Getting Started - -Contributions are made to this repo via Issues and Pull Requests (PRs). A few general guidelines that cover both: - -- To report security vulnerabilities, please email [kubequery@uptycs.com](mailto:kubequery@uptycs.com). -- Search for existing Issues and PRs before creating your own. - -### Issues - -Issues should be used to report problems with kubequery, request a new feature, or to discuss potential changes before a PR is created. When you create a new issue, a template will be loaded that will guide you through collecting and providing the information we need to investigate. - -If you find an issue that addresses the problem you're having, please add your own reproduction information to the existing issue rather than creating a new one. Adding a [reaction](https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) can also help be indicating to our maintainers that a particular problem is affecting more than just the reporter. - -### Pull Requests - -PRs to our libraries are always welcome and can be a quick way to get your fix or improvement slated for the next release. In general, PRs should: - -- Only fix/add the functionality in question. -- Add unit tests for fixed or changed functionality. -- Address a single concern in the least number of changed lines as possible. -- Include documentation in the repo. -- Be accompanied by a complete Pull Request template (loaded automatically when a PR is created). - -For changes that address core functionality or would require breaking changes (e.g. a major release), it's best to open an Issue to discuss your proposal first. This is not required but can save time creating and reviewing changes. - -In general, we follow the ["fork-and-pull" Git workflow](https://github.com/susam/gitpr) - -1. Fork the repository to your own Github account -2. Clone the project to your machine -3. Create a branch locally with a succinct but descriptive name -4. Commit changes to the branch -5. Following any formatting and testing guidelines specific to this repo -6. Push changes to your fork -7. Open a PR in our repository and follow the PR template so that we can efficiently review the changes. diff --git a/infrastructure/kubequery/Dockerfile b/infrastructure/kubequery/Dockerfile deleted file mode 100644 index 5480212ad9..0000000000 --- a/infrastructure/kubequery/Dockerfile +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) 2020-present, The kubequery authors -# -# This source code is licensed as defined by the LICENSE file found in the -# root directory of this source tree. -# -# SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - -FROM ubuntu:20.04@sha256:80ef4a44043dec4490506e6cc4289eeda2d106a70148b74b5ae91ee670e9c35d AS builder - -ARG BASEQUERY_VERSION=5.0.2 - -ADD https://uptycs-basequery.s3.amazonaws.com/${BASEQUERY_VERSION}/basequery_${BASEQUERY_VERSION}-1.linux_amd64.deb /tmp/basequery.deb - -RUN dpkg -i /tmp/basequery.deb - -# ===== - -FROM uptycs/busybox:v1.33.0@sha256:6a312f5959d374420eedce83f42d2ad19a027bd4e448ed734372bc1a07ad8b10 - -ARG BASEQUERY_VERSION -ARG KUBEQUERY_VERSION - -LABEL \ - name="kubequery" \ - description="kubequery powered by Osquery" \ - version="${KUBEQUERY_VERSION}" \ - url="https://github.com/Uptycs/kubequery" - -# uptycs/busybox comes with this user predefined. We need a non-root user -USER uptycs - -WORKDIR /opt/uptycs - -RUN set -ex; \ - mkdir /opt/uptycs/bin /opt/uptycs/etc /opt/uptycs/logs /opt/uptycs/var && \ - echo "/opt/uptycs/bin/kubequery.ext" > /opt/uptycs/etc/autoload.exts - -COPY --from=0 --chown=uptycs:uptycs /opt/osquery/bin/osqueryd /opt/uptycs/bin/basequery -COPY --from=0 --chown=uptycs:uptycs /opt/osquery/share/osquery/certs/certs.pem /opt/uptycs/etc/ -COPY --chown=uptycs:uptycs bin/entrypoint.sh bin/kubequeryi bin/uuidgen /opt/uptycs/bin/ -COPY --chown=uptycs:uptycs bin/kubequery /opt/uptycs/bin/kubequery.ext - -ENV KUBEQUERY_VERSION=${KUBEQUERY_VERSION} - -ENTRYPOINT ["/opt/uptycs/bin/entrypoint.sh"] diff --git a/infrastructure/kubequery/LICENSE b/infrastructure/kubequery/LICENSE deleted file mode 100644 index 84be566876..0000000000 --- a/infrastructure/kubequery/LICENSE +++ /dev/null @@ -1,9 +0,0 @@ -# License - -By contributing to kubequery you agree that your contributions will be licensed -under the terms of both the [LICENSE-Apache-2.0](LICENSE-Apache-2.0) and the -[LICENSE-GPL-2.0](LICENSE-GPL-2.0) files in the root of this source tree. - -If you're using kubequery you are free to choose one of the provided licenses. - -`SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-only` diff --git a/infrastructure/kubequery/LICENSE-Apache-2.0 b/infrastructure/kubequery/LICENSE-Apache-2.0 deleted file mode 100644 index 8f71f43fee..0000000000 --- a/infrastructure/kubequery/LICENSE-Apache-2.0 +++ /dev/null @@ -1,202 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - diff --git a/infrastructure/kubequery/LICENSE-GPL-2.0 b/infrastructure/kubequery/LICENSE-GPL-2.0 deleted file mode 100644 index 1f963da0d1..0000000000 --- a/infrastructure/kubequery/LICENSE-GPL-2.0 +++ /dev/null @@ -1,340 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. - diff --git a/infrastructure/kubequery/Makefile b/infrastructure/kubequery/Makefile deleted file mode 100644 index 34af3a2c20..0000000000 --- a/infrastructure/kubequery/Makefile +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/make -f - -# Copyright (c) 2020-present, The kubequery authors -# -# This source code is licensed as defined by the LICENSE file found in the -# root directory of this source tree. -# -# SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - -ifeq ($(VERSION),) - VERSION := $(shell git describe --tags HEAD | cut -d'-' -f1-2 | sed 's/-/./') -endif - -all: deps lint test build kubequery.yaml - -deps: - @go mod download - -lint: - @go install honnef.co/go/tools/cmd/staticcheck@latest - @go install golang.org/x/lint/golint - @staticcheck ./... - @golint ./... - -build: deps - @go build -ldflags="-s -w -X main.VERSION=${VERSION}" -o bin ./... - -test: - @go test -race -cover ./... - -integration: - @node integration/index.js - -docker: build - @docker build --build-arg KUBEQUERY_VERSION=${VERSION} -t uptycs/kubequery:${VERSION} . - -genschema: build - @./bin/gentables > docs/tables.json - @echo "\`\`\`sql" > docs/schema.md - @./bin/genschema >> docs/schema.md - @echo "\`\`\`" >> docs/schema.md - -kubequery.yaml: - @sed -e "s/^/ /g" etc/kubequery.flags > etc/kubequery.flags.tmp - @sed -e "s/^/ /g" etc/kubequery.conf > etc/kubequery.conf.tmp - @sed -e "/kubequery.flags: |/r etc/kubequery.flags.tmp" \ - -e "/kubequery.conf: |/r etc/kubequery.conf.tmp" \ - -e "s/version: latest/version: ${VERSION}/g" \ - kubequery-template.yaml > kubequery.yaml - @rm -f etc/*.tmp - -clean: - @rm -rf vendor kubequery.yaml bin/kubequery bin/genschema bin/uuidgen etc/*.tmp - -.PHONY: all integration diff --git a/infrastructure/kubequery/README.md b/infrastructure/kubequery/README.md deleted file mode 100644 index 6adad45b14..0000000000 --- a/infrastructure/kubequery/README.md +++ /dev/null @@ -1,180 +0,0 @@ -[![Build](https://github.com/Uptycs/kubequery/workflows/Build/badge.svg?branch=master)](https://github.com/Uptycs/kubequery/actions?query=workflow%3ABuild) -[![CodeQL](https://github.com/Uptycs/kubequery/workflows/CodeQL/badge.svg?branch=master)](https://github.com/Uptycs/kubequery/actions?query=workflow%3ACodeQL) -[![Go Report Card](https://goreportcard.com/badge/github.com/Uptycs/kubequery)](https://goreportcard.com/report/github.com/Uptycs/kubequery) -[![FOSSA Status](https://app.fossa.com/api/projects/custom%2B22616%2Fgit%40github.com%3AUptycs%2Fkubequery.git.svg?type=shield)](https://app.fossa.com/projects/custom%2B22616%2Fgit%40github.com%3AUptycs%2Fkubequery.git?ref=badge_shield) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg)](CODE_OF_CONDUCT.md) - ---- - -# kubequery powered by Osquery - -kubequery is a [Osquery](https://osquery.io) extension that provides SQL based analytics for [Kubernetes](https://kubernetes.io) clusters - -kubequery will be packaged as docker image available from [dockerhub](https://hub.docker.com/r/uptycs/kubequery). It is expected to be deployed as a [Kubernetes Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment) per cluster. A sample deployment template is available [here](kubequery-template.yaml) - -kubequery tables [schema is available here](docs/schema.md) - ---- - -## Build - -`Go 1.17` and `make` are required to build kubequery. Run: `make` - -Container image for master branch will be available on [dockerhub](https://hub.docker.com/r/uptycs/kubequery) -```sh -docker pull uptycs/kubequery:latest -``` - -For production, tagged container images should be used instead of `latest`. - ---- - -## Deployment - -[kubequery-template.yaml](kubequery-template.yaml) is a template that creates the following Kubernetes resources: -* [Namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) -* [ServiceAccount](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#service-account-tokens) -* [ClusterRole](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#role-and-clusterrole) -* [ClusterRoleBinding](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#rolebinding-and-clusterrolebinding) -* [ConfigMap](https://kubernetes.io/docs/concepts/configuration/configmap/) -* [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) - -`kubequery` Namespace will be the placeholder for all resources that are namespaced. - -`kubequery-sa` is ServiceAccount that is associated with the kubequery deployment pod specification. The container uses the service account token to authenticate with the API server. - -`kubequery-clusterrole` is a ClusterRole that allows `get` and `list` operations on all resources in the following API groups: -- "" (core) -- admissionregistration.k8s.io -- apps -- autoscaling -- batch -- networking.k8s.io -- policy -- rbac.authorization.k8s.io -- storage.k8s.io - -`kubequery-clusterrolebinding` is a ClusterRoleBinding that binds the cluster role with the service account. - -`kubequery-config` is a ConfigMap that will be mounted inside the container image as a directory. The contents of this config map should be similar to `/etc/osquery`. For example, kubequery.flags, kubequery.conf, etc. should be part of this config map. - -`kubequery` is the Deployment that creates one replica pod. The container launched as a part of the pod is run as non-root user. - -By default pod resource `requests` and `limits` are set to 500m (half a core) and 200MB. kubequery.yaml file should be tweaked to suite your needs before applying: - -```sh -kubectl apply -f kubequery.yaml -``` - -Check the status of the pod using the following command. Pod should be in Running Status. -```sh -kubectl get pods -n kubequery -``` - -Validate the installation was successful by first executing: - -```sh -kubectl exec -it $(kubectl get pods -n kubequery -o jsonpath='{.items[0].metadata.name}') -n kubequery -- kubequeryi '.tables' -``` - -Which should produce the following output: - -``` - => kubernetes_api_resources - => kubernetes_cluster_role_binding_subjects - => kubernetes_cluster_role_policy_rule - => kubernetes_config_maps - => kubernetes_cron_jobs - => kubernetes_csi_drivers - => kubernetes_csi_node_drivers - => kubernetes_daemon_set_containers - ... -``` - -Queries can be run using kubequeryi on the deployed container: - -```sh -kubectl exec -it $(kubectl get pods -n kubequery -o jsonpath='{.items[0].metadata.name}') -n kubequery -- kubequeryi --line 'SELECT * FROM kubernetes_pods' -``` - -Pod logs can be viewed using: -```sh -kubectl logs $(kubectl get pods -n kubequery -o jsonpath='{.items[0].metadata.name}') -n kubequery -``` - -## Helm - -[Helm](https://helm.sh) must be installed to use the charts. Please refer to Helm's [documentation](https://helm.sh/docs) to get started. - -Once Helm has been set up correctly, add the repo as follows: - -```sh -helm repo add uptycs https://uptycs.github.io/kubequery -``` - -If you had already added this repo earlier, run `helm repo update` to retrieve the latest versions of the packages. You can then run `helm search repo uptycs` to see the charts. - -To install the kubequery chart: -```sh -helm install my-kubequery uptycs/kubequery -``` - -To uninstall the chart: -```sh -helm delete my-kubequery -``` - ---- - -## FAQ - -### Use kubequery instead of Osquery in Kubernetes? - -No. kubequery should to be deployed as a [Kubernetes Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/). Which means there will be one [Pod](https://kubernetes.io/docs/concepts/workloads/pods/) of kubequery running per Kubernetes cluster. Osquery should be deployed to every node in the cluster. Querying most Osquery tables from an ephemeral pod does not provide much value. kubequery container image also runs as non-root user, which means most of the Osquery tables will either return an error or partial data. - -![Deployment](docs/deployment.svg) - -### Why are some columns JSON? - -Normalizing nested JSON data like Kubernetes API responses will create an explosion of tables. So some of the columns in kuberenetes tables are left as JSON. Data is eventually processed by [SQLite](https://www.sqlite.org/index.html) with-in Osquery. SQLite has very [good JSON](https://www.sqlite.org/json1.html) support. - -For example if `run_as_user` in `kubernetes_pod_security_policies` table looks like the following: -```json -{"rule": "MustRunAsNonRoot"} -``` - -To get the value of `rule`, the following query can be used: -```sql -SELECT value AS 'rule' -FROM kubernetes_pod_security_policies, json_tree(kubernetes_pod_security_policies.run_as_user) -WHERE key = 'rule'; - -+------------------+ -| rule | -+------------------+ -| MustRunAsNonRoot | -+------------------+ -``` - -[json_each](https://www.sqlite.org/json1.html#jeach) can be used to explode JSON array types. For example if `volumes` in `kubernetes_pod_security_policies` table looks like the following: -```json -{"volumes": ["configMap","emptyDir","projected","secret","downwardAPI","persistentVolumeClaim"]} -``` - -To get a separate row for each volume, the following query can be used: -```sql -SELECT value -FROM kubernetes_pod_security_policies, json_each(kubernetes_pod_security_policies.volumes); - -+-----------------------+ -| value | -+-----------------------+ -| configMap | -| emptyDir | -| projected | -| secret | -| downwardAPI | -| persistentVolumeClaim | -+-----------------------+ -``` - -Osquery logger's like TLS, Kafka loggers can be used to export scheduled query data to remove fleet management/security analytics platforms. Lamba like functions can be applied on rows of streaming data in these platforms. These lamba functions can extract necessary fields from embedded JSON to detect compliance issues or security concerns. If tables are normalized and are streamed at different schedules, it will not be trivial to JOIN across tables and trigger events/alerts. diff --git a/infrastructure/kubequery/SECURITY.md b/infrastructure/kubequery/SECURITY.md deleted file mode 100644 index c4451d097e..0000000000 --- a/infrastructure/kubequery/SECURITY.md +++ /dev/null @@ -1,15 +0,0 @@ -# Security Policy - -## Supported Versions - -| Version | Supported | -| ------- | ------------------ | -| 1.1.1 | :white_check_mark: | -| 1.1.0 | :white_check_mark: | -| 1.0.0 | :white_check_mark: | - -## Reporting a Vulnerability - -Please report vulnerabilties to [kubequery@uptycs.com](mailto:kubequery@uptycs.com). -We will evaluate the details and get back ASAP. -We are working on creating other communication channels for kubequery developers and users. diff --git a/infrastructure/kubequery/bin/entrypoint.sh b/infrastructure/kubequery/bin/entrypoint.sh deleted file mode 100755 index a4fb190906..0000000000 --- a/infrastructure/kubequery/bin/entrypoint.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/sh - -UUID=$(/opt/uptycs/bin/uuidgen) -if [ $? -eq 0 ]; then - # Use kube-system UUID as the host identifier - ADDITIONAL_FLAGS="--host_identifier=specified --specified_identifier=${UUID}" -fi - -if [ -d /opt/uptycs/config ]; then - # Copy bootstrap flags and configuration from volume mount - cp /opt/uptycs/config/* /opt/uptycs/etc/ -fi - -exec /opt/uptycs/bin/basequery \ - --flagfile=/opt/uptycs/etc/kubequery.flags \ - --config_path=/opt/uptycs/etc/kubequery.conf \ - --database_path=/opt/uptycs/kubequery.db \ - --logger_path=/opt/uptycs/logs \ - --pidfile=/opt/uptycs/var/kubequery.pid \ - --disable_watchdog \ - --enroll_tables=osquery_info,kubernetes_info \ - ${ADDITIONAL_FLAGS} \ - --tls_user_agent=kubequery/${KUBEQUERY_VERSION} \ - --extensions_socket=/opt/uptycs/var/kubequery.em \ - --extensions_autoload=/opt/uptycs/etc/autoload.exts \ - --extensions_require=kubequery \ - --extension_event_tables=kubernetes_events \ - -D diff --git a/infrastructure/kubequery/bin/kubequeryi b/infrastructure/kubequery/bin/kubequeryi deleted file mode 100755 index 0babe0328f..0000000000 --- a/infrastructure/kubequery/bin/kubequeryi +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh - -/opt/uptycs/bin/basequery \ - --flagfile=/opt/uptycs/etc/kubequery.flags \ - --config_path=/opt/uptycs/etc/kubequery.conf \ - --extensions_socket=/opt/uptycs/var/kubequeryi.em \ - --extensions_autoload=/opt/uptycs/etc/autoload.exts \ - --extensions_require=kubequery \ - --extension_event_tables=kubernetes_events \ - --disable_database \ - --disable_events=false \ - -S \ - "$@" diff --git a/infrastructure/kubequery/charts/kubequery/.helmignore b/infrastructure/kubequery/charts/kubequery/.helmignore deleted file mode 100644 index d2cfd4600b..0000000000 --- a/infrastructure/kubequery/charts/kubequery/.helmignore +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright (c) 2020-present, The kubequery authors -# -# This source code is licensed as defined by the LICENSE file found in the -# root directory of this source tree. -# -# SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - -# Patterns to ignore when building packages. - -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store - -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ - -# Common backup files -*.swp -*.bak -*.tmp -*.orig -*~ - -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ diff --git a/infrastructure/kubequery/charts/kubequery/Chart.yaml b/infrastructure/kubequery/charts/kubequery/Chart.yaml deleted file mode 100644 index 0adac6e19f..0000000000 --- a/infrastructure/kubequery/charts/kubequery/Chart.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) 2020-present, The kubequery authors -# -# This source code is licensed as defined by the LICENSE file found in the -# root directory of this source tree. -# -# SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - -apiVersion: v2 - -name: kubequery -description: kubequery powered by Osquery - -type: application -version: 1.1.1 -appVersion: 1.1.1 -icon: https://raw.githubusercontent.com/Uptycs/kubequery/master/docs/kubequery.png diff --git a/infrastructure/kubequery/charts/kubequery/templates/Chart.yaml b/infrastructure/kubequery/charts/kubequery/templates/Chart.yaml deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/infrastructure/kubequery/charts/kubequery/templates/_helpers.tpl b/infrastructure/kubequery/charts/kubequery/templates/_helpers.tpl deleted file mode 100644 index 59a2031ed7..0000000000 --- a/infrastructure/kubequery/charts/kubequery/templates/_helpers.tpl +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright (c) 2020-present, The kubequery authors -# -# This source code is licensed as defined by the LICENSE file found in the -# root directory of this source tree. -# -# SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - -{{/* -Expand the name of the chart. -*/}} -{{- define "kubequery.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "kubequery.fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride }} -{{- if contains $name .Release.Name }} -{{- .Release.Name | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} -{{- end }} -{{- end }} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "kubequery.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define "kubequery.labels" -}} -helm.sh/chart: {{ include "kubequery.chart" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -app.kubernetes.io/part-of: kubequery -{{- end }} - -{{/* -Selector labels -*/}} -{{- define "kubequery.selectorLabels" -}} -app.kubernetes.io/name: {{ include "kubequery.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} diff --git a/infrastructure/kubequery/charts/kubequery/templates/clusterrole.yaml b/infrastructure/kubequery/charts/kubequery/templates/clusterrole.yaml deleted file mode 100644 index 7bb8ca7af2..0000000000 --- a/infrastructure/kubequery/charts/kubequery/templates/clusterrole.yaml +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2020-present, The kubequery authors -# -# This source code is licensed as defined by the LICENSE file found in the -# root directory of this source tree. -# -# SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: {{ .Release.Name }}-clusterrole - labels: - app.kubernetes.io/name: {{ .Release.Name }}-clusterrole - {{- include "kubequery.labels" . | nindent 4 }} -rules: -- apiGroups: ["", "admissionregistration.k8s.io", "apps", "autoscaling", "batch", "events.k8s.io", "networking.k8s.io", "policy", "rbac.authorization.k8s.io", "storage.k8s.io"] - resources: ["*"] - verbs: ["get", "list", "watch"] diff --git a/infrastructure/kubequery/charts/kubequery/templates/clusterrolebinding.yaml b/infrastructure/kubequery/charts/kubequery/templates/clusterrolebinding.yaml deleted file mode 100644 index e9561aa222..0000000000 --- a/infrastructure/kubequery/charts/kubequery/templates/clusterrolebinding.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) 2020-present, The kubequery authors -# -# This source code is licensed as defined by the LICENSE file found in the -# root directory of this source tree. -# -# SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: {{ .Release.Name }}-clusterrolebinding - labels: - app.kubernetes.io/name: {{ .Release.Name }}-clusterrolebinding - {{- include "kubequery.labels" . | nindent 4 }} -roleRef: - kind: ClusterRole - name: {{ .Release.Name }}-clusterrole - apiGroup: rbac.authorization.k8s.io -subjects: -- kind: ServiceAccount - name: {{ .Release.Name }}-serviceaccount - namespace: {{ .Values.namespace }} diff --git a/infrastructure/kubequery/charts/kubequery/templates/configmap.yaml b/infrastructure/kubequery/charts/kubequery/templates/configmap.yaml deleted file mode 100644 index 0cb7020f73..0000000000 --- a/infrastructure/kubequery/charts/kubequery/templates/configmap.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) 2020-present, The kubequery authors -# -# This source code is licensed as defined by the LICENSE file found in the -# root directory of this source tree. -# -# SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ .Release.Name }}-configmap - namespace: {{ .Values.namespace }} - labels: - app.kubernetes.io/name: {{ .Release.Name }}-configmap - {{- include "kubequery.labels" . | nindent 4 }} -data: -{{- range $name, $config := .Values.config }} - {{ $name }}: |- -{{ tpl $config $ | indent 4 }} - {{- end }} diff --git a/infrastructure/kubequery/charts/kubequery/templates/deployment.yaml b/infrastructure/kubequery/charts/kubequery/templates/deployment.yaml deleted file mode 100644 index d7a000e7b3..0000000000 --- a/infrastructure/kubequery/charts/kubequery/templates/deployment.yaml +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright (c) 2020-present, The kubequery authors -# -# This source code is licensed as defined by the LICENSE file found in the -# root directory of this source tree. -# -# SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "kubequery.fullname" . }} - namespace: {{ .Values.namespace }} - labels: - app.kubernetes.io/name: {{ .Release.Name }}-deployment - {{- include "kubequery.labels" . | nindent 4 }} -spec: - replicas: 1 - selector: - matchLabels: - {{- include "kubequery.selectorLabels" . | nindent 6 }} - template: - metadata: - {{- with .Values.podAnnotations }} - annotations: - {{- toYaml . | nindent 8 }} - {{- end }} - labels: - {{- include "kubequery.selectorLabels" . | nindent 8 }} - spec: - hostname: {{ .Values.cluster }} - securityContext: - runAsNonRoot: true - runAsUser: 1000 - runAsGroup: 1000 - fsGroup: 1000 - terminationGracePeriodSeconds: 10 - serviceAccountName: {{ .Release.Name }}-serviceaccount - containers: - - name: {{ .Chart.Name }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - {{- with .Values.resources }} - resources: - {{- toYaml . | nindent 10 }} - {{- end }} - volumeMounts: - - name: config - mountPath: /opt/uptycs/config - {{- with .Values.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} - volumes: - - name: config - configMap: - name: {{ .Release.Name }}-configmap diff --git a/infrastructure/kubequery/charts/kubequery/templates/namespace.yaml b/infrastructure/kubequery/charts/kubequery/templates/namespace.yaml deleted file mode 100644 index 1e421a4854..0000000000 --- a/infrastructure/kubequery/charts/kubequery/templates/namespace.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2020-present, The kubequery authors -# -# This source code is licensed as defined by the LICENSE file found in the -# root directory of this source tree. -# -# SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - -apiVersion: v1 -kind: Namespace -metadata: - name: {{ .Values.namespace }} - labels: - app.kubernetes.io/name: {{ .Values.namespace }} - {{- include "kubequery.labels" . | nindent 4 }} diff --git a/infrastructure/kubequery/charts/kubequery/templates/serviceaccount.yaml b/infrastructure/kubequery/charts/kubequery/templates/serviceaccount.yaml deleted file mode 100644 index 335a744f55..0000000000 --- a/infrastructure/kubequery/charts/kubequery/templates/serviceaccount.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) 2020-present, The kubequery authors -# -# This source code is licensed as defined by the LICENSE file found in the -# root directory of this source tree. -# -# SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ .Release.Name }}-serviceaccount - namespace: {{ .Values.namespace }} - labels: - app.kubernetes.io/name: {{ .Release.Name }}-serviceaccount - {{- include "kubequery.labels" . | nindent 4 }} diff --git a/infrastructure/kubequery/charts/kubequery/values.yaml b/infrastructure/kubequery/charts/kubequery/values.yaml deleted file mode 100644 index 356cb1f9aa..0000000000 --- a/infrastructure/kubequery/charts/kubequery/values.yaml +++ /dev/null @@ -1,247 +0,0 @@ -# Copyright (c) 2020-present, The kubequery authors -# -# This source code is licensed as defined by the LICENSE file found in the -# root directory of this source tree. -# -# SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - -# Namespace to deploy into -namespace: kubequery - -nameOverride: "" -fullnameOverride: "" - -image: - repository: uptycs/kubequery - pullPolicy: IfNotPresent - # Overrides the image tag whose default is the chart appVersion. - tag: "" - -# Cluster name -cluster: mycluster - -resources: - requests: - cpu: 200m - memory: 128Mi - limits: - cpu: 1000m - memory: 512Mi - -# kubequery configuration -config: - # TLS enroll secret - enroll.secret: TODO - - # Flags - kubequery.flags: |- - --schedule_splay_percent=50 - - # Config - kubequery.conf: |- - { - "schedule": { - "kubernetes_api_resources": { - "query": "SELECT * FROM kubernetes_api_resources", - "interval": 43200 - }, - "kubernetes_cluster_role_policy_rules": { - "query": "SELECT * FROM kubernetes_cluster_role_policy_rules", - "interval": 1800 - }, - "kubernetes_cluster_role_binding_subjects": { - "query": "SELECT * FROM kubernetes_cluster_role_binding_subjects", - "interval": 1800 - }, - "kubernetes_component_statuses": { - "query": "SELECT * FROM kubernetes_component_statuses", - "interval": 3600 - }, - "kubernetes_config_maps": { - "query": "SELECT * FROM kubernetes_config_maps", - "interval": 600 - }, - "kubernetes_cron_jobs": { - "query": "SELECT * FROM kubernetes_cron_jobs", - "interval": 600 - }, - "kubernetes_csi_drivers": { - "query": "SELECT * FROM kubernetes_csi_drivers", - "interval": 43200 - }, - "kubernetes_csi_node_drivers": { - "query": "SELECT * FROM kubernetes_csi_node_drivers", - "interval": 43200 - }, - "kubernetes_daemon_set_containers": { - "query": "SELECT * FROM kubernetes_daemon_set_containers", - "interval": 600 - }, - "kubernetes_daemon_sets": { - "query": "SELECT * FROM kubernetes_daemon_sets", - "interval": 600 - }, - "kubernetes_daemon_set_volumes": { - "query": "SELECT * FROM kubernetes_daemon_set_volumes", - "interval": 600 - }, - "kubernetes_deployments": { - "query": "SELECT * FROM kubernetes_deployments", - "interval": 600 - }, - "kubernetes_deployments_containers": { - "query": "SELECT * FROM kubernetes_deployments_containers", - "interval": 600 - }, - "kubernetes_deployments_volumes": { - "query": "SELECT * FROM kubernetes_deployments_volumes", - "interval": 600 - }, - "kubernetes_endpoint_subsets": { - "query": "SELECT * FROM kubernetes_endpoint_subsets", - "interval": 1800 - }, - "kubernetes_horizontal_pod_autoscalers": { - "query": "SELECT * FROM kubernetes_horizontal_pod_autoscalers", - "interval": 7200 - }, - "kubernetes_info": { - "query": "SELECT * FROM kubernetes_info", - "interval": 43200 - }, - "kubernetes_ingress_classes": { - "query": "SELECT * FROM kubernetes_ingress_classes", - "interval": 21600 - }, - "kubernetes_ingresses": { - "query": "SELECT * FROM kubernetes_ingresses", - "interval": 21600 - }, - "kubernetes_jobs": { - "query": "SELECT * FROM kubernetes_jobs", - "interval": 600 - }, - "kubernetes_limit_ranges": { - "query": "SELECT * FROM kubernetes_limit_ranges", - "interval": 21600 - }, - "kubernetes_mutating_webhooks": { - "query": "SELECT * FROM kubernetes_mutating_webhooks", - "interval": 7200 - }, - "kubernetes_namespaces": { - "query": "SELECT * FROM kubernetes_namespaces", - "interval": 3600 - }, - "kubernetes_network_policies": { - "query": "SELECT * FROM kubernetes_network_policies", - "interval": 1800 - }, - "kubernetes_nodes": { - "query": "SELECT * FROM kubernetes_nodes", - "interval": 600 - }, - "kubernetes_persistent_volume_claims": { - "query": "SELECT * FROM kubernetes_persistent_volume_claims", - "interval": 1800 - }, - "kubernetes_persistent_volumes": { - "query": "SELECT * FROM kubernetes_persistent_volumes", - "interval": 1800 - }, - "kubernetes_pod_containers": { - "query": "SELECT * FROM kubernetes_pod_containers", - "interval": 600 - }, - "kubernetes_pod_disruption_budgets": { - "query": "SELECT * FROM kubernetes_pod_disruption_budgets", - "interval": 1800 - }, - "kubernetes_pods": { - "query": "SELECT * FROM kubernetes_pods", - "interval": 600 - }, - "kubernetes_pod_security_policies": { - "query": "SELECT * FROM kubernetes_pod_security_policies", - "interval": 600 - }, - "kubernetes_pod_template_containers": { - "query": "SELECT * FROM kubernetes_pod_template_containers", - "interval": 1800 - }, - "kubernetes_pod_templates": { - "query": "SELECT * FROM kubernetes_pod_templates", - "interval": 1800 - }, - "kubernetes_pod_templates_volumes": { - "query": "SELECT * FROM kubernetes_pod_templates_volumes", - "interval": 1800 - }, - "kubernetes_pod_volumes": { - "query": "SELECT * FROM kubernetes_pod_volumes", - "interval": 600 - }, - "kubernetes_replica_set_containers": { - "query": "SELECT * FROM kubernetes_replica_set_containers", - "interval": 600 - }, - "kubernetes_replica_sets": { - "query": "SELECT * FROM kubernetes_replica_sets", - "interval": 600 - }, - "kubernetes_replica_set_volumes": { - "query": "SELECT * FROM kubernetes_replica_set_volumes", - "interval": 600 - }, - "kubernetes_resource_quotas": { - "query": "SELECT * FROM kubernetes_resource_quotas", - "interval": 3600 - }, - "kubernetes_role_binding_subjects": { - "query": "SELECT * FROM kubernetes_role_binding_subjects", - "interval": 600 - }, - "kubernetes_role_policy_rules": { - "query": "SELECT * FROM kubernetes_role_policy_rules", - "interval": 600 - }, - "kubernetes_secrets": { - "query": "SELECT * FROM kubernetes_secrets", - "interval": 600 - }, - "kubernetes_service_accounts": { - "query": "SELECT * FROM kubernetes_service_accounts", - "interval": 600 - }, - "kubernetes_services": { - "query": "SELECT * FROM kubernetes_services", - "interval": 900 - }, - "kubernetes_stateful_set_containers": { - "query": "SELECT * FROM kubernetes_stateful_set_containers", - "interval": 600 - }, - "kubernetes_stateful_sets": { - "query": "SELECT * FROM kubernetes_stateful_sets", - "interval": 600 - }, - "kubernetes_stateful_set_volumes": { - "query": "SELECT * FROM kubernetes_stateful_set_volumes", - "interval": 600 - }, - "kubernetes_storage_classes": { - "query": "SELECT * FROM kubernetes_storage_classes", - "interval": 21600 - }, - "kubernetes_validating_webhooks": { - "query": "SELECT * FROM kubernetes_validating_webhooks", - "interval": 7200 - }, - "kubernetes_volume_attachments": { - "query": "SELECT * FROM kubernetes_volume_attachments", - "interval": 3600 - } - }, - "options":{ - } - } diff --git a/infrastructure/kubequery/cmd/genschema/main.go b/infrastructure/kubequery/cmd/genschema/main.go deleted file mode 100644 index 43436c9e22..0000000000 --- a/infrastructure/kubequery/cmd/genschema/main.go +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package main - -import ( - "fmt" - - "github.com/Uptycs/kubequery/internal/k8s/tables" -) - -func main() { - for _, t := range tables.GetTables() { - fmt.Printf("CREATE TABLE %s (\n", t.Name) - for i, c := range t.Columns { - fmt.Printf(" `%s` %s", c.Name, c.Type) - if i < len(t.Columns)-1 { - fmt.Printf(",") - } - fmt.Println() - } - fmt.Print(");\n\n") - } -} diff --git a/infrastructure/kubequery/cmd/gentables/main.go b/infrastructure/kubequery/cmd/gentables/main.go deleted file mode 100644 index 8b49b2d161..0000000000 --- a/infrastructure/kubequery/cmd/gentables/main.go +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package main - -import ( - "fmt" - - "github.com/Uptycs/kubequery/internal/k8s/tables" -) - -func main() { - tbls := tables.GetTables() - fmt.Printf("{\n \"tables\": [") - for j, t := range tbls { - fmt.Printf(" {\n \"name\": \"%s\",\n \"columns\": [\n", t.Name) - for i, c := range t.Columns { - fmt.Printf(" {\n \"name\": \"%s\",\n \"type\": \"%s\"\n", c.Name, c.Type) - if i < len(t.Columns)-1 { - fmt.Printf(" },\n") - } else { - fmt.Printf(" }\n") - } - } - fmt.Printf(" ]\n") - if j < len(tbls)-1 { - fmt.Printf(" },\n") - } else { - fmt.Printf(" }\n") - } - } - fmt.Printf(" ]\n}") -} diff --git a/infrastructure/kubequery/cmd/kubequery/main.go b/infrastructure/kubequery/cmd/kubequery/main.go deleted file mode 100644 index 03b2762018..0000000000 --- a/infrastructure/kubequery/cmd/kubequery/main.go +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package main - -import ( - "context" - "flag" - "fmt" - "os" - "os/signal" - "syscall" - "time" - - osquery "github.com/Uptycs/basequery-go" - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - "github.com/Uptycs/kubequery/internal/k8s/event" - "github.com/Uptycs/kubequery/internal/k8s/tables" -) - -var ( - // VERSION set by compiler - VERSION = "latest" - - //lint:ignore U1000 Argument is required by basequery - verbose = flag.Bool("verbose", false, "Whether to enable verbose logging") - version = flag.Bool("version", false, "Prints kubequery version") - socket = flag.String("socket", "", "Path to the extensions UNIX domain socket") - timeout = flag.Int("timeout", 5, "Seconds to wait for autoloaded extensions") - interval = flag.Int("interval", 5, "Seconds delay between connectivity checks") -) - -func main() { - flag.Parse() - - if *version { - fmt.Println("kubequery version:", VERSION) - os.Exit(0) - } - - if *socket == "" { - panic("Missing required --socket argument") - } - - err := k8s.Init() - if err != nil { - panic(fmt.Sprintf("Error connecting to kubernetes API server: %s", err)) - } - - server, err := osquery.NewExtensionManagerServer( - "kubequery", - *socket, - osquery.ServerVersion(VERSION), - osquery.ServerTimeout(time.Second*time.Duration(*timeout)), - osquery.ServerPingInterval(time.Second*time.Duration(*interval)), - ) - if err != nil { - panic(fmt.Sprintf("Error launching kubequery: %s", err)) - } - - quit := make(chan os.Signal, 1) - signal.Notify(quit, os.Interrupt) - - for _, t := range tables.GetTables() { - server.RegisterPlugin(table.NewPlugin(t.Name, t.Columns, t.GenFunc)) - } - - go func() { - if err := server.Run(); err != nil { - panic(fmt.Sprintf("Failed to start extension manager server: %s", err)) - } - syscall.Kill(syscall.Getpid(), syscall.SIGINT) - }() - - // Wait for the extension manager to start before sending events - time.Sleep(time.Second * 5) - - watcher, err := event.CreateEventWatcher(*socket, time.Second*time.Duration(*timeout)) - if err != nil { - fmt.Println("Failed to create kubernetes event watcher: ", err) - } else { - watcher.Start() - } - - <-quit - - if watcher != nil { - watcher.Stop() - } - server.Shutdown(context.Background()) -} diff --git a/infrastructure/kubequery/cmd/uuidgen/main.go b/infrastructure/kubequery/cmd/uuidgen/main.go deleted file mode 100644 index 964227ef18..0000000000 --- a/infrastructure/kubequery/cmd/uuidgen/main.go +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package main - -import ( - "context" - "fmt" - - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func main() { - err := k8s.Init() - if err != nil { - panic(fmt.Sprintf("Error connecting to kubernetes API server: %s", err)) - } - - options := v1.GetOptions{} - ks, err := k8s.GetClient().CoreV1().Namespaces().Get(context.Background(), "kube-system", options) - if err != nil { - panic(fmt.Sprintf("Error getting kube-system namespace: %s", err)) - } - - fmt.Print(ks.ObjectMeta.UID) -} diff --git a/infrastructure/kubequery/docs/deployment.svg b/infrastructure/kubequery/docs/deployment.svg deleted file mode 100644 index 0fc39eb294..0000000000 --- a/infrastructure/kubequery/docs/deployment.svg +++ /dev/null @@ -1,3 +0,0 @@ - - -100100100
Master
Master
Control Plane
Control Plane
NodeOsqueryDaemonSetkubequeryDeploymentNodeOsqueryDaemonSetNodeOsqueryDaemonSet
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/infrastructure/kubequery/docs/kubequery.png b/infrastructure/kubequery/docs/kubequery.png deleted file mode 100644 index eed9b07c1985dd78af60ffc5795a20496c5922c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6443 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 diff --git a/infrastructure/kubequery/docs/schema.md b/infrastructure/kubequery/docs/schema.md deleted file mode 100644 index 3e7424c737..0000000000 --- a/infrastructure/kubequery/docs/schema.md +++ /dev/null @@ -1,2083 +0,0 @@ -```sql -CREATE TABLE kubernetes_mutating_webhooks ( - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `client_config` TEXT, - `rules` TEXT, - `failure_policy` TEXT, - `match_policy` TEXT, - `namespace_selector` TEXT, - `object_selector` TEXT, - `side_effects` TEXT, - `timeout_seconds` INTEGER, - `admission_review_versions` TEXT, - `reinvocation_policy` TEXT -); - -CREATE TABLE kubernetes_validating_webhooks ( - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `client_config` TEXT, - `rules` TEXT, - `failure_policy` TEXT, - `match_policy` TEXT, - `namespace_selector` TEXT, - `object_selector` TEXT, - `side_effects` TEXT, - `timeout_seconds` INTEGER, - `admission_review_versions` TEXT -); - -CREATE TABLE kubernetes_daemon_sets ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `se_linux_options_user` TEXT, - `se_linux_options_role` TEXT, - `se_linux_options_type` TEXT, - `se_linux_options_level` TEXT, - `windows_options_gmsa_credential_spec_name` TEXT, - `windows_options_gmsa_credential_spec` TEXT, - `windows_options_run_as_user_name` TEXT, - `seccomp_profile_type` TEXT, - `seccomp_profile_localhost_profile` TEXT, - `run_as_user` BIGINT, - `run_as_group` BIGINT, - `run_as_non_root` INTEGER, - `supplemental_groups` TEXT, - `fs_group` BIGINT, - `sysctls` TEXT, - `fs_group_change_policy` TEXT, - `node_affinity` TEXT, - `pod_affinity` TEXT, - `pod_anti_affinity` TEXT, - `dns_config_nameservers` TEXT, - `dns_config_searches` TEXT, - `dns_config_options` TEXT, - `node_selector` TEXT, - `restart_policy` TEXT, - `termination_grace_period_seconds` BIGINT, - `active_deadline_seconds` BIGINT, - `dns_policy` TEXT, - `service_account_name` TEXT, - `automount_service_account_token` INTEGER, - `node_name` TEXT, - `host_network` INTEGER, - `host_pid` INTEGER, - `host_ipc` INTEGER, - `share_process_namespace` INTEGER, - `image_pull_secrets` TEXT, - `hostname` TEXT, - `subdomain` TEXT, - `scheduler_name` TEXT, - `tolerations` TEXT, - `host_aliases` TEXT, - `priority_class_name` TEXT, - `priority` INTEGER, - `readiness_gates` TEXT, - `runtime_class_name` TEXT, - `enable_service_links` INTEGER, - `preemption_policy` TEXT, - `overhead` TEXT, - `topology_spread_constraints` TEXT, - `set_hostname_as_fqdn` INTEGER, - `current_number_scheduled` INTEGER, - `number_misscheduled` INTEGER, - `desired_number_scheduled` INTEGER, - `number_ready` INTEGER, - `observed_generation` BIGINT, - `updated_number_scheduled` INTEGER, - `number_available` INTEGER, - `number_unavailable` INTEGER, - `collision_count` INTEGER, - `conditions` TEXT, - `selector` TEXT, - `update_strategy` TEXT, - `min_ready_seconds` INTEGER, - `revision_history_limit` INTEGER -); - -CREATE TABLE kubernetes_daemon_set_containers ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `se_linux_options_user` TEXT, - `se_linux_options_role` TEXT, - `se_linux_options_type` TEXT, - `se_linux_options_level` TEXT, - `windows_options_gmsa_credential_spec_name` TEXT, - `windows_options_gmsa_credential_spec` TEXT, - `windows_options_run_as_user_name` TEXT, - `seccomp_profile_type` TEXT, - `seccomp_profile_localhost_profile` TEXT, - `run_as_user` BIGINT, - `run_as_group` BIGINT, - `run_as_non_root` INTEGER, - `capabilities_add` TEXT, - `capabilities_drop` TEXT, - `privileged` INTEGER, - `read_only_root_filesystem` INTEGER, - `allow_privilege_escalation` INTEGER, - `proc_mount` TEXT, - `target_container_name` TEXT, - `image` TEXT, - `command` TEXT, - `args` TEXT, - `working_dir` TEXT, - `ports` TEXT, - `env_from` TEXT, - `env` TEXT, - `resource_limits` TEXT, - `resource_requests` TEXT, - `volume_mounts` TEXT, - `volume_devices` TEXT, - `liveness_probe` TEXT, - `readiness_probe` TEXT, - `startup_probe` TEXT, - `lifecycle` TEXT, - `termination_message_path` TEXT, - `termination_message_policy` TEXT, - `image_pull_policy` TEXT, - `stdin` INTEGER, - `stdin_once` INTEGER, - `tty` INTEGER, - `daemon_set_name` TEXT, - `container_type` TEXT -); - -CREATE TABLE kubernetes_daemon_set_volumes ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `volume_type` TEXT, - `fs_type` TEXT, - `read_only` INTEGER, - `secret_name` TEXT, - `host_path_path` TEXT, - `host_path_type` TEXT, - `empty_dir_medium` TEXT, - `empty_dir_size_limit` TEXT, - `gce_persistent_disk_pd_name` TEXT, - `gce_persistent_disk_partition` INTEGER, - `aws_elastic_block_store_volume_id` TEXT, - `aws_elastic_block_store_partition` INTEGER, - `git_repo_repository` TEXT, - `git_repo_revision` TEXT, - `git_repo_directory` TEXT, - `secret_items` TEXT, - `secret_default_mode` INTEGER, - `secret_optional` INTEGER, - `nfs_server` TEXT, - `nfs_path` TEXT, - `iscsi_target_portal` TEXT, - `iscsi_iqn` TEXT, - `iscsi_lun` INTEGER, - `iscsi_interface` TEXT, - `iscsi_portals` TEXT, - `iscsi_discovery_chap_auth` INTEGER, - `iscsi_session_chap_auth` INTEGER, - `iscsi_initiator_name` TEXT, - `glusterfs_endpoints_name` TEXT, - `glusterfs_path` TEXT, - `persistent_volume_claim_name` TEXT, - `rbd_ceph_monitors` TEXT, - `rbd_image` TEXT, - `rbd_pool` TEXT, - `rbd_rados_user` TEXT, - `rbd_keyring` TEXT, - `flex_volume_driver` TEXT, - `flex_volume_options` TEXT, - `cinder_volume_id` TEXT, - `ceph_fs_monitors` TEXT, - `ceph_fs_path` TEXT, - `ceph_fs_user` TEXT, - `ceph_fs_secret_file` TEXT, - `flocker_dataset_name` TEXT, - `flocker_dataset_uuid` TEXT, - `downward_api_items` TEXT, - `downward_api_default_mode` INTEGER, - `fc_target_ww_ns` TEXT, - `fc_lun` INTEGER, - `fc_ww_ids` TEXT, - `azure_file_share_name` TEXT, - `config_map_name` TEXT, - `config_map_items` TEXT, - `config_map_default_mode` INTEGER, - `config_map_optional` INTEGER, - `vsphere_volume_volume_path` TEXT, - `vsphere_volume_storage_policy_name` TEXT, - `vsphere_volume_storage_policy_id` TEXT, - `quobyte_registry` TEXT, - `quobyte_volume` TEXT, - `quobyte_user` TEXT, - `quobyte_group` TEXT, - `quobyte_tenant` TEXT, - `azure_disk_disk_name` TEXT, - `azure_disk_data_disk_uri` TEXT, - `azure_disk_caching_mode` TEXT, - `azure_disk_kind` TEXT, - `photon_persistent_disk_pd_id` TEXT, - `projected_sources` TEXT, - `projected_default_mode` INTEGER, - `portworx_volume_id` TEXT, - `scale_io_gateway` TEXT, - `scale_io_system` TEXT, - `scale_iossl_enabled` INTEGER, - `scale_io_protection_domain` TEXT, - `scale_io_storage_pool` TEXT, - `scale_io_storage_mode` TEXT, - `scale_io_volume_name` TEXT, - `storage_os_volume_name` TEXT, - `storage_os_volume_namespace` TEXT, - `csi_driver` TEXT, - `csi_volume_attributes` TEXT, - `ephemeral_volume_claim_template` TEXT, - `daemon_set_name` TEXT -); - -CREATE TABLE kubernetes_deployments ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `se_linux_options_user` TEXT, - `se_linux_options_role` TEXT, - `se_linux_options_type` TEXT, - `se_linux_options_level` TEXT, - `windows_options_gmsa_credential_spec_name` TEXT, - `windows_options_gmsa_credential_spec` TEXT, - `windows_options_run_as_user_name` TEXT, - `seccomp_profile_type` TEXT, - `seccomp_profile_localhost_profile` TEXT, - `run_as_user` BIGINT, - `run_as_group` BIGINT, - `run_as_non_root` INTEGER, - `supplemental_groups` TEXT, - `fs_group` BIGINT, - `sysctls` TEXT, - `fs_group_change_policy` TEXT, - `node_affinity` TEXT, - `pod_affinity` TEXT, - `pod_anti_affinity` TEXT, - `dns_config_nameservers` TEXT, - `dns_config_searches` TEXT, - `dns_config_options` TEXT, - `node_selector` TEXT, - `restart_policy` TEXT, - `termination_grace_period_seconds` BIGINT, - `active_deadline_seconds` BIGINT, - `dns_policy` TEXT, - `service_account_name` TEXT, - `automount_service_account_token` INTEGER, - `node_name` TEXT, - `host_network` INTEGER, - `host_pid` INTEGER, - `host_ipc` INTEGER, - `share_process_namespace` INTEGER, - `image_pull_secrets` TEXT, - `hostname` TEXT, - `subdomain` TEXT, - `scheduler_name` TEXT, - `tolerations` TEXT, - `host_aliases` TEXT, - `priority_class_name` TEXT, - `priority` INTEGER, - `readiness_gates` TEXT, - `runtime_class_name` TEXT, - `enable_service_links` INTEGER, - `preemption_policy` TEXT, - `overhead` TEXT, - `topology_spread_constraints` TEXT, - `set_hostname_as_fqdn` INTEGER, - `observed_generation` BIGINT, - `replicas` INTEGER, - `updated_replicas` INTEGER, - `ready_replicas` INTEGER, - `available_replicas` INTEGER, - `unavailable_replicas` INTEGER, - `conditions` TEXT, - `collision_count` INTEGER, - `deployment_replicas` INTEGER, - `selector` TEXT, - `strategy` TEXT, - `min_ready_seconds` INTEGER, - `revision_history_limit` INTEGER, - `paused` INTEGER, - `progress_deadline_seconds` INTEGER -); - -CREATE TABLE kubernetes_deployments_containers ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `se_linux_options_user` TEXT, - `se_linux_options_role` TEXT, - `se_linux_options_type` TEXT, - `se_linux_options_level` TEXT, - `windows_options_gmsa_credential_spec_name` TEXT, - `windows_options_gmsa_credential_spec` TEXT, - `windows_options_run_as_user_name` TEXT, - `seccomp_profile_type` TEXT, - `seccomp_profile_localhost_profile` TEXT, - `run_as_user` BIGINT, - `run_as_group` BIGINT, - `run_as_non_root` INTEGER, - `capabilities_add` TEXT, - `capabilities_drop` TEXT, - `privileged` INTEGER, - `read_only_root_filesystem` INTEGER, - `allow_privilege_escalation` INTEGER, - `proc_mount` TEXT, - `target_container_name` TEXT, - `image` TEXT, - `command` TEXT, - `args` TEXT, - `working_dir` TEXT, - `ports` TEXT, - `env_from` TEXT, - `env` TEXT, - `resource_limits` TEXT, - `resource_requests` TEXT, - `volume_mounts` TEXT, - `volume_devices` TEXT, - `liveness_probe` TEXT, - `readiness_probe` TEXT, - `startup_probe` TEXT, - `lifecycle` TEXT, - `termination_message_path` TEXT, - `termination_message_policy` TEXT, - `image_pull_policy` TEXT, - `stdin` INTEGER, - `stdin_once` INTEGER, - `tty` INTEGER, - `deployment_name` TEXT, - `container_type` TEXT -); - -CREATE TABLE kubernetes_deployments_volumes ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `volume_type` TEXT, - `fs_type` TEXT, - `read_only` INTEGER, - `secret_name` TEXT, - `host_path_path` TEXT, - `host_path_type` TEXT, - `empty_dir_medium` TEXT, - `empty_dir_size_limit` TEXT, - `gce_persistent_disk_pd_name` TEXT, - `gce_persistent_disk_partition` INTEGER, - `aws_elastic_block_store_volume_id` TEXT, - `aws_elastic_block_store_partition` INTEGER, - `git_repo_repository` TEXT, - `git_repo_revision` TEXT, - `git_repo_directory` TEXT, - `secret_items` TEXT, - `secret_default_mode` INTEGER, - `secret_optional` INTEGER, - `nfs_server` TEXT, - `nfs_path` TEXT, - `iscsi_target_portal` TEXT, - `iscsi_iqn` TEXT, - `iscsi_lun` INTEGER, - `iscsi_interface` TEXT, - `iscsi_portals` TEXT, - `iscsi_discovery_chap_auth` INTEGER, - `iscsi_session_chap_auth` INTEGER, - `iscsi_initiator_name` TEXT, - `glusterfs_endpoints_name` TEXT, - `glusterfs_path` TEXT, - `persistent_volume_claim_name` TEXT, - `rbd_ceph_monitors` TEXT, - `rbd_image` TEXT, - `rbd_pool` TEXT, - `rbd_rados_user` TEXT, - `rbd_keyring` TEXT, - `flex_volume_driver` TEXT, - `flex_volume_options` TEXT, - `cinder_volume_id` TEXT, - `ceph_fs_monitors` TEXT, - `ceph_fs_path` TEXT, - `ceph_fs_user` TEXT, - `ceph_fs_secret_file` TEXT, - `flocker_dataset_name` TEXT, - `flocker_dataset_uuid` TEXT, - `downward_api_items` TEXT, - `downward_api_default_mode` INTEGER, - `fc_target_ww_ns` TEXT, - `fc_lun` INTEGER, - `fc_ww_ids` TEXT, - `azure_file_share_name` TEXT, - `config_map_name` TEXT, - `config_map_items` TEXT, - `config_map_default_mode` INTEGER, - `config_map_optional` INTEGER, - `vsphere_volume_volume_path` TEXT, - `vsphere_volume_storage_policy_name` TEXT, - `vsphere_volume_storage_policy_id` TEXT, - `quobyte_registry` TEXT, - `quobyte_volume` TEXT, - `quobyte_user` TEXT, - `quobyte_group` TEXT, - `quobyte_tenant` TEXT, - `azure_disk_disk_name` TEXT, - `azure_disk_data_disk_uri` TEXT, - `azure_disk_caching_mode` TEXT, - `azure_disk_kind` TEXT, - `photon_persistent_disk_pd_id` TEXT, - `projected_sources` TEXT, - `projected_default_mode` INTEGER, - `portworx_volume_id` TEXT, - `scale_io_gateway` TEXT, - `scale_io_system` TEXT, - `scale_iossl_enabled` INTEGER, - `scale_io_protection_domain` TEXT, - `scale_io_storage_pool` TEXT, - `scale_io_storage_mode` TEXT, - `scale_io_volume_name` TEXT, - `storage_os_volume_name` TEXT, - `storage_os_volume_namespace` TEXT, - `csi_driver` TEXT, - `csi_volume_attributes` TEXT, - `ephemeral_volume_claim_template` TEXT, - `deployment_name` TEXT -); - -CREATE TABLE kubernetes_replica_sets ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `se_linux_options_user` TEXT, - `se_linux_options_role` TEXT, - `se_linux_options_type` TEXT, - `se_linux_options_level` TEXT, - `windows_options_gmsa_credential_spec_name` TEXT, - `windows_options_gmsa_credential_spec` TEXT, - `windows_options_run_as_user_name` TEXT, - `seccomp_profile_type` TEXT, - `seccomp_profile_localhost_profile` TEXT, - `run_as_user` BIGINT, - `run_as_group` BIGINT, - `run_as_non_root` INTEGER, - `supplemental_groups` TEXT, - `fs_group` BIGINT, - `sysctls` TEXT, - `fs_group_change_policy` TEXT, - `node_affinity` TEXT, - `pod_affinity` TEXT, - `pod_anti_affinity` TEXT, - `dns_config_nameservers` TEXT, - `dns_config_searches` TEXT, - `dns_config_options` TEXT, - `node_selector` TEXT, - `restart_policy` TEXT, - `termination_grace_period_seconds` BIGINT, - `active_deadline_seconds` BIGINT, - `dns_policy` TEXT, - `service_account_name` TEXT, - `automount_service_account_token` INTEGER, - `node_name` TEXT, - `host_network` INTEGER, - `host_pid` INTEGER, - `host_ipc` INTEGER, - `share_process_namespace` INTEGER, - `image_pull_secrets` TEXT, - `hostname` TEXT, - `subdomain` TEXT, - `scheduler_name` TEXT, - `tolerations` TEXT, - `host_aliases` TEXT, - `priority_class_name` TEXT, - `priority` INTEGER, - `readiness_gates` TEXT, - `runtime_class_name` TEXT, - `enable_service_links` INTEGER, - `preemption_policy` TEXT, - `overhead` TEXT, - `topology_spread_constraints` TEXT, - `set_hostname_as_fqdn` INTEGER, - `replicas` INTEGER, - `fully_labeled_replicas` INTEGER, - `ready_replicas` INTEGER, - `available_replicas` INTEGER, - `observed_generation` BIGINT, - `conditions` TEXT, - `replica_set_replicas` INTEGER, - `min_ready_seconds` INTEGER, - `selector` TEXT -); - -CREATE TABLE kubernetes_replica_set_containers ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `se_linux_options_user` TEXT, - `se_linux_options_role` TEXT, - `se_linux_options_type` TEXT, - `se_linux_options_level` TEXT, - `windows_options_gmsa_credential_spec_name` TEXT, - `windows_options_gmsa_credential_spec` TEXT, - `windows_options_run_as_user_name` TEXT, - `seccomp_profile_type` TEXT, - `seccomp_profile_localhost_profile` TEXT, - `run_as_user` BIGINT, - `run_as_group` BIGINT, - `run_as_non_root` INTEGER, - `capabilities_add` TEXT, - `capabilities_drop` TEXT, - `privileged` INTEGER, - `read_only_root_filesystem` INTEGER, - `allow_privilege_escalation` INTEGER, - `proc_mount` TEXT, - `target_container_name` TEXT, - `image` TEXT, - `command` TEXT, - `args` TEXT, - `working_dir` TEXT, - `ports` TEXT, - `env_from` TEXT, - `env` TEXT, - `resource_limits` TEXT, - `resource_requests` TEXT, - `volume_mounts` TEXT, - `volume_devices` TEXT, - `liveness_probe` TEXT, - `readiness_probe` TEXT, - `startup_probe` TEXT, - `lifecycle` TEXT, - `termination_message_path` TEXT, - `termination_message_policy` TEXT, - `image_pull_policy` TEXT, - `stdin` INTEGER, - `stdin_once` INTEGER, - `tty` INTEGER, - `replica_set_name` TEXT, - `container_type` TEXT -); - -CREATE TABLE kubernetes_replica_set_volumes ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `volume_type` TEXT, - `fs_type` TEXT, - `read_only` INTEGER, - `secret_name` TEXT, - `host_path_path` TEXT, - `host_path_type` TEXT, - `empty_dir_medium` TEXT, - `empty_dir_size_limit` TEXT, - `gce_persistent_disk_pd_name` TEXT, - `gce_persistent_disk_partition` INTEGER, - `aws_elastic_block_store_volume_id` TEXT, - `aws_elastic_block_store_partition` INTEGER, - `git_repo_repository` TEXT, - `git_repo_revision` TEXT, - `git_repo_directory` TEXT, - `secret_items` TEXT, - `secret_default_mode` INTEGER, - `secret_optional` INTEGER, - `nfs_server` TEXT, - `nfs_path` TEXT, - `iscsi_target_portal` TEXT, - `iscsi_iqn` TEXT, - `iscsi_lun` INTEGER, - `iscsi_interface` TEXT, - `iscsi_portals` TEXT, - `iscsi_discovery_chap_auth` INTEGER, - `iscsi_session_chap_auth` INTEGER, - `iscsi_initiator_name` TEXT, - `glusterfs_endpoints_name` TEXT, - `glusterfs_path` TEXT, - `persistent_volume_claim_name` TEXT, - `rbd_ceph_monitors` TEXT, - `rbd_image` TEXT, - `rbd_pool` TEXT, - `rbd_rados_user` TEXT, - `rbd_keyring` TEXT, - `flex_volume_driver` TEXT, - `flex_volume_options` TEXT, - `cinder_volume_id` TEXT, - `ceph_fs_monitors` TEXT, - `ceph_fs_path` TEXT, - `ceph_fs_user` TEXT, - `ceph_fs_secret_file` TEXT, - `flocker_dataset_name` TEXT, - `flocker_dataset_uuid` TEXT, - `downward_api_items` TEXT, - `downward_api_default_mode` INTEGER, - `fc_target_ww_ns` TEXT, - `fc_lun` INTEGER, - `fc_ww_ids` TEXT, - `azure_file_share_name` TEXT, - `config_map_name` TEXT, - `config_map_items` TEXT, - `config_map_default_mode` INTEGER, - `config_map_optional` INTEGER, - `vsphere_volume_volume_path` TEXT, - `vsphere_volume_storage_policy_name` TEXT, - `vsphere_volume_storage_policy_id` TEXT, - `quobyte_registry` TEXT, - `quobyte_volume` TEXT, - `quobyte_user` TEXT, - `quobyte_group` TEXT, - `quobyte_tenant` TEXT, - `azure_disk_disk_name` TEXT, - `azure_disk_data_disk_uri` TEXT, - `azure_disk_caching_mode` TEXT, - `azure_disk_kind` TEXT, - `photon_persistent_disk_pd_id` TEXT, - `projected_sources` TEXT, - `projected_default_mode` INTEGER, - `portworx_volume_id` TEXT, - `scale_io_gateway` TEXT, - `scale_io_system` TEXT, - `scale_iossl_enabled` INTEGER, - `scale_io_protection_domain` TEXT, - `scale_io_storage_pool` TEXT, - `scale_io_storage_mode` TEXT, - `scale_io_volume_name` TEXT, - `storage_os_volume_name` TEXT, - `storage_os_volume_namespace` TEXT, - `csi_driver` TEXT, - `csi_volume_attributes` TEXT, - `ephemeral_volume_claim_template` TEXT, - `replica_set_name` TEXT -); - -CREATE TABLE kubernetes_stateful_sets ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `se_linux_options_user` TEXT, - `se_linux_options_role` TEXT, - `se_linux_options_type` TEXT, - `se_linux_options_level` TEXT, - `windows_options_gmsa_credential_spec_name` TEXT, - `windows_options_gmsa_credential_spec` TEXT, - `windows_options_run_as_user_name` TEXT, - `seccomp_profile_type` TEXT, - `seccomp_profile_localhost_profile` TEXT, - `run_as_user` BIGINT, - `run_as_group` BIGINT, - `run_as_non_root` INTEGER, - `supplemental_groups` TEXT, - `fs_group` BIGINT, - `sysctls` TEXT, - `fs_group_change_policy` TEXT, - `node_affinity` TEXT, - `pod_affinity` TEXT, - `pod_anti_affinity` TEXT, - `dns_config_nameservers` TEXT, - `dns_config_searches` TEXT, - `dns_config_options` TEXT, - `node_selector` TEXT, - `restart_policy` TEXT, - `termination_grace_period_seconds` BIGINT, - `active_deadline_seconds` BIGINT, - `dns_policy` TEXT, - `service_account_name` TEXT, - `automount_service_account_token` INTEGER, - `node_name` TEXT, - `host_network` INTEGER, - `host_pid` INTEGER, - `host_ipc` INTEGER, - `share_process_namespace` INTEGER, - `image_pull_secrets` TEXT, - `hostname` TEXT, - `subdomain` TEXT, - `scheduler_name` TEXT, - `tolerations` TEXT, - `host_aliases` TEXT, - `priority_class_name` TEXT, - `priority` INTEGER, - `readiness_gates` TEXT, - `runtime_class_name` TEXT, - `enable_service_links` INTEGER, - `preemption_policy` TEXT, - `overhead` TEXT, - `topology_spread_constraints` TEXT, - `set_hostname_as_fqdn` INTEGER, - `observed_generation` BIGINT, - `replicas` INTEGER, - `ready_replicas` INTEGER, - `current_replicas` INTEGER, - `updated_replicas` INTEGER, - `current_revision` TEXT, - `update_revision` TEXT, - `collision_count` INTEGER, - `conditions` TEXT, - `available_replicas` INTEGER, - `stateful_set_replicas` INTEGER, - `selector` TEXT, - `volume_claim_templates` TEXT, - `service_name` TEXT, - `pod_management_policy` TEXT, - `update_strategy` TEXT, - `revision_history_limit` INTEGER -); - -CREATE TABLE kubernetes_stateful_set_containers ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `se_linux_options_user` TEXT, - `se_linux_options_role` TEXT, - `se_linux_options_type` TEXT, - `se_linux_options_level` TEXT, - `windows_options_gmsa_credential_spec_name` TEXT, - `windows_options_gmsa_credential_spec` TEXT, - `windows_options_run_as_user_name` TEXT, - `seccomp_profile_type` TEXT, - `seccomp_profile_localhost_profile` TEXT, - `run_as_user` BIGINT, - `run_as_group` BIGINT, - `run_as_non_root` INTEGER, - `capabilities_add` TEXT, - `capabilities_drop` TEXT, - `privileged` INTEGER, - `read_only_root_filesystem` INTEGER, - `allow_privilege_escalation` INTEGER, - `proc_mount` TEXT, - `target_container_name` TEXT, - `image` TEXT, - `command` TEXT, - `args` TEXT, - `working_dir` TEXT, - `ports` TEXT, - `env_from` TEXT, - `env` TEXT, - `resource_limits` TEXT, - `resource_requests` TEXT, - `volume_mounts` TEXT, - `volume_devices` TEXT, - `liveness_probe` TEXT, - `readiness_probe` TEXT, - `startup_probe` TEXT, - `lifecycle` TEXT, - `termination_message_path` TEXT, - `termination_message_policy` TEXT, - `image_pull_policy` TEXT, - `stdin` INTEGER, - `stdin_once` INTEGER, - `tty` INTEGER, - `stateful_set_name` TEXT, - `container_type` TEXT -); - -CREATE TABLE kubernetes_stateful_set_volumes ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `volume_type` TEXT, - `fs_type` TEXT, - `read_only` INTEGER, - `secret_name` TEXT, - `host_path_path` TEXT, - `host_path_type` TEXT, - `empty_dir_medium` TEXT, - `empty_dir_size_limit` TEXT, - `gce_persistent_disk_pd_name` TEXT, - `gce_persistent_disk_partition` INTEGER, - `aws_elastic_block_store_volume_id` TEXT, - `aws_elastic_block_store_partition` INTEGER, - `git_repo_repository` TEXT, - `git_repo_revision` TEXT, - `git_repo_directory` TEXT, - `secret_items` TEXT, - `secret_default_mode` INTEGER, - `secret_optional` INTEGER, - `nfs_server` TEXT, - `nfs_path` TEXT, - `iscsi_target_portal` TEXT, - `iscsi_iqn` TEXT, - `iscsi_lun` INTEGER, - `iscsi_interface` TEXT, - `iscsi_portals` TEXT, - `iscsi_discovery_chap_auth` INTEGER, - `iscsi_session_chap_auth` INTEGER, - `iscsi_initiator_name` TEXT, - `glusterfs_endpoints_name` TEXT, - `glusterfs_path` TEXT, - `persistent_volume_claim_name` TEXT, - `rbd_ceph_monitors` TEXT, - `rbd_image` TEXT, - `rbd_pool` TEXT, - `rbd_rados_user` TEXT, - `rbd_keyring` TEXT, - `flex_volume_driver` TEXT, - `flex_volume_options` TEXT, - `cinder_volume_id` TEXT, - `ceph_fs_monitors` TEXT, - `ceph_fs_path` TEXT, - `ceph_fs_user` TEXT, - `ceph_fs_secret_file` TEXT, - `flocker_dataset_name` TEXT, - `flocker_dataset_uuid` TEXT, - `downward_api_items` TEXT, - `downward_api_default_mode` INTEGER, - `fc_target_ww_ns` TEXT, - `fc_lun` INTEGER, - `fc_ww_ids` TEXT, - `azure_file_share_name` TEXT, - `config_map_name` TEXT, - `config_map_items` TEXT, - `config_map_default_mode` INTEGER, - `config_map_optional` INTEGER, - `vsphere_volume_volume_path` TEXT, - `vsphere_volume_storage_policy_name` TEXT, - `vsphere_volume_storage_policy_id` TEXT, - `quobyte_registry` TEXT, - `quobyte_volume` TEXT, - `quobyte_user` TEXT, - `quobyte_group` TEXT, - `quobyte_tenant` TEXT, - `azure_disk_disk_name` TEXT, - `azure_disk_data_disk_uri` TEXT, - `azure_disk_caching_mode` TEXT, - `azure_disk_kind` TEXT, - `photon_persistent_disk_pd_id` TEXT, - `projected_sources` TEXT, - `projected_default_mode` INTEGER, - `portworx_volume_id` TEXT, - `scale_io_gateway` TEXT, - `scale_io_system` TEXT, - `scale_iossl_enabled` INTEGER, - `scale_io_protection_domain` TEXT, - `scale_io_storage_pool` TEXT, - `scale_io_storage_mode` TEXT, - `scale_io_volume_name` TEXT, - `storage_os_volume_name` TEXT, - `storage_os_volume_namespace` TEXT, - `csi_driver` TEXT, - `csi_volume_attributes` TEXT, - `ephemeral_volume_claim_template` TEXT, - `stateful_set_name` TEXT -); - -CREATE TABLE kubernetes_horizontal_pod_autoscalers ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `scale_target_ref` TEXT, - `min_replicas` INTEGER, - `max_replicas` INTEGER, - `target_cpu_utilization_percentage` INTEGER, - `observed_generation` BIGINT, - `last_scale_time` BIGINT, - `current_replicas` INTEGER, - `desired_replicas` INTEGER, - `current_cpu_utilization_percentage` INTEGER -); - -CREATE TABLE kubernetes_cron_jobs ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `se_linux_options_user` TEXT, - `se_linux_options_role` TEXT, - `se_linux_options_type` TEXT, - `se_linux_options_level` TEXT, - `windows_options_gmsa_credential_spec_name` TEXT, - `windows_options_gmsa_credential_spec` TEXT, - `windows_options_run_as_user_name` TEXT, - `seccomp_profile_type` TEXT, - `seccomp_profile_localhost_profile` TEXT, - `run_as_user` BIGINT, - `run_as_group` BIGINT, - `run_as_non_root` INTEGER, - `supplemental_groups` TEXT, - `fs_group` BIGINT, - `sysctls` TEXT, - `fs_group_change_policy` TEXT, - `node_affinity` TEXT, - `pod_affinity` TEXT, - `pod_anti_affinity` TEXT, - `dns_config_nameservers` TEXT, - `dns_config_searches` TEXT, - `dns_config_options` TEXT, - `node_selector` TEXT, - `restart_policy` TEXT, - `termination_grace_period_seconds` BIGINT, - `active_deadline_seconds` BIGINT, - `dns_policy` TEXT, - `service_account_name` TEXT, - `automount_service_account_token` INTEGER, - `node_name` TEXT, - `host_network` INTEGER, - `host_pid` INTEGER, - `host_ipc` INTEGER, - `share_process_namespace` INTEGER, - `image_pull_secrets` TEXT, - `hostname` TEXT, - `subdomain` TEXT, - `scheduler_name` TEXT, - `tolerations` TEXT, - `host_aliases` TEXT, - `priority_class_name` TEXT, - `priority` INTEGER, - `readiness_gates` TEXT, - `runtime_class_name` TEXT, - `enable_service_links` INTEGER, - `preemption_policy` TEXT, - `overhead` TEXT, - `topology_spread_constraints` TEXT, - `set_hostname_as_fqdn` INTEGER, - `active` TEXT, - `last_schedule_time` BIGINT, - `last_successful_time` BIGINT, - `schedule` TEXT, - `starting_deadline_seconds` BIGINT, - `concurrency_policy` TEXT, - `suspend` INTEGER, - `successful_jobs_history_limit` INTEGER, - `failed_jobs_history_limit` INTEGER, - `parallelism` INTEGER, - `completions` INTEGER, - `job_active_deadline_seconds` BIGINT, - `backoff_limit` INTEGER, - `selector` TEXT, - `manual_selector` INTEGER, - `ttl_seconds_after_finished` INTEGER -); - -CREATE TABLE kubernetes_jobs ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `se_linux_options_user` TEXT, - `se_linux_options_role` TEXT, - `se_linux_options_type` TEXT, - `se_linux_options_level` TEXT, - `windows_options_gmsa_credential_spec_name` TEXT, - `windows_options_gmsa_credential_spec` TEXT, - `windows_options_run_as_user_name` TEXT, - `seccomp_profile_type` TEXT, - `seccomp_profile_localhost_profile` TEXT, - `run_as_user` BIGINT, - `run_as_group` BIGINT, - `run_as_non_root` INTEGER, - `supplemental_groups` TEXT, - `fs_group` BIGINT, - `sysctls` TEXT, - `fs_group_change_policy` TEXT, - `node_affinity` TEXT, - `pod_affinity` TEXT, - `pod_anti_affinity` TEXT, - `dns_config_nameservers` TEXT, - `dns_config_searches` TEXT, - `dns_config_options` TEXT, - `node_selector` TEXT, - `restart_policy` TEXT, - `termination_grace_period_seconds` BIGINT, - `active_deadline_seconds` BIGINT, - `dns_policy` TEXT, - `service_account_name` TEXT, - `automount_service_account_token` INTEGER, - `node_name` TEXT, - `host_network` INTEGER, - `host_pid` INTEGER, - `host_ipc` INTEGER, - `share_process_namespace` INTEGER, - `image_pull_secrets` TEXT, - `hostname` TEXT, - `subdomain` TEXT, - `scheduler_name` TEXT, - `tolerations` TEXT, - `host_aliases` TEXT, - `priority_class_name` TEXT, - `priority` INTEGER, - `readiness_gates` TEXT, - `runtime_class_name` TEXT, - `enable_service_links` INTEGER, - `preemption_policy` TEXT, - `overhead` TEXT, - `topology_spread_constraints` TEXT, - `set_hostname_as_fqdn` INTEGER, - `conditions` TEXT, - `start_time` BIGINT, - `completion_time` BIGINT, - `active` INTEGER, - `succeeded` INTEGER, - `failed` INTEGER, - `completed_indexes` TEXT, - `uncounted_terminated_pods` TEXT, - `parallelism` INTEGER, - `completions` INTEGER, - `job_active_deadline_seconds` BIGINT, - `backoff_limit` INTEGER, - `selector` TEXT, - `manual_selector` INTEGER, - `ttl_seconds_after_finished` INTEGER -); - -CREATE TABLE kubernetes_component_statuses ( - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `type` TEXT, - `status` TEXT, - `message` TEXT, - `error` TEXT -); - -CREATE TABLE kubernetes_config_maps ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `immutable` INTEGER -); - -CREATE TABLE kubernetes_endpoint_subsets ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `addresses` TEXT, - `not_ready_addresses` TEXT, - `ports` TEXT -); - -CREATE TABLE kubernetes_limit_ranges ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `type` TEXT, - `max` TEXT, - `min` TEXT, - `default` TEXT, - `default_request` TEXT, - `max_limit_request_ratio` TEXT -); - -CREATE TABLE kubernetes_namespaces ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `phase` TEXT, - `conditions` TEXT -); - -CREATE TABLE kubernetes_nodes ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `pod_cidr` TEXT, - `pod_cidrs` TEXT, - `provider_id` TEXT, - `unschedulable` INTEGER, - `taints` TEXT, - `config_source` TEXT, - `do_not_use_external_id` TEXT, - `capacity` TEXT, - `allocatable` TEXT, - `phase` TEXT, - `conditions` TEXT, - `addresses` TEXT, - `daemon_endpoints` TEXT, - `node_info` TEXT, - `images` TEXT, - `volumes_in_use` TEXT, - `volumes_attached` TEXT, - `config` TEXT -); - -CREATE TABLE kubernetes_persistent_volume_claims ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `access_modes` TEXT, - `selector` TEXT, - `resources` TEXT, - `volume_name` TEXT, - `storage_class_name` TEXT, - `volume_mode` TEXT, - `data_source` TEXT, - `data_source_ref` TEXT, - `phase` TEXT, - `capacity` TEXT, - `conditions` TEXT -); - -CREATE TABLE kubernetes_persistent_volumes ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `capacity` TEXT, - `access_modes` TEXT, - `claim_ref` TEXT, - `persistent_volume_reclaim_policy` TEXT, - `storage_class_name` TEXT, - `mount_options` TEXT, - `volume_mode` TEXT, - `node_affinity` TEXT, - `status_phase` TEXT, - `status_message` TEXT, - `status_reason` TEXT, - `volume_type` TEXT, - `fs_type` TEXT, - `read_only` INTEGER, - `secret_name` TEXT, - `host_path_path` TEXT, - `host_path_type` TEXT, - `gce_persistent_disk_pd_name` TEXT, - `gce_persistent_disk_partition` INTEGER, - `aws_elastic_block_store_volume_id` TEXT, - `aws_elastic_block_store_partition` INTEGER, - `nfs_server` TEXT, - `nfs_path` TEXT, - `iscsi_target_portal` TEXT, - `iscsi_iqn` TEXT, - `iscsi_lun` INTEGER, - `iscsi_interface` TEXT, - `iscsi_portals` TEXT, - `iscsi_discovery_chap_auth` INTEGER, - `iscsi_session_chap_auth` INTEGER, - `iscsi_initiator_name` TEXT, - `local_path` TEXT, - `glusterfs_endpoints_name` TEXT, - `glusterfs_path` TEXT, - `rbd_ceph_monitors` TEXT, - `rbd_image` TEXT, - `rbd_pool` TEXT, - `rbd_rados_user` TEXT, - `rbd_keyring` TEXT, - `flex_volume_driver` TEXT, - `flex_volume_options` TEXT, - `cinder_volume_id` TEXT, - `ceph_fs_monitors` TEXT, - `ceph_fs_path` TEXT, - `ceph_fs_user` TEXT, - `ceph_fs_secret_file` TEXT, - `flocker_dataset_name` TEXT, - `flocker_dataset_uuid` TEXT, - `fc_target_ww_ns` TEXT, - `fc_lun` INTEGER, - `fc_ww_ids` TEXT, - `azure_file_share_name` TEXT, - `vsphere_volume_volume_path` TEXT, - `vsphere_volume_storage_policy_name` TEXT, - `vsphere_volume_storage_policy_id` TEXT, - `quobyte_registry` TEXT, - `quobyte_volume` TEXT, - `quobyte_user` TEXT, - `quobyte_group` TEXT, - `quobyte_tenant` TEXT, - `azure_disk_disk_name` TEXT, - `azure_disk_data_disk_uri` TEXT, - `azure_disk_caching_mode` TEXT, - `azure_disk_kind` TEXT, - `photon_persistent_disk_pd_id` TEXT, - `portworx_volume_id` TEXT, - `scale_io_gateway` TEXT, - `scale_io_system` TEXT, - `scale_iossl_enabled` INTEGER, - `scale_io_protection_domain` TEXT, - `scale_io_storage_pool` TEXT, - `scale_io_storage_mode` TEXT, - `scale_io_volume_name` TEXT, - `storage_os_volume_name` TEXT, - `storage_os_volume_namespace` TEXT, - `csi_driver` TEXT, - `csi_volume_attributes` TEXT -); - -CREATE TABLE kubernetes_pod_templates ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `se_linux_options_user` TEXT, - `se_linux_options_role` TEXT, - `se_linux_options_type` TEXT, - `se_linux_options_level` TEXT, - `windows_options_gmsa_credential_spec_name` TEXT, - `windows_options_gmsa_credential_spec` TEXT, - `windows_options_run_as_user_name` TEXT, - `seccomp_profile_type` TEXT, - `seccomp_profile_localhost_profile` TEXT, - `run_as_user` BIGINT, - `run_as_group` BIGINT, - `run_as_non_root` INTEGER, - `supplemental_groups` TEXT, - `fs_group` BIGINT, - `sysctls` TEXT, - `fs_group_change_policy` TEXT, - `node_affinity` TEXT, - `pod_affinity` TEXT, - `pod_anti_affinity` TEXT, - `dns_config_nameservers` TEXT, - `dns_config_searches` TEXT, - `dns_config_options` TEXT, - `node_selector` TEXT, - `restart_policy` TEXT, - `termination_grace_period_seconds` BIGINT, - `active_deadline_seconds` BIGINT, - `dns_policy` TEXT, - `service_account_name` TEXT, - `automount_service_account_token` INTEGER, - `node_name` TEXT, - `host_network` INTEGER, - `host_pid` INTEGER, - `host_ipc` INTEGER, - `share_process_namespace` INTEGER, - `image_pull_secrets` TEXT, - `hostname` TEXT, - `subdomain` TEXT, - `scheduler_name` TEXT, - `tolerations` TEXT, - `host_aliases` TEXT, - `priority_class_name` TEXT, - `priority` INTEGER, - `readiness_gates` TEXT, - `runtime_class_name` TEXT, - `enable_service_links` INTEGER, - `preemption_policy` TEXT, - `overhead` TEXT, - `topology_spread_constraints` TEXT, - `set_hostname_as_fqdn` INTEGER -); - -CREATE TABLE kubernetes_pod_template_containers ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `se_linux_options_user` TEXT, - `se_linux_options_role` TEXT, - `se_linux_options_type` TEXT, - `se_linux_options_level` TEXT, - `windows_options_gmsa_credential_spec_name` TEXT, - `windows_options_gmsa_credential_spec` TEXT, - `windows_options_run_as_user_name` TEXT, - `seccomp_profile_type` TEXT, - `seccomp_profile_localhost_profile` TEXT, - `run_as_user` BIGINT, - `run_as_group` BIGINT, - `run_as_non_root` INTEGER, - `capabilities_add` TEXT, - `capabilities_drop` TEXT, - `privileged` INTEGER, - `read_only_root_filesystem` INTEGER, - `allow_privilege_escalation` INTEGER, - `proc_mount` TEXT, - `target_container_name` TEXT, - `image` TEXT, - `command` TEXT, - `args` TEXT, - `working_dir` TEXT, - `ports` TEXT, - `env_from` TEXT, - `env` TEXT, - `resource_limits` TEXT, - `resource_requests` TEXT, - `volume_mounts` TEXT, - `volume_devices` TEXT, - `liveness_probe` TEXT, - `readiness_probe` TEXT, - `startup_probe` TEXT, - `lifecycle` TEXT, - `termination_message_path` TEXT, - `termination_message_policy` TEXT, - `image_pull_policy` TEXT, - `stdin` INTEGER, - `stdin_once` INTEGER, - `tty` INTEGER, - `pod_template_name` TEXT, - `container_type` TEXT -); - -CREATE TABLE kubernetes_pod_templates_volumes ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `volume_type` TEXT, - `fs_type` TEXT, - `read_only` INTEGER, - `secret_name` TEXT, - `host_path_path` TEXT, - `host_path_type` TEXT, - `empty_dir_medium` TEXT, - `empty_dir_size_limit` TEXT, - `gce_persistent_disk_pd_name` TEXT, - `gce_persistent_disk_partition` INTEGER, - `aws_elastic_block_store_volume_id` TEXT, - `aws_elastic_block_store_partition` INTEGER, - `git_repo_repository` TEXT, - `git_repo_revision` TEXT, - `git_repo_directory` TEXT, - `secret_items` TEXT, - `secret_default_mode` INTEGER, - `secret_optional` INTEGER, - `nfs_server` TEXT, - `nfs_path` TEXT, - `iscsi_target_portal` TEXT, - `iscsi_iqn` TEXT, - `iscsi_lun` INTEGER, - `iscsi_interface` TEXT, - `iscsi_portals` TEXT, - `iscsi_discovery_chap_auth` INTEGER, - `iscsi_session_chap_auth` INTEGER, - `iscsi_initiator_name` TEXT, - `glusterfs_endpoints_name` TEXT, - `glusterfs_path` TEXT, - `persistent_volume_claim_name` TEXT, - `rbd_ceph_monitors` TEXT, - `rbd_image` TEXT, - `rbd_pool` TEXT, - `rbd_rados_user` TEXT, - `rbd_keyring` TEXT, - `flex_volume_driver` TEXT, - `flex_volume_options` TEXT, - `cinder_volume_id` TEXT, - `ceph_fs_monitors` TEXT, - `ceph_fs_path` TEXT, - `ceph_fs_user` TEXT, - `ceph_fs_secret_file` TEXT, - `flocker_dataset_name` TEXT, - `flocker_dataset_uuid` TEXT, - `downward_api_items` TEXT, - `downward_api_default_mode` INTEGER, - `fc_target_ww_ns` TEXT, - `fc_lun` INTEGER, - `fc_ww_ids` TEXT, - `azure_file_share_name` TEXT, - `config_map_name` TEXT, - `config_map_items` TEXT, - `config_map_default_mode` INTEGER, - `config_map_optional` INTEGER, - `vsphere_volume_volume_path` TEXT, - `vsphere_volume_storage_policy_name` TEXT, - `vsphere_volume_storage_policy_id` TEXT, - `quobyte_registry` TEXT, - `quobyte_volume` TEXT, - `quobyte_user` TEXT, - `quobyte_group` TEXT, - `quobyte_tenant` TEXT, - `azure_disk_disk_name` TEXT, - `azure_disk_data_disk_uri` TEXT, - `azure_disk_caching_mode` TEXT, - `azure_disk_kind` TEXT, - `photon_persistent_disk_pd_id` TEXT, - `projected_sources` TEXT, - `projected_default_mode` INTEGER, - `portworx_volume_id` TEXT, - `scale_io_gateway` TEXT, - `scale_io_system` TEXT, - `scale_iossl_enabled` INTEGER, - `scale_io_protection_domain` TEXT, - `scale_io_storage_pool` TEXT, - `scale_io_storage_mode` TEXT, - `scale_io_volume_name` TEXT, - `storage_os_volume_name` TEXT, - `storage_os_volume_namespace` TEXT, - `csi_driver` TEXT, - `csi_volume_attributes` TEXT, - `ephemeral_volume_claim_template` TEXT, - `pod_template_name` TEXT -); - -CREATE TABLE kubernetes_pods ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `se_linux_options_user` TEXT, - `se_linux_options_role` TEXT, - `se_linux_options_type` TEXT, - `se_linux_options_level` TEXT, - `windows_options_gmsa_credential_spec_name` TEXT, - `windows_options_gmsa_credential_spec` TEXT, - `windows_options_run_as_user_name` TEXT, - `seccomp_profile_type` TEXT, - `seccomp_profile_localhost_profile` TEXT, - `run_as_user` BIGINT, - `run_as_group` BIGINT, - `run_as_non_root` INTEGER, - `supplemental_groups` TEXT, - `fs_group` BIGINT, - `sysctls` TEXT, - `fs_group_change_policy` TEXT, - `node_affinity` TEXT, - `pod_affinity` TEXT, - `pod_anti_affinity` TEXT, - `dns_config_nameservers` TEXT, - `dns_config_searches` TEXT, - `dns_config_options` TEXT, - `node_selector` TEXT, - `restart_policy` TEXT, - `termination_grace_period_seconds` BIGINT, - `active_deadline_seconds` BIGINT, - `dns_policy` TEXT, - `service_account_name` TEXT, - `automount_service_account_token` INTEGER, - `node_name` TEXT, - `host_network` INTEGER, - `host_pid` INTEGER, - `host_ipc` INTEGER, - `share_process_namespace` INTEGER, - `image_pull_secrets` TEXT, - `hostname` TEXT, - `subdomain` TEXT, - `scheduler_name` TEXT, - `tolerations` TEXT, - `host_aliases` TEXT, - `priority_class_name` TEXT, - `priority` INTEGER, - `readiness_gates` TEXT, - `runtime_class_name` TEXT, - `enable_service_links` INTEGER, - `preemption_policy` TEXT, - `overhead` TEXT, - `topology_spread_constraints` TEXT, - `set_hostname_as_fqdn` INTEGER, - `phase` TEXT, - `conditions` TEXT, - `message` TEXT, - `reason` TEXT, - `nominated_node_name` TEXT, - `host_ip` TEXT, - `pod_ip` TEXT, - `pod_ips` TEXT, - `start_time` BIGINT, - `init_container_statuses` TEXT, - `container_statuses` TEXT, - `qos_class` TEXT, - `ephemeral_container_statuses` TEXT -); - -CREATE TABLE kubernetes_pod_containers ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `se_linux_options_user` TEXT, - `se_linux_options_role` TEXT, - `se_linux_options_type` TEXT, - `se_linux_options_level` TEXT, - `windows_options_gmsa_credential_spec_name` TEXT, - `windows_options_gmsa_credential_spec` TEXT, - `windows_options_run_as_user_name` TEXT, - `seccomp_profile_type` TEXT, - `seccomp_profile_localhost_profile` TEXT, - `run_as_user` BIGINT, - `run_as_group` BIGINT, - `run_as_non_root` INTEGER, - `capabilities_add` TEXT, - `capabilities_drop` TEXT, - `privileged` INTEGER, - `read_only_root_filesystem` INTEGER, - `allow_privilege_escalation` INTEGER, - `proc_mount` TEXT, - `target_container_name` TEXT, - `image` TEXT, - `command` TEXT, - `args` TEXT, - `working_dir` TEXT, - `ports` TEXT, - `env_from` TEXT, - `env` TEXT, - `resource_limits` TEXT, - `resource_requests` TEXT, - `volume_mounts` TEXT, - `volume_devices` TEXT, - `liveness_probe` TEXT, - `readiness_probe` TEXT, - `startup_probe` TEXT, - `lifecycle` TEXT, - `termination_message_path` TEXT, - `termination_message_policy` TEXT, - `image_pull_policy` TEXT, - `stdin` INTEGER, - `stdin_once` INTEGER, - `tty` INTEGER, - `pod_name` TEXT, - `container_type` TEXT, - `state` TEXT, - `last_termination_state` TEXT, - `ready` INTEGER, - `restart_count` INTEGER, - `image_repo` TEXT, - `image_id` TEXT, - `container_id` TEXT, - `started` INTEGER -); - -CREATE TABLE kubernetes_pod_volumes ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `volume_type` TEXT, - `fs_type` TEXT, - `read_only` INTEGER, - `secret_name` TEXT, - `host_path_path` TEXT, - `host_path_type` TEXT, - `empty_dir_medium` TEXT, - `empty_dir_size_limit` TEXT, - `gce_persistent_disk_pd_name` TEXT, - `gce_persistent_disk_partition` INTEGER, - `aws_elastic_block_store_volume_id` TEXT, - `aws_elastic_block_store_partition` INTEGER, - `git_repo_repository` TEXT, - `git_repo_revision` TEXT, - `git_repo_directory` TEXT, - `secret_items` TEXT, - `secret_default_mode` INTEGER, - `secret_optional` INTEGER, - `nfs_server` TEXT, - `nfs_path` TEXT, - `iscsi_target_portal` TEXT, - `iscsi_iqn` TEXT, - `iscsi_lun` INTEGER, - `iscsi_interface` TEXT, - `iscsi_portals` TEXT, - `iscsi_discovery_chap_auth` INTEGER, - `iscsi_session_chap_auth` INTEGER, - `iscsi_initiator_name` TEXT, - `glusterfs_endpoints_name` TEXT, - `glusterfs_path` TEXT, - `persistent_volume_claim_name` TEXT, - `rbd_ceph_monitors` TEXT, - `rbd_image` TEXT, - `rbd_pool` TEXT, - `rbd_rados_user` TEXT, - `rbd_keyring` TEXT, - `flex_volume_driver` TEXT, - `flex_volume_options` TEXT, - `cinder_volume_id` TEXT, - `ceph_fs_monitors` TEXT, - `ceph_fs_path` TEXT, - `ceph_fs_user` TEXT, - `ceph_fs_secret_file` TEXT, - `flocker_dataset_name` TEXT, - `flocker_dataset_uuid` TEXT, - `downward_api_items` TEXT, - `downward_api_default_mode` INTEGER, - `fc_target_ww_ns` TEXT, - `fc_lun` INTEGER, - `fc_ww_ids` TEXT, - `azure_file_share_name` TEXT, - `config_map_name` TEXT, - `config_map_items` TEXT, - `config_map_default_mode` INTEGER, - `config_map_optional` INTEGER, - `vsphere_volume_volume_path` TEXT, - `vsphere_volume_storage_policy_name` TEXT, - `vsphere_volume_storage_policy_id` TEXT, - `quobyte_registry` TEXT, - `quobyte_volume` TEXT, - `quobyte_user` TEXT, - `quobyte_group` TEXT, - `quobyte_tenant` TEXT, - `azure_disk_disk_name` TEXT, - `azure_disk_data_disk_uri` TEXT, - `azure_disk_caching_mode` TEXT, - `azure_disk_kind` TEXT, - `photon_persistent_disk_pd_id` TEXT, - `projected_sources` TEXT, - `projected_default_mode` INTEGER, - `portworx_volume_id` TEXT, - `scale_io_gateway` TEXT, - `scale_io_system` TEXT, - `scale_iossl_enabled` INTEGER, - `scale_io_protection_domain` TEXT, - `scale_io_storage_pool` TEXT, - `scale_io_storage_mode` TEXT, - `scale_io_volume_name` TEXT, - `storage_os_volume_name` TEXT, - `storage_os_volume_namespace` TEXT, - `csi_driver` TEXT, - `csi_volume_attributes` TEXT, - `ephemeral_volume_claim_template` TEXT, - `pod_name` TEXT -); - -CREATE TABLE kubernetes_resource_quotas ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `hard` TEXT, - `scopes` TEXT, - `scope_selector` TEXT, - `status_hard` TEXT, - `status_used` TEXT -); - -CREATE TABLE kubernetes_secrets ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `immutable` INTEGER, - `type` TEXT -); - -CREATE TABLE kubernetes_service_accounts ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `secrets` TEXT, - `image_pull_secrets` TEXT, - `automount_service_account_token` INTEGER -); - -CREATE TABLE kubernetes_services ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `ports` TEXT, - `selector` TEXT, - `cluster_ip` TEXT, - `cluster_ips` TEXT, - `type` TEXT, - `external_ips` TEXT, - `session_affinity` TEXT, - `load_balancer_ip` TEXT, - `load_balancer_source_ranges` TEXT, - `external_name` TEXT, - `external_traffic_policy` TEXT, - `health_check_node_port` INTEGER, - `publish_not_ready_addresses` INTEGER, - `session_affinity_config` TEXT, - `ip_families` TEXT, - `ip_family_policy` TEXT, - `allocate_load_balancer_node_ports` INTEGER, - `load_balancer_class` TEXT, - `internal_traffic_policy` TEXT, - `load_balancer` TEXT, - `conditions` TEXT -); - -CREATE TABLE kubernetes_api_resources ( - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `singular_name` TEXT, - `namespaced` INTEGER, - `group` TEXT, - `version` TEXT, - `kind` TEXT, - `verbs` TEXT, - `short_names` TEXT, - `categories` TEXT, - `storage_version_hash` TEXT, - `group_version` TEXT -); - -CREATE TABLE kubernetes_info ( - `cluster_uid` TEXT, - `cluster_name` TEXT, - `major` TEXT, - `minor` TEXT, - `git_version` TEXT, - `git_commit` TEXT, - `git_tree_state` TEXT, - `build_date` TEXT, - `go_version` TEXT, - `compiler` TEXT, - `platform` TEXT -); - -CREATE TABLE kubernetes_events ( - `time` BIGINT, - `event_type` TEXT, - `cluster_uid` TEXT, - `cluster_name` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `reporting_controller` TEXT, - `reporting_instance` TEXT, - `action` TEXT, - `reason` TEXT, - `note` TEXT, - `type` TEXT, - `regarding_kind` TEXT, - `regarding_namespace` TEXT, - `regarding_name` TEXT, - `regarding_uid` TEXT, - `related_kind` TEXT, - `related_namespace` TEXT, - `related_name` TEXT, - `related_uid` TEXT -); - -CREATE TABLE kubernetes_ingress_classes ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `controller` TEXT, - `parameters` TEXT -); - -CREATE TABLE kubernetes_ingresses ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `ingress_class_name` TEXT, - `default_backend` TEXT, - `tls` TEXT, - `rules` TEXT, - `load_balancer` TEXT -); - -CREATE TABLE kubernetes_network_policies ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `pod_selector` TEXT, - `policy_types` TEXT, - `type` TEXT, - `ports` TEXT, - `from_to` TEXT -); - -CREATE TABLE kubernetes_pod_disruption_budgets ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `min_available` TEXT, - `selector` TEXT, - `max_unavailable` TEXT, - `observed_generation` BIGINT, - `disrupted_pods` TEXT, - `disruptions_allowed` INTEGER, - `current_healthy` INTEGER, - `desired_healthy` INTEGER, - `expected_pods` INTEGER, - `conditions` TEXT -); - -CREATE TABLE kubernetes_pod_security_policies ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `privileged` INTEGER, - `default_add_capabilities` TEXT, - `required_drop_capabilities` TEXT, - `allowed_capabilities` TEXT, - `volumes` TEXT, - `host_network` INTEGER, - `host_ports` TEXT, - `host_pid` INTEGER, - `host_ipc` INTEGER, - `se_linux` TEXT, - `run_as_user` TEXT, - `run_as_group` TEXT, - `supplemental_groups` TEXT, - `fs_group` TEXT, - `read_only_root_filesystem` INTEGER, - `default_allow_privilege_escalation` INTEGER, - `allow_privilege_escalation` INTEGER, - `allowed_host_paths` TEXT, - `allowed_flex_volumes` TEXT, - `allowed_csi_drivers` TEXT, - `allowed_unsafe_sysctls` TEXT, - `forbidden_sysctls` TEXT, - `allowed_proc_mount_types` TEXT, - `runtime_class` TEXT -); - -CREATE TABLE kubernetes_cluster_role_binding_subjects ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `role_api_group` TEXT, - `role_name` TEXT, - `role_kind` TEXT, - `subject_name` TEXT, - `subject_kind` TEXT, - `subject_namespace` TEXT -); - -CREATE TABLE kubernetes_cluster_role_policy_rules ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `verbs` TEXT, - `api_groups` TEXT, - `resources` TEXT, - `resource_names` TEXT, - `non_resource_urls` TEXT, - `aggregation_rule` TEXT -); - -CREATE TABLE kubernetes_role_binding_subjects ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `role_name` TEXT, - `role_kind` TEXT, - `subject_name` TEXT, - `subject_kind` TEXT, - `subject_namespace` TEXT -); - -CREATE TABLE kubernetes_role_policy_rules ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `namespace` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `verbs` TEXT, - `api_groups` TEXT, - `resources` TEXT, - `resource_names` TEXT, - `non_resource_urls` TEXT -); - -CREATE TABLE kubernetes_csi_drivers ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `attach_required` INTEGER, - `pod_info_on_mount` INTEGER, - `volume_lifecycle_modes` TEXT, - `storage_capacity` INTEGER, - `fs_group_policy` TEXT, - `token_requests` TEXT, - `requires_republish` INTEGER -); - -CREATE TABLE kubernetes_csi_node_drivers ( - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `node_id` TEXT, - `topology_keys` TEXT, - `allocatable` TEXT -); - -CREATE TABLE kubernetes_storage_classes ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `provisioner` TEXT, - `parameters` TEXT, - `reclaim_policy` TEXT, - `mount_options` TEXT, - `allow_volume_expansion` INTEGER, - `volume_binding_mode` TEXT, - `allowed_topologies` TEXT -); - -CREATE TABLE kubernetes_volume_attachments ( - `uid` TEXT, - `cluster_name` TEXT, - `cluster_uid` TEXT, - `name` TEXT, - `creation_timestamp` BIGINT, - `labels` TEXT, - `annotations` TEXT, - `attacher` TEXT, - `source` TEXT, - `node_name` TEXT, - `attached` INTEGER, - `attachment_metadata` TEXT, - `attach_error` TEXT, - `detach_error` TEXT -); - -``` diff --git a/infrastructure/kubequery/docs/tables.json b/infrastructure/kubequery/docs/tables.json deleted file mode 100644 index 584da6d0a6..0000000000 --- a/infrastructure/kubequery/docs/tables.json +++ /dev/null @@ -1,7970 +0,0 @@ -{ - "tables": [ { - "name": "kubernetes_mutating_webhooks", - "columns": [ - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "client_config", - "type": "TEXT" - }, - { - "name": "rules", - "type": "TEXT" - }, - { - "name": "failure_policy", - "type": "TEXT" - }, - { - "name": "match_policy", - "type": "TEXT" - }, - { - "name": "namespace_selector", - "type": "TEXT" - }, - { - "name": "object_selector", - "type": "TEXT" - }, - { - "name": "side_effects", - "type": "TEXT" - }, - { - "name": "timeout_seconds", - "type": "INTEGER" - }, - { - "name": "admission_review_versions", - "type": "TEXT" - }, - { - "name": "reinvocation_policy", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_validating_webhooks", - "columns": [ - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "client_config", - "type": "TEXT" - }, - { - "name": "rules", - "type": "TEXT" - }, - { - "name": "failure_policy", - "type": "TEXT" - }, - { - "name": "match_policy", - "type": "TEXT" - }, - { - "name": "namespace_selector", - "type": "TEXT" - }, - { - "name": "object_selector", - "type": "TEXT" - }, - { - "name": "side_effects", - "type": "TEXT" - }, - { - "name": "timeout_seconds", - "type": "INTEGER" - }, - { - "name": "admission_review_versions", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_daemon_sets", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "se_linux_options_user", - "type": "TEXT" - }, - { - "name": "se_linux_options_role", - "type": "TEXT" - }, - { - "name": "se_linux_options_type", - "type": "TEXT" - }, - { - "name": "se_linux_options_level", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec_name", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec", - "type": "TEXT" - }, - { - "name": "windows_options_run_as_user_name", - "type": "TEXT" - }, - { - "name": "seccomp_profile_type", - "type": "TEXT" - }, - { - "name": "seccomp_profile_localhost_profile", - "type": "TEXT" - }, - { - "name": "run_as_user", - "type": "BIGINT" - }, - { - "name": "run_as_group", - "type": "BIGINT" - }, - { - "name": "run_as_non_root", - "type": "INTEGER" - }, - { - "name": "supplemental_groups", - "type": "TEXT" - }, - { - "name": "fs_group", - "type": "BIGINT" - }, - { - "name": "sysctls", - "type": "TEXT" - }, - { - "name": "fs_group_change_policy", - "type": "TEXT" - }, - { - "name": "node_affinity", - "type": "TEXT" - }, - { - "name": "pod_affinity", - "type": "TEXT" - }, - { - "name": "pod_anti_affinity", - "type": "TEXT" - }, - { - "name": "dns_config_nameservers", - "type": "TEXT" - }, - { - "name": "dns_config_searches", - "type": "TEXT" - }, - { - "name": "dns_config_options", - "type": "TEXT" - }, - { - "name": "node_selector", - "type": "TEXT" - }, - { - "name": "restart_policy", - "type": "TEXT" - }, - { - "name": "termination_grace_period_seconds", - "type": "BIGINT" - }, - { - "name": "active_deadline_seconds", - "type": "BIGINT" - }, - { - "name": "dns_policy", - "type": "TEXT" - }, - { - "name": "service_account_name", - "type": "TEXT" - }, - { - "name": "automount_service_account_token", - "type": "INTEGER" - }, - { - "name": "node_name", - "type": "TEXT" - }, - { - "name": "host_network", - "type": "INTEGER" - }, - { - "name": "host_pid", - "type": "INTEGER" - }, - { - "name": "host_ipc", - "type": "INTEGER" - }, - { - "name": "share_process_namespace", - "type": "INTEGER" - }, - { - "name": "image_pull_secrets", - "type": "TEXT" - }, - { - "name": "hostname", - "type": "TEXT" - }, - { - "name": "subdomain", - "type": "TEXT" - }, - { - "name": "scheduler_name", - "type": "TEXT" - }, - { - "name": "tolerations", - "type": "TEXT" - }, - { - "name": "host_aliases", - "type": "TEXT" - }, - { - "name": "priority_class_name", - "type": "TEXT" - }, - { - "name": "priority", - "type": "INTEGER" - }, - { - "name": "readiness_gates", - "type": "TEXT" - }, - { - "name": "runtime_class_name", - "type": "TEXT" - }, - { - "name": "enable_service_links", - "type": "INTEGER" - }, - { - "name": "preemption_policy", - "type": "TEXT" - }, - { - "name": "overhead", - "type": "TEXT" - }, - { - "name": "topology_spread_constraints", - "type": "TEXT" - }, - { - "name": "set_hostname_as_fqdn", - "type": "INTEGER" - }, - { - "name": "current_number_scheduled", - "type": "INTEGER" - }, - { - "name": "number_misscheduled", - "type": "INTEGER" - }, - { - "name": "desired_number_scheduled", - "type": "INTEGER" - }, - { - "name": "number_ready", - "type": "INTEGER" - }, - { - "name": "observed_generation", - "type": "BIGINT" - }, - { - "name": "updated_number_scheduled", - "type": "INTEGER" - }, - { - "name": "number_available", - "type": "INTEGER" - }, - { - "name": "number_unavailable", - "type": "INTEGER" - }, - { - "name": "collision_count", - "type": "INTEGER" - }, - { - "name": "conditions", - "type": "TEXT" - }, - { - "name": "selector", - "type": "TEXT" - }, - { - "name": "update_strategy", - "type": "TEXT" - }, - { - "name": "min_ready_seconds", - "type": "INTEGER" - }, - { - "name": "revision_history_limit", - "type": "INTEGER" - } - ] - }, - { - "name": "kubernetes_daemon_set_containers", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "se_linux_options_user", - "type": "TEXT" - }, - { - "name": "se_linux_options_role", - "type": "TEXT" - }, - { - "name": "se_linux_options_type", - "type": "TEXT" - }, - { - "name": "se_linux_options_level", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec_name", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec", - "type": "TEXT" - }, - { - "name": "windows_options_run_as_user_name", - "type": "TEXT" - }, - { - "name": "seccomp_profile_type", - "type": "TEXT" - }, - { - "name": "seccomp_profile_localhost_profile", - "type": "TEXT" - }, - { - "name": "run_as_user", - "type": "BIGINT" - }, - { - "name": "run_as_group", - "type": "BIGINT" - }, - { - "name": "run_as_non_root", - "type": "INTEGER" - }, - { - "name": "capabilities_add", - "type": "TEXT" - }, - { - "name": "capabilities_drop", - "type": "TEXT" - }, - { - "name": "privileged", - "type": "INTEGER" - }, - { - "name": "read_only_root_filesystem", - "type": "INTEGER" - }, - { - "name": "allow_privilege_escalation", - "type": "INTEGER" - }, - { - "name": "proc_mount", - "type": "TEXT" - }, - { - "name": "target_container_name", - "type": "TEXT" - }, - { - "name": "image", - "type": "TEXT" - }, - { - "name": "command", - "type": "TEXT" - }, - { - "name": "args", - "type": "TEXT" - }, - { - "name": "working_dir", - "type": "TEXT" - }, - { - "name": "ports", - "type": "TEXT" - }, - { - "name": "env_from", - "type": "TEXT" - }, - { - "name": "env", - "type": "TEXT" - }, - { - "name": "resource_limits", - "type": "TEXT" - }, - { - "name": "resource_requests", - "type": "TEXT" - }, - { - "name": "volume_mounts", - "type": "TEXT" - }, - { - "name": "volume_devices", - "type": "TEXT" - }, - { - "name": "liveness_probe", - "type": "TEXT" - }, - { - "name": "readiness_probe", - "type": "TEXT" - }, - { - "name": "startup_probe", - "type": "TEXT" - }, - { - "name": "lifecycle", - "type": "TEXT" - }, - { - "name": "termination_message_path", - "type": "TEXT" - }, - { - "name": "termination_message_policy", - "type": "TEXT" - }, - { - "name": "image_pull_policy", - "type": "TEXT" - }, - { - "name": "stdin", - "type": "INTEGER" - }, - { - "name": "stdin_once", - "type": "INTEGER" - }, - { - "name": "tty", - "type": "INTEGER" - }, - { - "name": "daemon_set_name", - "type": "TEXT" - }, - { - "name": "container_type", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_daemon_set_volumes", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "volume_type", - "type": "TEXT" - }, - { - "name": "fs_type", - "type": "TEXT" - }, - { - "name": "read_only", - "type": "INTEGER" - }, - { - "name": "secret_name", - "type": "TEXT" - }, - { - "name": "host_path_path", - "type": "TEXT" - }, - { - "name": "host_path_type", - "type": "TEXT" - }, - { - "name": "empty_dir_medium", - "type": "TEXT" - }, - { - "name": "empty_dir_size_limit", - "type": "TEXT" - }, - { - "name": "gce_persistent_disk_pd_name", - "type": "TEXT" - }, - { - "name": "gce_persistent_disk_partition", - "type": "INTEGER" - }, - { - "name": "aws_elastic_block_store_volume_id", - "type": "TEXT" - }, - { - "name": "aws_elastic_block_store_partition", - "type": "INTEGER" - }, - { - "name": "git_repo_repository", - "type": "TEXT" - }, - { - "name": "git_repo_revision", - "type": "TEXT" - }, - { - "name": "git_repo_directory", - "type": "TEXT" - }, - { - "name": "secret_items", - "type": "TEXT" - }, - { - "name": "secret_default_mode", - "type": "INTEGER" - }, - { - "name": "secret_optional", - "type": "INTEGER" - }, - { - "name": "nfs_server", - "type": "TEXT" - }, - { - "name": "nfs_path", - "type": "TEXT" - }, - { - "name": "iscsi_target_portal", - "type": "TEXT" - }, - { - "name": "iscsi_iqn", - "type": "TEXT" - }, - { - "name": "iscsi_lun", - "type": "INTEGER" - }, - { - "name": "iscsi_interface", - "type": "TEXT" - }, - { - "name": "iscsi_portals", - "type": "TEXT" - }, - { - "name": "iscsi_discovery_chap_auth", - "type": "INTEGER" - }, - { - "name": "iscsi_session_chap_auth", - "type": "INTEGER" - }, - { - "name": "iscsi_initiator_name", - "type": "TEXT" - }, - { - "name": "glusterfs_endpoints_name", - "type": "TEXT" - }, - { - "name": "glusterfs_path", - "type": "TEXT" - }, - { - "name": "persistent_volume_claim_name", - "type": "TEXT" - }, - { - "name": "rbd_ceph_monitors", - "type": "TEXT" - }, - { - "name": "rbd_image", - "type": "TEXT" - }, - { - "name": "rbd_pool", - "type": "TEXT" - }, - { - "name": "rbd_rados_user", - "type": "TEXT" - }, - { - "name": "rbd_keyring", - "type": "TEXT" - }, - { - "name": "flex_volume_driver", - "type": "TEXT" - }, - { - "name": "flex_volume_options", - "type": "TEXT" - }, - { - "name": "cinder_volume_id", - "type": "TEXT" - }, - { - "name": "ceph_fs_monitors", - "type": "TEXT" - }, - { - "name": "ceph_fs_path", - "type": "TEXT" - }, - { - "name": "ceph_fs_user", - "type": "TEXT" - }, - { - "name": "ceph_fs_secret_file", - "type": "TEXT" - }, - { - "name": "flocker_dataset_name", - "type": "TEXT" - }, - { - "name": "flocker_dataset_uuid", - "type": "TEXT" - }, - { - "name": "downward_api_items", - "type": "TEXT" - }, - { - "name": "downward_api_default_mode", - "type": "INTEGER" - }, - { - "name": "fc_target_ww_ns", - "type": "TEXT" - }, - { - "name": "fc_lun", - "type": "INTEGER" - }, - { - "name": "fc_ww_ids", - "type": "TEXT" - }, - { - "name": "azure_file_share_name", - "type": "TEXT" - }, - { - "name": "config_map_name", - "type": "TEXT" - }, - { - "name": "config_map_items", - "type": "TEXT" - }, - { - "name": "config_map_default_mode", - "type": "INTEGER" - }, - { - "name": "config_map_optional", - "type": "INTEGER" - }, - { - "name": "vsphere_volume_volume_path", - "type": "TEXT" - }, - { - "name": "vsphere_volume_storage_policy_name", - "type": "TEXT" - }, - { - "name": "vsphere_volume_storage_policy_id", - "type": "TEXT" - }, - { - "name": "quobyte_registry", - "type": "TEXT" - }, - { - "name": "quobyte_volume", - "type": "TEXT" - }, - { - "name": "quobyte_user", - "type": "TEXT" - }, - { - "name": "quobyte_group", - "type": "TEXT" - }, - { - "name": "quobyte_tenant", - "type": "TEXT" - }, - { - "name": "azure_disk_disk_name", - "type": "TEXT" - }, - { - "name": "azure_disk_data_disk_uri", - "type": "TEXT" - }, - { - "name": "azure_disk_caching_mode", - "type": "TEXT" - }, - { - "name": "azure_disk_kind", - "type": "TEXT" - }, - { - "name": "photon_persistent_disk_pd_id", - "type": "TEXT" - }, - { - "name": "projected_sources", - "type": "TEXT" - }, - { - "name": "projected_default_mode", - "type": "INTEGER" - }, - { - "name": "portworx_volume_id", - "type": "TEXT" - }, - { - "name": "scale_io_gateway", - "type": "TEXT" - }, - { - "name": "scale_io_system", - "type": "TEXT" - }, - { - "name": "scale_iossl_enabled", - "type": "INTEGER" - }, - { - "name": "scale_io_protection_domain", - "type": "TEXT" - }, - { - "name": "scale_io_storage_pool", - "type": "TEXT" - }, - { - "name": "scale_io_storage_mode", - "type": "TEXT" - }, - { - "name": "scale_io_volume_name", - "type": "TEXT" - }, - { - "name": "storage_os_volume_name", - "type": "TEXT" - }, - { - "name": "storage_os_volume_namespace", - "type": "TEXT" - }, - { - "name": "csi_driver", - "type": "TEXT" - }, - { - "name": "csi_volume_attributes", - "type": "TEXT" - }, - { - "name": "ephemeral_volume_claim_template", - "type": "TEXT" - }, - { - "name": "daemon_set_name", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_deployments", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "se_linux_options_user", - "type": "TEXT" - }, - { - "name": "se_linux_options_role", - "type": "TEXT" - }, - { - "name": "se_linux_options_type", - "type": "TEXT" - }, - { - "name": "se_linux_options_level", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec_name", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec", - "type": "TEXT" - }, - { - "name": "windows_options_run_as_user_name", - "type": "TEXT" - }, - { - "name": "seccomp_profile_type", - "type": "TEXT" - }, - { - "name": "seccomp_profile_localhost_profile", - "type": "TEXT" - }, - { - "name": "run_as_user", - "type": "BIGINT" - }, - { - "name": "run_as_group", - "type": "BIGINT" - }, - { - "name": "run_as_non_root", - "type": "INTEGER" - }, - { - "name": "supplemental_groups", - "type": "TEXT" - }, - { - "name": "fs_group", - "type": "BIGINT" - }, - { - "name": "sysctls", - "type": "TEXT" - }, - { - "name": "fs_group_change_policy", - "type": "TEXT" - }, - { - "name": "node_affinity", - "type": "TEXT" - }, - { - "name": "pod_affinity", - "type": "TEXT" - }, - { - "name": "pod_anti_affinity", - "type": "TEXT" - }, - { - "name": "dns_config_nameservers", - "type": "TEXT" - }, - { - "name": "dns_config_searches", - "type": "TEXT" - }, - { - "name": "dns_config_options", - "type": "TEXT" - }, - { - "name": "node_selector", - "type": "TEXT" - }, - { - "name": "restart_policy", - "type": "TEXT" - }, - { - "name": "termination_grace_period_seconds", - "type": "BIGINT" - }, - { - "name": "active_deadline_seconds", - "type": "BIGINT" - }, - { - "name": "dns_policy", - "type": "TEXT" - }, - { - "name": "service_account_name", - "type": "TEXT" - }, - { - "name": "automount_service_account_token", - "type": "INTEGER" - }, - { - "name": "node_name", - "type": "TEXT" - }, - { - "name": "host_network", - "type": "INTEGER" - }, - { - "name": "host_pid", - "type": "INTEGER" - }, - { - "name": "host_ipc", - "type": "INTEGER" - }, - { - "name": "share_process_namespace", - "type": "INTEGER" - }, - { - "name": "image_pull_secrets", - "type": "TEXT" - }, - { - "name": "hostname", - "type": "TEXT" - }, - { - "name": "subdomain", - "type": "TEXT" - }, - { - "name": "scheduler_name", - "type": "TEXT" - }, - { - "name": "tolerations", - "type": "TEXT" - }, - { - "name": "host_aliases", - "type": "TEXT" - }, - { - "name": "priority_class_name", - "type": "TEXT" - }, - { - "name": "priority", - "type": "INTEGER" - }, - { - "name": "readiness_gates", - "type": "TEXT" - }, - { - "name": "runtime_class_name", - "type": "TEXT" - }, - { - "name": "enable_service_links", - "type": "INTEGER" - }, - { - "name": "preemption_policy", - "type": "TEXT" - }, - { - "name": "overhead", - "type": "TEXT" - }, - { - "name": "topology_spread_constraints", - "type": "TEXT" - }, - { - "name": "set_hostname_as_fqdn", - "type": "INTEGER" - }, - { - "name": "observed_generation", - "type": "BIGINT" - }, - { - "name": "replicas", - "type": "INTEGER" - }, - { - "name": "updated_replicas", - "type": "INTEGER" - }, - { - "name": "ready_replicas", - "type": "INTEGER" - }, - { - "name": "available_replicas", - "type": "INTEGER" - }, - { - "name": "unavailable_replicas", - "type": "INTEGER" - }, - { - "name": "conditions", - "type": "TEXT" - }, - { - "name": "collision_count", - "type": "INTEGER" - }, - { - "name": "deployment_replicas", - "type": "INTEGER" - }, - { - "name": "selector", - "type": "TEXT" - }, - { - "name": "strategy", - "type": "TEXT" - }, - { - "name": "min_ready_seconds", - "type": "INTEGER" - }, - { - "name": "revision_history_limit", - "type": "INTEGER" - }, - { - "name": "paused", - "type": "INTEGER" - }, - { - "name": "progress_deadline_seconds", - "type": "INTEGER" - } - ] - }, - { - "name": "kubernetes_deployments_containers", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "se_linux_options_user", - "type": "TEXT" - }, - { - "name": "se_linux_options_role", - "type": "TEXT" - }, - { - "name": "se_linux_options_type", - "type": "TEXT" - }, - { - "name": "se_linux_options_level", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec_name", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec", - "type": "TEXT" - }, - { - "name": "windows_options_run_as_user_name", - "type": "TEXT" - }, - { - "name": "seccomp_profile_type", - "type": "TEXT" - }, - { - "name": "seccomp_profile_localhost_profile", - "type": "TEXT" - }, - { - "name": "run_as_user", - "type": "BIGINT" - }, - { - "name": "run_as_group", - "type": "BIGINT" - }, - { - "name": "run_as_non_root", - "type": "INTEGER" - }, - { - "name": "capabilities_add", - "type": "TEXT" - }, - { - "name": "capabilities_drop", - "type": "TEXT" - }, - { - "name": "privileged", - "type": "INTEGER" - }, - { - "name": "read_only_root_filesystem", - "type": "INTEGER" - }, - { - "name": "allow_privilege_escalation", - "type": "INTEGER" - }, - { - "name": "proc_mount", - "type": "TEXT" - }, - { - "name": "target_container_name", - "type": "TEXT" - }, - { - "name": "image", - "type": "TEXT" - }, - { - "name": "command", - "type": "TEXT" - }, - { - "name": "args", - "type": "TEXT" - }, - { - "name": "working_dir", - "type": "TEXT" - }, - { - "name": "ports", - "type": "TEXT" - }, - { - "name": "env_from", - "type": "TEXT" - }, - { - "name": "env", - "type": "TEXT" - }, - { - "name": "resource_limits", - "type": "TEXT" - }, - { - "name": "resource_requests", - "type": "TEXT" - }, - { - "name": "volume_mounts", - "type": "TEXT" - }, - { - "name": "volume_devices", - "type": "TEXT" - }, - { - "name": "liveness_probe", - "type": "TEXT" - }, - { - "name": "readiness_probe", - "type": "TEXT" - }, - { - "name": "startup_probe", - "type": "TEXT" - }, - { - "name": "lifecycle", - "type": "TEXT" - }, - { - "name": "termination_message_path", - "type": "TEXT" - }, - { - "name": "termination_message_policy", - "type": "TEXT" - }, - { - "name": "image_pull_policy", - "type": "TEXT" - }, - { - "name": "stdin", - "type": "INTEGER" - }, - { - "name": "stdin_once", - "type": "INTEGER" - }, - { - "name": "tty", - "type": "INTEGER" - }, - { - "name": "deployment_name", - "type": "TEXT" - }, - { - "name": "container_type", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_deployments_volumes", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "volume_type", - "type": "TEXT" - }, - { - "name": "fs_type", - "type": "TEXT" - }, - { - "name": "read_only", - "type": "INTEGER" - }, - { - "name": "secret_name", - "type": "TEXT" - }, - { - "name": "host_path_path", - "type": "TEXT" - }, - { - "name": "host_path_type", - "type": "TEXT" - }, - { - "name": "empty_dir_medium", - "type": "TEXT" - }, - { - "name": "empty_dir_size_limit", - "type": "TEXT" - }, - { - "name": "gce_persistent_disk_pd_name", - "type": "TEXT" - }, - { - "name": "gce_persistent_disk_partition", - "type": "INTEGER" - }, - { - "name": "aws_elastic_block_store_volume_id", - "type": "TEXT" - }, - { - "name": "aws_elastic_block_store_partition", - "type": "INTEGER" - }, - { - "name": "git_repo_repository", - "type": "TEXT" - }, - { - "name": "git_repo_revision", - "type": "TEXT" - }, - { - "name": "git_repo_directory", - "type": "TEXT" - }, - { - "name": "secret_items", - "type": "TEXT" - }, - { - "name": "secret_default_mode", - "type": "INTEGER" - }, - { - "name": "secret_optional", - "type": "INTEGER" - }, - { - "name": "nfs_server", - "type": "TEXT" - }, - { - "name": "nfs_path", - "type": "TEXT" - }, - { - "name": "iscsi_target_portal", - "type": "TEXT" - }, - { - "name": "iscsi_iqn", - "type": "TEXT" - }, - { - "name": "iscsi_lun", - "type": "INTEGER" - }, - { - "name": "iscsi_interface", - "type": "TEXT" - }, - { - "name": "iscsi_portals", - "type": "TEXT" - }, - { - "name": "iscsi_discovery_chap_auth", - "type": "INTEGER" - }, - { - "name": "iscsi_session_chap_auth", - "type": "INTEGER" - }, - { - "name": "iscsi_initiator_name", - "type": "TEXT" - }, - { - "name": "glusterfs_endpoints_name", - "type": "TEXT" - }, - { - "name": "glusterfs_path", - "type": "TEXT" - }, - { - "name": "persistent_volume_claim_name", - "type": "TEXT" - }, - { - "name": "rbd_ceph_monitors", - "type": "TEXT" - }, - { - "name": "rbd_image", - "type": "TEXT" - }, - { - "name": "rbd_pool", - "type": "TEXT" - }, - { - "name": "rbd_rados_user", - "type": "TEXT" - }, - { - "name": "rbd_keyring", - "type": "TEXT" - }, - { - "name": "flex_volume_driver", - "type": "TEXT" - }, - { - "name": "flex_volume_options", - "type": "TEXT" - }, - { - "name": "cinder_volume_id", - "type": "TEXT" - }, - { - "name": "ceph_fs_monitors", - "type": "TEXT" - }, - { - "name": "ceph_fs_path", - "type": "TEXT" - }, - { - "name": "ceph_fs_user", - "type": "TEXT" - }, - { - "name": "ceph_fs_secret_file", - "type": "TEXT" - }, - { - "name": "flocker_dataset_name", - "type": "TEXT" - }, - { - "name": "flocker_dataset_uuid", - "type": "TEXT" - }, - { - "name": "downward_api_items", - "type": "TEXT" - }, - { - "name": "downward_api_default_mode", - "type": "INTEGER" - }, - { - "name": "fc_target_ww_ns", - "type": "TEXT" - }, - { - "name": "fc_lun", - "type": "INTEGER" - }, - { - "name": "fc_ww_ids", - "type": "TEXT" - }, - { - "name": "azure_file_share_name", - "type": "TEXT" - }, - { - "name": "config_map_name", - "type": "TEXT" - }, - { - "name": "config_map_items", - "type": "TEXT" - }, - { - "name": "config_map_default_mode", - "type": "INTEGER" - }, - { - "name": "config_map_optional", - "type": "INTEGER" - }, - { - "name": "vsphere_volume_volume_path", - "type": "TEXT" - }, - { - "name": "vsphere_volume_storage_policy_name", - "type": "TEXT" - }, - { - "name": "vsphere_volume_storage_policy_id", - "type": "TEXT" - }, - { - "name": "quobyte_registry", - "type": "TEXT" - }, - { - "name": "quobyte_volume", - "type": "TEXT" - }, - { - "name": "quobyte_user", - "type": "TEXT" - }, - { - "name": "quobyte_group", - "type": "TEXT" - }, - { - "name": "quobyte_tenant", - "type": "TEXT" - }, - { - "name": "azure_disk_disk_name", - "type": "TEXT" - }, - { - "name": "azure_disk_data_disk_uri", - "type": "TEXT" - }, - { - "name": "azure_disk_caching_mode", - "type": "TEXT" - }, - { - "name": "azure_disk_kind", - "type": "TEXT" - }, - { - "name": "photon_persistent_disk_pd_id", - "type": "TEXT" - }, - { - "name": "projected_sources", - "type": "TEXT" - }, - { - "name": "projected_default_mode", - "type": "INTEGER" - }, - { - "name": "portworx_volume_id", - "type": "TEXT" - }, - { - "name": "scale_io_gateway", - "type": "TEXT" - }, - { - "name": "scale_io_system", - "type": "TEXT" - }, - { - "name": "scale_iossl_enabled", - "type": "INTEGER" - }, - { - "name": "scale_io_protection_domain", - "type": "TEXT" - }, - { - "name": "scale_io_storage_pool", - "type": "TEXT" - }, - { - "name": "scale_io_storage_mode", - "type": "TEXT" - }, - { - "name": "scale_io_volume_name", - "type": "TEXT" - }, - { - "name": "storage_os_volume_name", - "type": "TEXT" - }, - { - "name": "storage_os_volume_namespace", - "type": "TEXT" - }, - { - "name": "csi_driver", - "type": "TEXT" - }, - { - "name": "csi_volume_attributes", - "type": "TEXT" - }, - { - "name": "ephemeral_volume_claim_template", - "type": "TEXT" - }, - { - "name": "deployment_name", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_replica_sets", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "se_linux_options_user", - "type": "TEXT" - }, - { - "name": "se_linux_options_role", - "type": "TEXT" - }, - { - "name": "se_linux_options_type", - "type": "TEXT" - }, - { - "name": "se_linux_options_level", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec_name", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec", - "type": "TEXT" - }, - { - "name": "windows_options_run_as_user_name", - "type": "TEXT" - }, - { - "name": "seccomp_profile_type", - "type": "TEXT" - }, - { - "name": "seccomp_profile_localhost_profile", - "type": "TEXT" - }, - { - "name": "run_as_user", - "type": "BIGINT" - }, - { - "name": "run_as_group", - "type": "BIGINT" - }, - { - "name": "run_as_non_root", - "type": "INTEGER" - }, - { - "name": "supplemental_groups", - "type": "TEXT" - }, - { - "name": "fs_group", - "type": "BIGINT" - }, - { - "name": "sysctls", - "type": "TEXT" - }, - { - "name": "fs_group_change_policy", - "type": "TEXT" - }, - { - "name": "node_affinity", - "type": "TEXT" - }, - { - "name": "pod_affinity", - "type": "TEXT" - }, - { - "name": "pod_anti_affinity", - "type": "TEXT" - }, - { - "name": "dns_config_nameservers", - "type": "TEXT" - }, - { - "name": "dns_config_searches", - "type": "TEXT" - }, - { - "name": "dns_config_options", - "type": "TEXT" - }, - { - "name": "node_selector", - "type": "TEXT" - }, - { - "name": "restart_policy", - "type": "TEXT" - }, - { - "name": "termination_grace_period_seconds", - "type": "BIGINT" - }, - { - "name": "active_deadline_seconds", - "type": "BIGINT" - }, - { - "name": "dns_policy", - "type": "TEXT" - }, - { - "name": "service_account_name", - "type": "TEXT" - }, - { - "name": "automount_service_account_token", - "type": "INTEGER" - }, - { - "name": "node_name", - "type": "TEXT" - }, - { - "name": "host_network", - "type": "INTEGER" - }, - { - "name": "host_pid", - "type": "INTEGER" - }, - { - "name": "host_ipc", - "type": "INTEGER" - }, - { - "name": "share_process_namespace", - "type": "INTEGER" - }, - { - "name": "image_pull_secrets", - "type": "TEXT" - }, - { - "name": "hostname", - "type": "TEXT" - }, - { - "name": "subdomain", - "type": "TEXT" - }, - { - "name": "scheduler_name", - "type": "TEXT" - }, - { - "name": "tolerations", - "type": "TEXT" - }, - { - "name": "host_aliases", - "type": "TEXT" - }, - { - "name": "priority_class_name", - "type": "TEXT" - }, - { - "name": "priority", - "type": "INTEGER" - }, - { - "name": "readiness_gates", - "type": "TEXT" - }, - { - "name": "runtime_class_name", - "type": "TEXT" - }, - { - "name": "enable_service_links", - "type": "INTEGER" - }, - { - "name": "preemption_policy", - "type": "TEXT" - }, - { - "name": "overhead", - "type": "TEXT" - }, - { - "name": "topology_spread_constraints", - "type": "TEXT" - }, - { - "name": "set_hostname_as_fqdn", - "type": "INTEGER" - }, - { - "name": "replicas", - "type": "INTEGER" - }, - { - "name": "fully_labeled_replicas", - "type": "INTEGER" - }, - { - "name": "ready_replicas", - "type": "INTEGER" - }, - { - "name": "available_replicas", - "type": "INTEGER" - }, - { - "name": "observed_generation", - "type": "BIGINT" - }, - { - "name": "conditions", - "type": "TEXT" - }, - { - "name": "replica_set_replicas", - "type": "INTEGER" - }, - { - "name": "min_ready_seconds", - "type": "INTEGER" - }, - { - "name": "selector", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_replica_set_containers", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "se_linux_options_user", - "type": "TEXT" - }, - { - "name": "se_linux_options_role", - "type": "TEXT" - }, - { - "name": "se_linux_options_type", - "type": "TEXT" - }, - { - "name": "se_linux_options_level", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec_name", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec", - "type": "TEXT" - }, - { - "name": "windows_options_run_as_user_name", - "type": "TEXT" - }, - { - "name": "seccomp_profile_type", - "type": "TEXT" - }, - { - "name": "seccomp_profile_localhost_profile", - "type": "TEXT" - }, - { - "name": "run_as_user", - "type": "BIGINT" - }, - { - "name": "run_as_group", - "type": "BIGINT" - }, - { - "name": "run_as_non_root", - "type": "INTEGER" - }, - { - "name": "capabilities_add", - "type": "TEXT" - }, - { - "name": "capabilities_drop", - "type": "TEXT" - }, - { - "name": "privileged", - "type": "INTEGER" - }, - { - "name": "read_only_root_filesystem", - "type": "INTEGER" - }, - { - "name": "allow_privilege_escalation", - "type": "INTEGER" - }, - { - "name": "proc_mount", - "type": "TEXT" - }, - { - "name": "target_container_name", - "type": "TEXT" - }, - { - "name": "image", - "type": "TEXT" - }, - { - "name": "command", - "type": "TEXT" - }, - { - "name": "args", - "type": "TEXT" - }, - { - "name": "working_dir", - "type": "TEXT" - }, - { - "name": "ports", - "type": "TEXT" - }, - { - "name": "env_from", - "type": "TEXT" - }, - { - "name": "env", - "type": "TEXT" - }, - { - "name": "resource_limits", - "type": "TEXT" - }, - { - "name": "resource_requests", - "type": "TEXT" - }, - { - "name": "volume_mounts", - "type": "TEXT" - }, - { - "name": "volume_devices", - "type": "TEXT" - }, - { - "name": "liveness_probe", - "type": "TEXT" - }, - { - "name": "readiness_probe", - "type": "TEXT" - }, - { - "name": "startup_probe", - "type": "TEXT" - }, - { - "name": "lifecycle", - "type": "TEXT" - }, - { - "name": "termination_message_path", - "type": "TEXT" - }, - { - "name": "termination_message_policy", - "type": "TEXT" - }, - { - "name": "image_pull_policy", - "type": "TEXT" - }, - { - "name": "stdin", - "type": "INTEGER" - }, - { - "name": "stdin_once", - "type": "INTEGER" - }, - { - "name": "tty", - "type": "INTEGER" - }, - { - "name": "replica_set_name", - "type": "TEXT" - }, - { - "name": "container_type", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_replica_set_volumes", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "volume_type", - "type": "TEXT" - }, - { - "name": "fs_type", - "type": "TEXT" - }, - { - "name": "read_only", - "type": "INTEGER" - }, - { - "name": "secret_name", - "type": "TEXT" - }, - { - "name": "host_path_path", - "type": "TEXT" - }, - { - "name": "host_path_type", - "type": "TEXT" - }, - { - "name": "empty_dir_medium", - "type": "TEXT" - }, - { - "name": "empty_dir_size_limit", - "type": "TEXT" - }, - { - "name": "gce_persistent_disk_pd_name", - "type": "TEXT" - }, - { - "name": "gce_persistent_disk_partition", - "type": "INTEGER" - }, - { - "name": "aws_elastic_block_store_volume_id", - "type": "TEXT" - }, - { - "name": "aws_elastic_block_store_partition", - "type": "INTEGER" - }, - { - "name": "git_repo_repository", - "type": "TEXT" - }, - { - "name": "git_repo_revision", - "type": "TEXT" - }, - { - "name": "git_repo_directory", - "type": "TEXT" - }, - { - "name": "secret_items", - "type": "TEXT" - }, - { - "name": "secret_default_mode", - "type": "INTEGER" - }, - { - "name": "secret_optional", - "type": "INTEGER" - }, - { - "name": "nfs_server", - "type": "TEXT" - }, - { - "name": "nfs_path", - "type": "TEXT" - }, - { - "name": "iscsi_target_portal", - "type": "TEXT" - }, - { - "name": "iscsi_iqn", - "type": "TEXT" - }, - { - "name": "iscsi_lun", - "type": "INTEGER" - }, - { - "name": "iscsi_interface", - "type": "TEXT" - }, - { - "name": "iscsi_portals", - "type": "TEXT" - }, - { - "name": "iscsi_discovery_chap_auth", - "type": "INTEGER" - }, - { - "name": "iscsi_session_chap_auth", - "type": "INTEGER" - }, - { - "name": "iscsi_initiator_name", - "type": "TEXT" - }, - { - "name": "glusterfs_endpoints_name", - "type": "TEXT" - }, - { - "name": "glusterfs_path", - "type": "TEXT" - }, - { - "name": "persistent_volume_claim_name", - "type": "TEXT" - }, - { - "name": "rbd_ceph_monitors", - "type": "TEXT" - }, - { - "name": "rbd_image", - "type": "TEXT" - }, - { - "name": "rbd_pool", - "type": "TEXT" - }, - { - "name": "rbd_rados_user", - "type": "TEXT" - }, - { - "name": "rbd_keyring", - "type": "TEXT" - }, - { - "name": "flex_volume_driver", - "type": "TEXT" - }, - { - "name": "flex_volume_options", - "type": "TEXT" - }, - { - "name": "cinder_volume_id", - "type": "TEXT" - }, - { - "name": "ceph_fs_monitors", - "type": "TEXT" - }, - { - "name": "ceph_fs_path", - "type": "TEXT" - }, - { - "name": "ceph_fs_user", - "type": "TEXT" - }, - { - "name": "ceph_fs_secret_file", - "type": "TEXT" - }, - { - "name": "flocker_dataset_name", - "type": "TEXT" - }, - { - "name": "flocker_dataset_uuid", - "type": "TEXT" - }, - { - "name": "downward_api_items", - "type": "TEXT" - }, - { - "name": "downward_api_default_mode", - "type": "INTEGER" - }, - { - "name": "fc_target_ww_ns", - "type": "TEXT" - }, - { - "name": "fc_lun", - "type": "INTEGER" - }, - { - "name": "fc_ww_ids", - "type": "TEXT" - }, - { - "name": "azure_file_share_name", - "type": "TEXT" - }, - { - "name": "config_map_name", - "type": "TEXT" - }, - { - "name": "config_map_items", - "type": "TEXT" - }, - { - "name": "config_map_default_mode", - "type": "INTEGER" - }, - { - "name": "config_map_optional", - "type": "INTEGER" - }, - { - "name": "vsphere_volume_volume_path", - "type": "TEXT" - }, - { - "name": "vsphere_volume_storage_policy_name", - "type": "TEXT" - }, - { - "name": "vsphere_volume_storage_policy_id", - "type": "TEXT" - }, - { - "name": "quobyte_registry", - "type": "TEXT" - }, - { - "name": "quobyte_volume", - "type": "TEXT" - }, - { - "name": "quobyte_user", - "type": "TEXT" - }, - { - "name": "quobyte_group", - "type": "TEXT" - }, - { - "name": "quobyte_tenant", - "type": "TEXT" - }, - { - "name": "azure_disk_disk_name", - "type": "TEXT" - }, - { - "name": "azure_disk_data_disk_uri", - "type": "TEXT" - }, - { - "name": "azure_disk_caching_mode", - "type": "TEXT" - }, - { - "name": "azure_disk_kind", - "type": "TEXT" - }, - { - "name": "photon_persistent_disk_pd_id", - "type": "TEXT" - }, - { - "name": "projected_sources", - "type": "TEXT" - }, - { - "name": "projected_default_mode", - "type": "INTEGER" - }, - { - "name": "portworx_volume_id", - "type": "TEXT" - }, - { - "name": "scale_io_gateway", - "type": "TEXT" - }, - { - "name": "scale_io_system", - "type": "TEXT" - }, - { - "name": "scale_iossl_enabled", - "type": "INTEGER" - }, - { - "name": "scale_io_protection_domain", - "type": "TEXT" - }, - { - "name": "scale_io_storage_pool", - "type": "TEXT" - }, - { - "name": "scale_io_storage_mode", - "type": "TEXT" - }, - { - "name": "scale_io_volume_name", - "type": "TEXT" - }, - { - "name": "storage_os_volume_name", - "type": "TEXT" - }, - { - "name": "storage_os_volume_namespace", - "type": "TEXT" - }, - { - "name": "csi_driver", - "type": "TEXT" - }, - { - "name": "csi_volume_attributes", - "type": "TEXT" - }, - { - "name": "ephemeral_volume_claim_template", - "type": "TEXT" - }, - { - "name": "replica_set_name", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_stateful_sets", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "se_linux_options_user", - "type": "TEXT" - }, - { - "name": "se_linux_options_role", - "type": "TEXT" - }, - { - "name": "se_linux_options_type", - "type": "TEXT" - }, - { - "name": "se_linux_options_level", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec_name", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec", - "type": "TEXT" - }, - { - "name": "windows_options_run_as_user_name", - "type": "TEXT" - }, - { - "name": "seccomp_profile_type", - "type": "TEXT" - }, - { - "name": "seccomp_profile_localhost_profile", - "type": "TEXT" - }, - { - "name": "run_as_user", - "type": "BIGINT" - }, - { - "name": "run_as_group", - "type": "BIGINT" - }, - { - "name": "run_as_non_root", - "type": "INTEGER" - }, - { - "name": "supplemental_groups", - "type": "TEXT" - }, - { - "name": "fs_group", - "type": "BIGINT" - }, - { - "name": "sysctls", - "type": "TEXT" - }, - { - "name": "fs_group_change_policy", - "type": "TEXT" - }, - { - "name": "node_affinity", - "type": "TEXT" - }, - { - "name": "pod_affinity", - "type": "TEXT" - }, - { - "name": "pod_anti_affinity", - "type": "TEXT" - }, - { - "name": "dns_config_nameservers", - "type": "TEXT" - }, - { - "name": "dns_config_searches", - "type": "TEXT" - }, - { - "name": "dns_config_options", - "type": "TEXT" - }, - { - "name": "node_selector", - "type": "TEXT" - }, - { - "name": "restart_policy", - "type": "TEXT" - }, - { - "name": "termination_grace_period_seconds", - "type": "BIGINT" - }, - { - "name": "active_deadline_seconds", - "type": "BIGINT" - }, - { - "name": "dns_policy", - "type": "TEXT" - }, - { - "name": "service_account_name", - "type": "TEXT" - }, - { - "name": "automount_service_account_token", - "type": "INTEGER" - }, - { - "name": "node_name", - "type": "TEXT" - }, - { - "name": "host_network", - "type": "INTEGER" - }, - { - "name": "host_pid", - "type": "INTEGER" - }, - { - "name": "host_ipc", - "type": "INTEGER" - }, - { - "name": "share_process_namespace", - "type": "INTEGER" - }, - { - "name": "image_pull_secrets", - "type": "TEXT" - }, - { - "name": "hostname", - "type": "TEXT" - }, - { - "name": "subdomain", - "type": "TEXT" - }, - { - "name": "scheduler_name", - "type": "TEXT" - }, - { - "name": "tolerations", - "type": "TEXT" - }, - { - "name": "host_aliases", - "type": "TEXT" - }, - { - "name": "priority_class_name", - "type": "TEXT" - }, - { - "name": "priority", - "type": "INTEGER" - }, - { - "name": "readiness_gates", - "type": "TEXT" - }, - { - "name": "runtime_class_name", - "type": "TEXT" - }, - { - "name": "enable_service_links", - "type": "INTEGER" - }, - { - "name": "preemption_policy", - "type": "TEXT" - }, - { - "name": "overhead", - "type": "TEXT" - }, - { - "name": "topology_spread_constraints", - "type": "TEXT" - }, - { - "name": "set_hostname_as_fqdn", - "type": "INTEGER" - }, - { - "name": "observed_generation", - "type": "BIGINT" - }, - { - "name": "replicas", - "type": "INTEGER" - }, - { - "name": "ready_replicas", - "type": "INTEGER" - }, - { - "name": "current_replicas", - "type": "INTEGER" - }, - { - "name": "updated_replicas", - "type": "INTEGER" - }, - { - "name": "current_revision", - "type": "TEXT" - }, - { - "name": "update_revision", - "type": "TEXT" - }, - { - "name": "collision_count", - "type": "INTEGER" - }, - { - "name": "conditions", - "type": "TEXT" - }, - { - "name": "available_replicas", - "type": "INTEGER" - }, - { - "name": "stateful_set_replicas", - "type": "INTEGER" - }, - { - "name": "selector", - "type": "TEXT" - }, - { - "name": "volume_claim_templates", - "type": "TEXT" - }, - { - "name": "service_name", - "type": "TEXT" - }, - { - "name": "pod_management_policy", - "type": "TEXT" - }, - { - "name": "update_strategy", - "type": "TEXT" - }, - { - "name": "revision_history_limit", - "type": "INTEGER" - } - ] - }, - { - "name": "kubernetes_stateful_set_containers", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "se_linux_options_user", - "type": "TEXT" - }, - { - "name": "se_linux_options_role", - "type": "TEXT" - }, - { - "name": "se_linux_options_type", - "type": "TEXT" - }, - { - "name": "se_linux_options_level", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec_name", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec", - "type": "TEXT" - }, - { - "name": "windows_options_run_as_user_name", - "type": "TEXT" - }, - { - "name": "seccomp_profile_type", - "type": "TEXT" - }, - { - "name": "seccomp_profile_localhost_profile", - "type": "TEXT" - }, - { - "name": "run_as_user", - "type": "BIGINT" - }, - { - "name": "run_as_group", - "type": "BIGINT" - }, - { - "name": "run_as_non_root", - "type": "INTEGER" - }, - { - "name": "capabilities_add", - "type": "TEXT" - }, - { - "name": "capabilities_drop", - "type": "TEXT" - }, - { - "name": "privileged", - "type": "INTEGER" - }, - { - "name": "read_only_root_filesystem", - "type": "INTEGER" - }, - { - "name": "allow_privilege_escalation", - "type": "INTEGER" - }, - { - "name": "proc_mount", - "type": "TEXT" - }, - { - "name": "target_container_name", - "type": "TEXT" - }, - { - "name": "image", - "type": "TEXT" - }, - { - "name": "command", - "type": "TEXT" - }, - { - "name": "args", - "type": "TEXT" - }, - { - "name": "working_dir", - "type": "TEXT" - }, - { - "name": "ports", - "type": "TEXT" - }, - { - "name": "env_from", - "type": "TEXT" - }, - { - "name": "env", - "type": "TEXT" - }, - { - "name": "resource_limits", - "type": "TEXT" - }, - { - "name": "resource_requests", - "type": "TEXT" - }, - { - "name": "volume_mounts", - "type": "TEXT" - }, - { - "name": "volume_devices", - "type": "TEXT" - }, - { - "name": "liveness_probe", - "type": "TEXT" - }, - { - "name": "readiness_probe", - "type": "TEXT" - }, - { - "name": "startup_probe", - "type": "TEXT" - }, - { - "name": "lifecycle", - "type": "TEXT" - }, - { - "name": "termination_message_path", - "type": "TEXT" - }, - { - "name": "termination_message_policy", - "type": "TEXT" - }, - { - "name": "image_pull_policy", - "type": "TEXT" - }, - { - "name": "stdin", - "type": "INTEGER" - }, - { - "name": "stdin_once", - "type": "INTEGER" - }, - { - "name": "tty", - "type": "INTEGER" - }, - { - "name": "stateful_set_name", - "type": "TEXT" - }, - { - "name": "container_type", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_stateful_set_volumes", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "volume_type", - "type": "TEXT" - }, - { - "name": "fs_type", - "type": "TEXT" - }, - { - "name": "read_only", - "type": "INTEGER" - }, - { - "name": "secret_name", - "type": "TEXT" - }, - { - "name": "host_path_path", - "type": "TEXT" - }, - { - "name": "host_path_type", - "type": "TEXT" - }, - { - "name": "empty_dir_medium", - "type": "TEXT" - }, - { - "name": "empty_dir_size_limit", - "type": "TEXT" - }, - { - "name": "gce_persistent_disk_pd_name", - "type": "TEXT" - }, - { - "name": "gce_persistent_disk_partition", - "type": "INTEGER" - }, - { - "name": "aws_elastic_block_store_volume_id", - "type": "TEXT" - }, - { - "name": "aws_elastic_block_store_partition", - "type": "INTEGER" - }, - { - "name": "git_repo_repository", - "type": "TEXT" - }, - { - "name": "git_repo_revision", - "type": "TEXT" - }, - { - "name": "git_repo_directory", - "type": "TEXT" - }, - { - "name": "secret_items", - "type": "TEXT" - }, - { - "name": "secret_default_mode", - "type": "INTEGER" - }, - { - "name": "secret_optional", - "type": "INTEGER" - }, - { - "name": "nfs_server", - "type": "TEXT" - }, - { - "name": "nfs_path", - "type": "TEXT" - }, - { - "name": "iscsi_target_portal", - "type": "TEXT" - }, - { - "name": "iscsi_iqn", - "type": "TEXT" - }, - { - "name": "iscsi_lun", - "type": "INTEGER" - }, - { - "name": "iscsi_interface", - "type": "TEXT" - }, - { - "name": "iscsi_portals", - "type": "TEXT" - }, - { - "name": "iscsi_discovery_chap_auth", - "type": "INTEGER" - }, - { - "name": "iscsi_session_chap_auth", - "type": "INTEGER" - }, - { - "name": "iscsi_initiator_name", - "type": "TEXT" - }, - { - "name": "glusterfs_endpoints_name", - "type": "TEXT" - }, - { - "name": "glusterfs_path", - "type": "TEXT" - }, - { - "name": "persistent_volume_claim_name", - "type": "TEXT" - }, - { - "name": "rbd_ceph_monitors", - "type": "TEXT" - }, - { - "name": "rbd_image", - "type": "TEXT" - }, - { - "name": "rbd_pool", - "type": "TEXT" - }, - { - "name": "rbd_rados_user", - "type": "TEXT" - }, - { - "name": "rbd_keyring", - "type": "TEXT" - }, - { - "name": "flex_volume_driver", - "type": "TEXT" - }, - { - "name": "flex_volume_options", - "type": "TEXT" - }, - { - "name": "cinder_volume_id", - "type": "TEXT" - }, - { - "name": "ceph_fs_monitors", - "type": "TEXT" - }, - { - "name": "ceph_fs_path", - "type": "TEXT" - }, - { - "name": "ceph_fs_user", - "type": "TEXT" - }, - { - "name": "ceph_fs_secret_file", - "type": "TEXT" - }, - { - "name": "flocker_dataset_name", - "type": "TEXT" - }, - { - "name": "flocker_dataset_uuid", - "type": "TEXT" - }, - { - "name": "downward_api_items", - "type": "TEXT" - }, - { - "name": "downward_api_default_mode", - "type": "INTEGER" - }, - { - "name": "fc_target_ww_ns", - "type": "TEXT" - }, - { - "name": "fc_lun", - "type": "INTEGER" - }, - { - "name": "fc_ww_ids", - "type": "TEXT" - }, - { - "name": "azure_file_share_name", - "type": "TEXT" - }, - { - "name": "config_map_name", - "type": "TEXT" - }, - { - "name": "config_map_items", - "type": "TEXT" - }, - { - "name": "config_map_default_mode", - "type": "INTEGER" - }, - { - "name": "config_map_optional", - "type": "INTEGER" - }, - { - "name": "vsphere_volume_volume_path", - "type": "TEXT" - }, - { - "name": "vsphere_volume_storage_policy_name", - "type": "TEXT" - }, - { - "name": "vsphere_volume_storage_policy_id", - "type": "TEXT" - }, - { - "name": "quobyte_registry", - "type": "TEXT" - }, - { - "name": "quobyte_volume", - "type": "TEXT" - }, - { - "name": "quobyte_user", - "type": "TEXT" - }, - { - "name": "quobyte_group", - "type": "TEXT" - }, - { - "name": "quobyte_tenant", - "type": "TEXT" - }, - { - "name": "azure_disk_disk_name", - "type": "TEXT" - }, - { - "name": "azure_disk_data_disk_uri", - "type": "TEXT" - }, - { - "name": "azure_disk_caching_mode", - "type": "TEXT" - }, - { - "name": "azure_disk_kind", - "type": "TEXT" - }, - { - "name": "photon_persistent_disk_pd_id", - "type": "TEXT" - }, - { - "name": "projected_sources", - "type": "TEXT" - }, - { - "name": "projected_default_mode", - "type": "INTEGER" - }, - { - "name": "portworx_volume_id", - "type": "TEXT" - }, - { - "name": "scale_io_gateway", - "type": "TEXT" - }, - { - "name": "scale_io_system", - "type": "TEXT" - }, - { - "name": "scale_iossl_enabled", - "type": "INTEGER" - }, - { - "name": "scale_io_protection_domain", - "type": "TEXT" - }, - { - "name": "scale_io_storage_pool", - "type": "TEXT" - }, - { - "name": "scale_io_storage_mode", - "type": "TEXT" - }, - { - "name": "scale_io_volume_name", - "type": "TEXT" - }, - { - "name": "storage_os_volume_name", - "type": "TEXT" - }, - { - "name": "storage_os_volume_namespace", - "type": "TEXT" - }, - { - "name": "csi_driver", - "type": "TEXT" - }, - { - "name": "csi_volume_attributes", - "type": "TEXT" - }, - { - "name": "ephemeral_volume_claim_template", - "type": "TEXT" - }, - { - "name": "stateful_set_name", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_horizontal_pod_autoscalers", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "scale_target_ref", - "type": "TEXT" - }, - { - "name": "min_replicas", - "type": "INTEGER" - }, - { - "name": "max_replicas", - "type": "INTEGER" - }, - { - "name": "target_cpu_utilization_percentage", - "type": "INTEGER" - }, - { - "name": "observed_generation", - "type": "BIGINT" - }, - { - "name": "last_scale_time", - "type": "BIGINT" - }, - { - "name": "current_replicas", - "type": "INTEGER" - }, - { - "name": "desired_replicas", - "type": "INTEGER" - }, - { - "name": "current_cpu_utilization_percentage", - "type": "INTEGER" - } - ] - }, - { - "name": "kubernetes_cron_jobs", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "se_linux_options_user", - "type": "TEXT" - }, - { - "name": "se_linux_options_role", - "type": "TEXT" - }, - { - "name": "se_linux_options_type", - "type": "TEXT" - }, - { - "name": "se_linux_options_level", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec_name", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec", - "type": "TEXT" - }, - { - "name": "windows_options_run_as_user_name", - "type": "TEXT" - }, - { - "name": "seccomp_profile_type", - "type": "TEXT" - }, - { - "name": "seccomp_profile_localhost_profile", - "type": "TEXT" - }, - { - "name": "run_as_user", - "type": "BIGINT" - }, - { - "name": "run_as_group", - "type": "BIGINT" - }, - { - "name": "run_as_non_root", - "type": "INTEGER" - }, - { - "name": "supplemental_groups", - "type": "TEXT" - }, - { - "name": "fs_group", - "type": "BIGINT" - }, - { - "name": "sysctls", - "type": "TEXT" - }, - { - "name": "fs_group_change_policy", - "type": "TEXT" - }, - { - "name": "node_affinity", - "type": "TEXT" - }, - { - "name": "pod_affinity", - "type": "TEXT" - }, - { - "name": "pod_anti_affinity", - "type": "TEXT" - }, - { - "name": "dns_config_nameservers", - "type": "TEXT" - }, - { - "name": "dns_config_searches", - "type": "TEXT" - }, - { - "name": "dns_config_options", - "type": "TEXT" - }, - { - "name": "node_selector", - "type": "TEXT" - }, - { - "name": "restart_policy", - "type": "TEXT" - }, - { - "name": "termination_grace_period_seconds", - "type": "BIGINT" - }, - { - "name": "active_deadline_seconds", - "type": "BIGINT" - }, - { - "name": "dns_policy", - "type": "TEXT" - }, - { - "name": "service_account_name", - "type": "TEXT" - }, - { - "name": "automount_service_account_token", - "type": "INTEGER" - }, - { - "name": "node_name", - "type": "TEXT" - }, - { - "name": "host_network", - "type": "INTEGER" - }, - { - "name": "host_pid", - "type": "INTEGER" - }, - { - "name": "host_ipc", - "type": "INTEGER" - }, - { - "name": "share_process_namespace", - "type": "INTEGER" - }, - { - "name": "image_pull_secrets", - "type": "TEXT" - }, - { - "name": "hostname", - "type": "TEXT" - }, - { - "name": "subdomain", - "type": "TEXT" - }, - { - "name": "scheduler_name", - "type": "TEXT" - }, - { - "name": "tolerations", - "type": "TEXT" - }, - { - "name": "host_aliases", - "type": "TEXT" - }, - { - "name": "priority_class_name", - "type": "TEXT" - }, - { - "name": "priority", - "type": "INTEGER" - }, - { - "name": "readiness_gates", - "type": "TEXT" - }, - { - "name": "runtime_class_name", - "type": "TEXT" - }, - { - "name": "enable_service_links", - "type": "INTEGER" - }, - { - "name": "preemption_policy", - "type": "TEXT" - }, - { - "name": "overhead", - "type": "TEXT" - }, - { - "name": "topology_spread_constraints", - "type": "TEXT" - }, - { - "name": "set_hostname_as_fqdn", - "type": "INTEGER" - }, - { - "name": "active", - "type": "TEXT" - }, - { - "name": "last_schedule_time", - "type": "BIGINT" - }, - { - "name": "last_successful_time", - "type": "BIGINT" - }, - { - "name": "schedule", - "type": "TEXT" - }, - { - "name": "starting_deadline_seconds", - "type": "BIGINT" - }, - { - "name": "concurrency_policy", - "type": "TEXT" - }, - { - "name": "suspend", - "type": "INTEGER" - }, - { - "name": "successful_jobs_history_limit", - "type": "INTEGER" - }, - { - "name": "failed_jobs_history_limit", - "type": "INTEGER" - }, - { - "name": "parallelism", - "type": "INTEGER" - }, - { - "name": "completions", - "type": "INTEGER" - }, - { - "name": "job_active_deadline_seconds", - "type": "BIGINT" - }, - { - "name": "backoff_limit", - "type": "INTEGER" - }, - { - "name": "selector", - "type": "TEXT" - }, - { - "name": "manual_selector", - "type": "INTEGER" - }, - { - "name": "ttl_seconds_after_finished", - "type": "INTEGER" - } - ] - }, - { - "name": "kubernetes_jobs", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "se_linux_options_user", - "type": "TEXT" - }, - { - "name": "se_linux_options_role", - "type": "TEXT" - }, - { - "name": "se_linux_options_type", - "type": "TEXT" - }, - { - "name": "se_linux_options_level", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec_name", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec", - "type": "TEXT" - }, - { - "name": "windows_options_run_as_user_name", - "type": "TEXT" - }, - { - "name": "seccomp_profile_type", - "type": "TEXT" - }, - { - "name": "seccomp_profile_localhost_profile", - "type": "TEXT" - }, - { - "name": "run_as_user", - "type": "BIGINT" - }, - { - "name": "run_as_group", - "type": "BIGINT" - }, - { - "name": "run_as_non_root", - "type": "INTEGER" - }, - { - "name": "supplemental_groups", - "type": "TEXT" - }, - { - "name": "fs_group", - "type": "BIGINT" - }, - { - "name": "sysctls", - "type": "TEXT" - }, - { - "name": "fs_group_change_policy", - "type": "TEXT" - }, - { - "name": "node_affinity", - "type": "TEXT" - }, - { - "name": "pod_affinity", - "type": "TEXT" - }, - { - "name": "pod_anti_affinity", - "type": "TEXT" - }, - { - "name": "dns_config_nameservers", - "type": "TEXT" - }, - { - "name": "dns_config_searches", - "type": "TEXT" - }, - { - "name": "dns_config_options", - "type": "TEXT" - }, - { - "name": "node_selector", - "type": "TEXT" - }, - { - "name": "restart_policy", - "type": "TEXT" - }, - { - "name": "termination_grace_period_seconds", - "type": "BIGINT" - }, - { - "name": "active_deadline_seconds", - "type": "BIGINT" - }, - { - "name": "dns_policy", - "type": "TEXT" - }, - { - "name": "service_account_name", - "type": "TEXT" - }, - { - "name": "automount_service_account_token", - "type": "INTEGER" - }, - { - "name": "node_name", - "type": "TEXT" - }, - { - "name": "host_network", - "type": "INTEGER" - }, - { - "name": "host_pid", - "type": "INTEGER" - }, - { - "name": "host_ipc", - "type": "INTEGER" - }, - { - "name": "share_process_namespace", - "type": "INTEGER" - }, - { - "name": "image_pull_secrets", - "type": "TEXT" - }, - { - "name": "hostname", - "type": "TEXT" - }, - { - "name": "subdomain", - "type": "TEXT" - }, - { - "name": "scheduler_name", - "type": "TEXT" - }, - { - "name": "tolerations", - "type": "TEXT" - }, - { - "name": "host_aliases", - "type": "TEXT" - }, - { - "name": "priority_class_name", - "type": "TEXT" - }, - { - "name": "priority", - "type": "INTEGER" - }, - { - "name": "readiness_gates", - "type": "TEXT" - }, - { - "name": "runtime_class_name", - "type": "TEXT" - }, - { - "name": "enable_service_links", - "type": "INTEGER" - }, - { - "name": "preemption_policy", - "type": "TEXT" - }, - { - "name": "overhead", - "type": "TEXT" - }, - { - "name": "topology_spread_constraints", - "type": "TEXT" - }, - { - "name": "set_hostname_as_fqdn", - "type": "INTEGER" - }, - { - "name": "conditions", - "type": "TEXT" - }, - { - "name": "start_time", - "type": "BIGINT" - }, - { - "name": "completion_time", - "type": "BIGINT" - }, - { - "name": "active", - "type": "INTEGER" - }, - { - "name": "succeeded", - "type": "INTEGER" - }, - { - "name": "failed", - "type": "INTEGER" - }, - { - "name": "completed_indexes", - "type": "TEXT" - }, - { - "name": "uncounted_terminated_pods", - "type": "TEXT" - }, - { - "name": "parallelism", - "type": "INTEGER" - }, - { - "name": "completions", - "type": "INTEGER" - }, - { - "name": "job_active_deadline_seconds", - "type": "BIGINT" - }, - { - "name": "backoff_limit", - "type": "INTEGER" - }, - { - "name": "selector", - "type": "TEXT" - }, - { - "name": "manual_selector", - "type": "INTEGER" - }, - { - "name": "ttl_seconds_after_finished", - "type": "INTEGER" - } - ] - }, - { - "name": "kubernetes_component_statuses", - "columns": [ - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "type", - "type": "TEXT" - }, - { - "name": "status", - "type": "TEXT" - }, - { - "name": "message", - "type": "TEXT" - }, - { - "name": "error", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_config_maps", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "immutable", - "type": "INTEGER" - } - ] - }, - { - "name": "kubernetes_endpoint_subsets", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "addresses", - "type": "TEXT" - }, - { - "name": "not_ready_addresses", - "type": "TEXT" - }, - { - "name": "ports", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_limit_ranges", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "type", - "type": "TEXT" - }, - { - "name": "max", - "type": "TEXT" - }, - { - "name": "min", - "type": "TEXT" - }, - { - "name": "default", - "type": "TEXT" - }, - { - "name": "default_request", - "type": "TEXT" - }, - { - "name": "max_limit_request_ratio", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_namespaces", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "phase", - "type": "TEXT" - }, - { - "name": "conditions", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_nodes", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "pod_cidr", - "type": "TEXT" - }, - { - "name": "pod_cidrs", - "type": "TEXT" - }, - { - "name": "provider_id", - "type": "TEXT" - }, - { - "name": "unschedulable", - "type": "INTEGER" - }, - { - "name": "taints", - "type": "TEXT" - }, - { - "name": "config_source", - "type": "TEXT" - }, - { - "name": "do_not_use_external_id", - "type": "TEXT" - }, - { - "name": "capacity", - "type": "TEXT" - }, - { - "name": "allocatable", - "type": "TEXT" - }, - { - "name": "phase", - "type": "TEXT" - }, - { - "name": "conditions", - "type": "TEXT" - }, - { - "name": "addresses", - "type": "TEXT" - }, - { - "name": "daemon_endpoints", - "type": "TEXT" - }, - { - "name": "node_info", - "type": "TEXT" - }, - { - "name": "images", - "type": "TEXT" - }, - { - "name": "volumes_in_use", - "type": "TEXT" - }, - { - "name": "volumes_attached", - "type": "TEXT" - }, - { - "name": "config", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_persistent_volume_claims", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "access_modes", - "type": "TEXT" - }, - { - "name": "selector", - "type": "TEXT" - }, - { - "name": "resources", - "type": "TEXT" - }, - { - "name": "volume_name", - "type": "TEXT" - }, - { - "name": "storage_class_name", - "type": "TEXT" - }, - { - "name": "volume_mode", - "type": "TEXT" - }, - { - "name": "data_source", - "type": "TEXT" - }, - { - "name": "data_source_ref", - "type": "TEXT" - }, - { - "name": "phase", - "type": "TEXT" - }, - { - "name": "capacity", - "type": "TEXT" - }, - { - "name": "conditions", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_persistent_volumes", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "capacity", - "type": "TEXT" - }, - { - "name": "access_modes", - "type": "TEXT" - }, - { - "name": "claim_ref", - "type": "TEXT" - }, - { - "name": "persistent_volume_reclaim_policy", - "type": "TEXT" - }, - { - "name": "storage_class_name", - "type": "TEXT" - }, - { - "name": "mount_options", - "type": "TEXT" - }, - { - "name": "volume_mode", - "type": "TEXT" - }, - { - "name": "node_affinity", - "type": "TEXT" - }, - { - "name": "status_phase", - "type": "TEXT" - }, - { - "name": "status_message", - "type": "TEXT" - }, - { - "name": "status_reason", - "type": "TEXT" - }, - { - "name": "volume_type", - "type": "TEXT" - }, - { - "name": "fs_type", - "type": "TEXT" - }, - { - "name": "read_only", - "type": "INTEGER" - }, - { - "name": "secret_name", - "type": "TEXT" - }, - { - "name": "host_path_path", - "type": "TEXT" - }, - { - "name": "host_path_type", - "type": "TEXT" - }, - { - "name": "gce_persistent_disk_pd_name", - "type": "TEXT" - }, - { - "name": "gce_persistent_disk_partition", - "type": "INTEGER" - }, - { - "name": "aws_elastic_block_store_volume_id", - "type": "TEXT" - }, - { - "name": "aws_elastic_block_store_partition", - "type": "INTEGER" - }, - { - "name": "nfs_server", - "type": "TEXT" - }, - { - "name": "nfs_path", - "type": "TEXT" - }, - { - "name": "iscsi_target_portal", - "type": "TEXT" - }, - { - "name": "iscsi_iqn", - "type": "TEXT" - }, - { - "name": "iscsi_lun", - "type": "INTEGER" - }, - { - "name": "iscsi_interface", - "type": "TEXT" - }, - { - "name": "iscsi_portals", - "type": "TEXT" - }, - { - "name": "iscsi_discovery_chap_auth", - "type": "INTEGER" - }, - { - "name": "iscsi_session_chap_auth", - "type": "INTEGER" - }, - { - "name": "iscsi_initiator_name", - "type": "TEXT" - }, - { - "name": "local_path", - "type": "TEXT" - }, - { - "name": "glusterfs_endpoints_name", - "type": "TEXT" - }, - { - "name": "glusterfs_path", - "type": "TEXT" - }, - { - "name": "rbd_ceph_monitors", - "type": "TEXT" - }, - { - "name": "rbd_image", - "type": "TEXT" - }, - { - "name": "rbd_pool", - "type": "TEXT" - }, - { - "name": "rbd_rados_user", - "type": "TEXT" - }, - { - "name": "rbd_keyring", - "type": "TEXT" - }, - { - "name": "flex_volume_driver", - "type": "TEXT" - }, - { - "name": "flex_volume_options", - "type": "TEXT" - }, - { - "name": "cinder_volume_id", - "type": "TEXT" - }, - { - "name": "ceph_fs_monitors", - "type": "TEXT" - }, - { - "name": "ceph_fs_path", - "type": "TEXT" - }, - { - "name": "ceph_fs_user", - "type": "TEXT" - }, - { - "name": "ceph_fs_secret_file", - "type": "TEXT" - }, - { - "name": "flocker_dataset_name", - "type": "TEXT" - }, - { - "name": "flocker_dataset_uuid", - "type": "TEXT" - }, - { - "name": "fc_target_ww_ns", - "type": "TEXT" - }, - { - "name": "fc_lun", - "type": "INTEGER" - }, - { - "name": "fc_ww_ids", - "type": "TEXT" - }, - { - "name": "azure_file_share_name", - "type": "TEXT" - }, - { - "name": "vsphere_volume_volume_path", - "type": "TEXT" - }, - { - "name": "vsphere_volume_storage_policy_name", - "type": "TEXT" - }, - { - "name": "vsphere_volume_storage_policy_id", - "type": "TEXT" - }, - { - "name": "quobyte_registry", - "type": "TEXT" - }, - { - "name": "quobyte_volume", - "type": "TEXT" - }, - { - "name": "quobyte_user", - "type": "TEXT" - }, - { - "name": "quobyte_group", - "type": "TEXT" - }, - { - "name": "quobyte_tenant", - "type": "TEXT" - }, - { - "name": "azure_disk_disk_name", - "type": "TEXT" - }, - { - "name": "azure_disk_data_disk_uri", - "type": "TEXT" - }, - { - "name": "azure_disk_caching_mode", - "type": "TEXT" - }, - { - "name": "azure_disk_kind", - "type": "TEXT" - }, - { - "name": "photon_persistent_disk_pd_id", - "type": "TEXT" - }, - { - "name": "portworx_volume_id", - "type": "TEXT" - }, - { - "name": "scale_io_gateway", - "type": "TEXT" - }, - { - "name": "scale_io_system", - "type": "TEXT" - }, - { - "name": "scale_iossl_enabled", - "type": "INTEGER" - }, - { - "name": "scale_io_protection_domain", - "type": "TEXT" - }, - { - "name": "scale_io_storage_pool", - "type": "TEXT" - }, - { - "name": "scale_io_storage_mode", - "type": "TEXT" - }, - { - "name": "scale_io_volume_name", - "type": "TEXT" - }, - { - "name": "storage_os_volume_name", - "type": "TEXT" - }, - { - "name": "storage_os_volume_namespace", - "type": "TEXT" - }, - { - "name": "csi_driver", - "type": "TEXT" - }, - { - "name": "csi_volume_attributes", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_pod_templates", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "se_linux_options_user", - "type": "TEXT" - }, - { - "name": "se_linux_options_role", - "type": "TEXT" - }, - { - "name": "se_linux_options_type", - "type": "TEXT" - }, - { - "name": "se_linux_options_level", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec_name", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec", - "type": "TEXT" - }, - { - "name": "windows_options_run_as_user_name", - "type": "TEXT" - }, - { - "name": "seccomp_profile_type", - "type": "TEXT" - }, - { - "name": "seccomp_profile_localhost_profile", - "type": "TEXT" - }, - { - "name": "run_as_user", - "type": "BIGINT" - }, - { - "name": "run_as_group", - "type": "BIGINT" - }, - { - "name": "run_as_non_root", - "type": "INTEGER" - }, - { - "name": "supplemental_groups", - "type": "TEXT" - }, - { - "name": "fs_group", - "type": "BIGINT" - }, - { - "name": "sysctls", - "type": "TEXT" - }, - { - "name": "fs_group_change_policy", - "type": "TEXT" - }, - { - "name": "node_affinity", - "type": "TEXT" - }, - { - "name": "pod_affinity", - "type": "TEXT" - }, - { - "name": "pod_anti_affinity", - "type": "TEXT" - }, - { - "name": "dns_config_nameservers", - "type": "TEXT" - }, - { - "name": "dns_config_searches", - "type": "TEXT" - }, - { - "name": "dns_config_options", - "type": "TEXT" - }, - { - "name": "node_selector", - "type": "TEXT" - }, - { - "name": "restart_policy", - "type": "TEXT" - }, - { - "name": "termination_grace_period_seconds", - "type": "BIGINT" - }, - { - "name": "active_deadline_seconds", - "type": "BIGINT" - }, - { - "name": "dns_policy", - "type": "TEXT" - }, - { - "name": "service_account_name", - "type": "TEXT" - }, - { - "name": "automount_service_account_token", - "type": "INTEGER" - }, - { - "name": "node_name", - "type": "TEXT" - }, - { - "name": "host_network", - "type": "INTEGER" - }, - { - "name": "host_pid", - "type": "INTEGER" - }, - { - "name": "host_ipc", - "type": "INTEGER" - }, - { - "name": "share_process_namespace", - "type": "INTEGER" - }, - { - "name": "image_pull_secrets", - "type": "TEXT" - }, - { - "name": "hostname", - "type": "TEXT" - }, - { - "name": "subdomain", - "type": "TEXT" - }, - { - "name": "scheduler_name", - "type": "TEXT" - }, - { - "name": "tolerations", - "type": "TEXT" - }, - { - "name": "host_aliases", - "type": "TEXT" - }, - { - "name": "priority_class_name", - "type": "TEXT" - }, - { - "name": "priority", - "type": "INTEGER" - }, - { - "name": "readiness_gates", - "type": "TEXT" - }, - { - "name": "runtime_class_name", - "type": "TEXT" - }, - { - "name": "enable_service_links", - "type": "INTEGER" - }, - { - "name": "preemption_policy", - "type": "TEXT" - }, - { - "name": "overhead", - "type": "TEXT" - }, - { - "name": "topology_spread_constraints", - "type": "TEXT" - }, - { - "name": "set_hostname_as_fqdn", - "type": "INTEGER" - } - ] - }, - { - "name": "kubernetes_pod_template_containers", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "se_linux_options_user", - "type": "TEXT" - }, - { - "name": "se_linux_options_role", - "type": "TEXT" - }, - { - "name": "se_linux_options_type", - "type": "TEXT" - }, - { - "name": "se_linux_options_level", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec_name", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec", - "type": "TEXT" - }, - { - "name": "windows_options_run_as_user_name", - "type": "TEXT" - }, - { - "name": "seccomp_profile_type", - "type": "TEXT" - }, - { - "name": "seccomp_profile_localhost_profile", - "type": "TEXT" - }, - { - "name": "run_as_user", - "type": "BIGINT" - }, - { - "name": "run_as_group", - "type": "BIGINT" - }, - { - "name": "run_as_non_root", - "type": "INTEGER" - }, - { - "name": "capabilities_add", - "type": "TEXT" - }, - { - "name": "capabilities_drop", - "type": "TEXT" - }, - { - "name": "privileged", - "type": "INTEGER" - }, - { - "name": "read_only_root_filesystem", - "type": "INTEGER" - }, - { - "name": "allow_privilege_escalation", - "type": "INTEGER" - }, - { - "name": "proc_mount", - "type": "TEXT" - }, - { - "name": "target_container_name", - "type": "TEXT" - }, - { - "name": "image", - "type": "TEXT" - }, - { - "name": "command", - "type": "TEXT" - }, - { - "name": "args", - "type": "TEXT" - }, - { - "name": "working_dir", - "type": "TEXT" - }, - { - "name": "ports", - "type": "TEXT" - }, - { - "name": "env_from", - "type": "TEXT" - }, - { - "name": "env", - "type": "TEXT" - }, - { - "name": "resource_limits", - "type": "TEXT" - }, - { - "name": "resource_requests", - "type": "TEXT" - }, - { - "name": "volume_mounts", - "type": "TEXT" - }, - { - "name": "volume_devices", - "type": "TEXT" - }, - { - "name": "liveness_probe", - "type": "TEXT" - }, - { - "name": "readiness_probe", - "type": "TEXT" - }, - { - "name": "startup_probe", - "type": "TEXT" - }, - { - "name": "lifecycle", - "type": "TEXT" - }, - { - "name": "termination_message_path", - "type": "TEXT" - }, - { - "name": "termination_message_policy", - "type": "TEXT" - }, - { - "name": "image_pull_policy", - "type": "TEXT" - }, - { - "name": "stdin", - "type": "INTEGER" - }, - { - "name": "stdin_once", - "type": "INTEGER" - }, - { - "name": "tty", - "type": "INTEGER" - }, - { - "name": "pod_template_name", - "type": "TEXT" - }, - { - "name": "container_type", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_pod_templates_volumes", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "volume_type", - "type": "TEXT" - }, - { - "name": "fs_type", - "type": "TEXT" - }, - { - "name": "read_only", - "type": "INTEGER" - }, - { - "name": "secret_name", - "type": "TEXT" - }, - { - "name": "host_path_path", - "type": "TEXT" - }, - { - "name": "host_path_type", - "type": "TEXT" - }, - { - "name": "empty_dir_medium", - "type": "TEXT" - }, - { - "name": "empty_dir_size_limit", - "type": "TEXT" - }, - { - "name": "gce_persistent_disk_pd_name", - "type": "TEXT" - }, - { - "name": "gce_persistent_disk_partition", - "type": "INTEGER" - }, - { - "name": "aws_elastic_block_store_volume_id", - "type": "TEXT" - }, - { - "name": "aws_elastic_block_store_partition", - "type": "INTEGER" - }, - { - "name": "git_repo_repository", - "type": "TEXT" - }, - { - "name": "git_repo_revision", - "type": "TEXT" - }, - { - "name": "git_repo_directory", - "type": "TEXT" - }, - { - "name": "secret_items", - "type": "TEXT" - }, - { - "name": "secret_default_mode", - "type": "INTEGER" - }, - { - "name": "secret_optional", - "type": "INTEGER" - }, - { - "name": "nfs_server", - "type": "TEXT" - }, - { - "name": "nfs_path", - "type": "TEXT" - }, - { - "name": "iscsi_target_portal", - "type": "TEXT" - }, - { - "name": "iscsi_iqn", - "type": "TEXT" - }, - { - "name": "iscsi_lun", - "type": "INTEGER" - }, - { - "name": "iscsi_interface", - "type": "TEXT" - }, - { - "name": "iscsi_portals", - "type": "TEXT" - }, - { - "name": "iscsi_discovery_chap_auth", - "type": "INTEGER" - }, - { - "name": "iscsi_session_chap_auth", - "type": "INTEGER" - }, - { - "name": "iscsi_initiator_name", - "type": "TEXT" - }, - { - "name": "glusterfs_endpoints_name", - "type": "TEXT" - }, - { - "name": "glusterfs_path", - "type": "TEXT" - }, - { - "name": "persistent_volume_claim_name", - "type": "TEXT" - }, - { - "name": "rbd_ceph_monitors", - "type": "TEXT" - }, - { - "name": "rbd_image", - "type": "TEXT" - }, - { - "name": "rbd_pool", - "type": "TEXT" - }, - { - "name": "rbd_rados_user", - "type": "TEXT" - }, - { - "name": "rbd_keyring", - "type": "TEXT" - }, - { - "name": "flex_volume_driver", - "type": "TEXT" - }, - { - "name": "flex_volume_options", - "type": "TEXT" - }, - { - "name": "cinder_volume_id", - "type": "TEXT" - }, - { - "name": "ceph_fs_monitors", - "type": "TEXT" - }, - { - "name": "ceph_fs_path", - "type": "TEXT" - }, - { - "name": "ceph_fs_user", - "type": "TEXT" - }, - { - "name": "ceph_fs_secret_file", - "type": "TEXT" - }, - { - "name": "flocker_dataset_name", - "type": "TEXT" - }, - { - "name": "flocker_dataset_uuid", - "type": "TEXT" - }, - { - "name": "downward_api_items", - "type": "TEXT" - }, - { - "name": "downward_api_default_mode", - "type": "INTEGER" - }, - { - "name": "fc_target_ww_ns", - "type": "TEXT" - }, - { - "name": "fc_lun", - "type": "INTEGER" - }, - { - "name": "fc_ww_ids", - "type": "TEXT" - }, - { - "name": "azure_file_share_name", - "type": "TEXT" - }, - { - "name": "config_map_name", - "type": "TEXT" - }, - { - "name": "config_map_items", - "type": "TEXT" - }, - { - "name": "config_map_default_mode", - "type": "INTEGER" - }, - { - "name": "config_map_optional", - "type": "INTEGER" - }, - { - "name": "vsphere_volume_volume_path", - "type": "TEXT" - }, - { - "name": "vsphere_volume_storage_policy_name", - "type": "TEXT" - }, - { - "name": "vsphere_volume_storage_policy_id", - "type": "TEXT" - }, - { - "name": "quobyte_registry", - "type": "TEXT" - }, - { - "name": "quobyte_volume", - "type": "TEXT" - }, - { - "name": "quobyte_user", - "type": "TEXT" - }, - { - "name": "quobyte_group", - "type": "TEXT" - }, - { - "name": "quobyte_tenant", - "type": "TEXT" - }, - { - "name": "azure_disk_disk_name", - "type": "TEXT" - }, - { - "name": "azure_disk_data_disk_uri", - "type": "TEXT" - }, - { - "name": "azure_disk_caching_mode", - "type": "TEXT" - }, - { - "name": "azure_disk_kind", - "type": "TEXT" - }, - { - "name": "photon_persistent_disk_pd_id", - "type": "TEXT" - }, - { - "name": "projected_sources", - "type": "TEXT" - }, - { - "name": "projected_default_mode", - "type": "INTEGER" - }, - { - "name": "portworx_volume_id", - "type": "TEXT" - }, - { - "name": "scale_io_gateway", - "type": "TEXT" - }, - { - "name": "scale_io_system", - "type": "TEXT" - }, - { - "name": "scale_iossl_enabled", - "type": "INTEGER" - }, - { - "name": "scale_io_protection_domain", - "type": "TEXT" - }, - { - "name": "scale_io_storage_pool", - "type": "TEXT" - }, - { - "name": "scale_io_storage_mode", - "type": "TEXT" - }, - { - "name": "scale_io_volume_name", - "type": "TEXT" - }, - { - "name": "storage_os_volume_name", - "type": "TEXT" - }, - { - "name": "storage_os_volume_namespace", - "type": "TEXT" - }, - { - "name": "csi_driver", - "type": "TEXT" - }, - { - "name": "csi_volume_attributes", - "type": "TEXT" - }, - { - "name": "ephemeral_volume_claim_template", - "type": "TEXT" - }, - { - "name": "pod_template_name", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_pods", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "se_linux_options_user", - "type": "TEXT" - }, - { - "name": "se_linux_options_role", - "type": "TEXT" - }, - { - "name": "se_linux_options_type", - "type": "TEXT" - }, - { - "name": "se_linux_options_level", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec_name", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec", - "type": "TEXT" - }, - { - "name": "windows_options_run_as_user_name", - "type": "TEXT" - }, - { - "name": "seccomp_profile_type", - "type": "TEXT" - }, - { - "name": "seccomp_profile_localhost_profile", - "type": "TEXT" - }, - { - "name": "run_as_user", - "type": "BIGINT" - }, - { - "name": "run_as_group", - "type": "BIGINT" - }, - { - "name": "run_as_non_root", - "type": "INTEGER" - }, - { - "name": "supplemental_groups", - "type": "TEXT" - }, - { - "name": "fs_group", - "type": "BIGINT" - }, - { - "name": "sysctls", - "type": "TEXT" - }, - { - "name": "fs_group_change_policy", - "type": "TEXT" - }, - { - "name": "node_affinity", - "type": "TEXT" - }, - { - "name": "pod_affinity", - "type": "TEXT" - }, - { - "name": "pod_anti_affinity", - "type": "TEXT" - }, - { - "name": "dns_config_nameservers", - "type": "TEXT" - }, - { - "name": "dns_config_searches", - "type": "TEXT" - }, - { - "name": "dns_config_options", - "type": "TEXT" - }, - { - "name": "node_selector", - "type": "TEXT" - }, - { - "name": "restart_policy", - "type": "TEXT" - }, - { - "name": "termination_grace_period_seconds", - "type": "BIGINT" - }, - { - "name": "active_deadline_seconds", - "type": "BIGINT" - }, - { - "name": "dns_policy", - "type": "TEXT" - }, - { - "name": "service_account_name", - "type": "TEXT" - }, - { - "name": "automount_service_account_token", - "type": "INTEGER" - }, - { - "name": "node_name", - "type": "TEXT" - }, - { - "name": "host_network", - "type": "INTEGER" - }, - { - "name": "host_pid", - "type": "INTEGER" - }, - { - "name": "host_ipc", - "type": "INTEGER" - }, - { - "name": "share_process_namespace", - "type": "INTEGER" - }, - { - "name": "image_pull_secrets", - "type": "TEXT" - }, - { - "name": "hostname", - "type": "TEXT" - }, - { - "name": "subdomain", - "type": "TEXT" - }, - { - "name": "scheduler_name", - "type": "TEXT" - }, - { - "name": "tolerations", - "type": "TEXT" - }, - { - "name": "host_aliases", - "type": "TEXT" - }, - { - "name": "priority_class_name", - "type": "TEXT" - }, - { - "name": "priority", - "type": "INTEGER" - }, - { - "name": "readiness_gates", - "type": "TEXT" - }, - { - "name": "runtime_class_name", - "type": "TEXT" - }, - { - "name": "enable_service_links", - "type": "INTEGER" - }, - { - "name": "preemption_policy", - "type": "TEXT" - }, - { - "name": "overhead", - "type": "TEXT" - }, - { - "name": "topology_spread_constraints", - "type": "TEXT" - }, - { - "name": "set_hostname_as_fqdn", - "type": "INTEGER" - }, - { - "name": "phase", - "type": "TEXT" - }, - { - "name": "conditions", - "type": "TEXT" - }, - { - "name": "message", - "type": "TEXT" - }, - { - "name": "reason", - "type": "TEXT" - }, - { - "name": "nominated_node_name", - "type": "TEXT" - }, - { - "name": "host_ip", - "type": "TEXT" - }, - { - "name": "pod_ip", - "type": "TEXT" - }, - { - "name": "pod_ips", - "type": "TEXT" - }, - { - "name": "start_time", - "type": "BIGINT" - }, - { - "name": "init_container_statuses", - "type": "TEXT" - }, - { - "name": "container_statuses", - "type": "TEXT" - }, - { - "name": "qos_class", - "type": "TEXT" - }, - { - "name": "ephemeral_container_statuses", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_pod_containers", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "se_linux_options_user", - "type": "TEXT" - }, - { - "name": "se_linux_options_role", - "type": "TEXT" - }, - { - "name": "se_linux_options_type", - "type": "TEXT" - }, - { - "name": "se_linux_options_level", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec_name", - "type": "TEXT" - }, - { - "name": "windows_options_gmsa_credential_spec", - "type": "TEXT" - }, - { - "name": "windows_options_run_as_user_name", - "type": "TEXT" - }, - { - "name": "seccomp_profile_type", - "type": "TEXT" - }, - { - "name": "seccomp_profile_localhost_profile", - "type": "TEXT" - }, - { - "name": "run_as_user", - "type": "BIGINT" - }, - { - "name": "run_as_group", - "type": "BIGINT" - }, - { - "name": "run_as_non_root", - "type": "INTEGER" - }, - { - "name": "capabilities_add", - "type": "TEXT" - }, - { - "name": "capabilities_drop", - "type": "TEXT" - }, - { - "name": "privileged", - "type": "INTEGER" - }, - { - "name": "read_only_root_filesystem", - "type": "INTEGER" - }, - { - "name": "allow_privilege_escalation", - "type": "INTEGER" - }, - { - "name": "proc_mount", - "type": "TEXT" - }, - { - "name": "target_container_name", - "type": "TEXT" - }, - { - "name": "image", - "type": "TEXT" - }, - { - "name": "command", - "type": "TEXT" - }, - { - "name": "args", - "type": "TEXT" - }, - { - "name": "working_dir", - "type": "TEXT" - }, - { - "name": "ports", - "type": "TEXT" - }, - { - "name": "env_from", - "type": "TEXT" - }, - { - "name": "env", - "type": "TEXT" - }, - { - "name": "resource_limits", - "type": "TEXT" - }, - { - "name": "resource_requests", - "type": "TEXT" - }, - { - "name": "volume_mounts", - "type": "TEXT" - }, - { - "name": "volume_devices", - "type": "TEXT" - }, - { - "name": "liveness_probe", - "type": "TEXT" - }, - { - "name": "readiness_probe", - "type": "TEXT" - }, - { - "name": "startup_probe", - "type": "TEXT" - }, - { - "name": "lifecycle", - "type": "TEXT" - }, - { - "name": "termination_message_path", - "type": "TEXT" - }, - { - "name": "termination_message_policy", - "type": "TEXT" - }, - { - "name": "image_pull_policy", - "type": "TEXT" - }, - { - "name": "stdin", - "type": "INTEGER" - }, - { - "name": "stdin_once", - "type": "INTEGER" - }, - { - "name": "tty", - "type": "INTEGER" - }, - { - "name": "pod_name", - "type": "TEXT" - }, - { - "name": "container_type", - "type": "TEXT" - }, - { - "name": "state", - "type": "TEXT" - }, - { - "name": "last_termination_state", - "type": "TEXT" - }, - { - "name": "ready", - "type": "INTEGER" - }, - { - "name": "restart_count", - "type": "INTEGER" - }, - { - "name": "image_repo", - "type": "TEXT" - }, - { - "name": "image_id", - "type": "TEXT" - }, - { - "name": "container_id", - "type": "TEXT" - }, - { - "name": "started", - "type": "INTEGER" - } - ] - }, - { - "name": "kubernetes_pod_volumes", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "volume_type", - "type": "TEXT" - }, - { - "name": "fs_type", - "type": "TEXT" - }, - { - "name": "read_only", - "type": "INTEGER" - }, - { - "name": "secret_name", - "type": "TEXT" - }, - { - "name": "host_path_path", - "type": "TEXT" - }, - { - "name": "host_path_type", - "type": "TEXT" - }, - { - "name": "empty_dir_medium", - "type": "TEXT" - }, - { - "name": "empty_dir_size_limit", - "type": "TEXT" - }, - { - "name": "gce_persistent_disk_pd_name", - "type": "TEXT" - }, - { - "name": "gce_persistent_disk_partition", - "type": "INTEGER" - }, - { - "name": "aws_elastic_block_store_volume_id", - "type": "TEXT" - }, - { - "name": "aws_elastic_block_store_partition", - "type": "INTEGER" - }, - { - "name": "git_repo_repository", - "type": "TEXT" - }, - { - "name": "git_repo_revision", - "type": "TEXT" - }, - { - "name": "git_repo_directory", - "type": "TEXT" - }, - { - "name": "secret_items", - "type": "TEXT" - }, - { - "name": "secret_default_mode", - "type": "INTEGER" - }, - { - "name": "secret_optional", - "type": "INTEGER" - }, - { - "name": "nfs_server", - "type": "TEXT" - }, - { - "name": "nfs_path", - "type": "TEXT" - }, - { - "name": "iscsi_target_portal", - "type": "TEXT" - }, - { - "name": "iscsi_iqn", - "type": "TEXT" - }, - { - "name": "iscsi_lun", - "type": "INTEGER" - }, - { - "name": "iscsi_interface", - "type": "TEXT" - }, - { - "name": "iscsi_portals", - "type": "TEXT" - }, - { - "name": "iscsi_discovery_chap_auth", - "type": "INTEGER" - }, - { - "name": "iscsi_session_chap_auth", - "type": "INTEGER" - }, - { - "name": "iscsi_initiator_name", - "type": "TEXT" - }, - { - "name": "glusterfs_endpoints_name", - "type": "TEXT" - }, - { - "name": "glusterfs_path", - "type": "TEXT" - }, - { - "name": "persistent_volume_claim_name", - "type": "TEXT" - }, - { - "name": "rbd_ceph_monitors", - "type": "TEXT" - }, - { - "name": "rbd_image", - "type": "TEXT" - }, - { - "name": "rbd_pool", - "type": "TEXT" - }, - { - "name": "rbd_rados_user", - "type": "TEXT" - }, - { - "name": "rbd_keyring", - "type": "TEXT" - }, - { - "name": "flex_volume_driver", - "type": "TEXT" - }, - { - "name": "flex_volume_options", - "type": "TEXT" - }, - { - "name": "cinder_volume_id", - "type": "TEXT" - }, - { - "name": "ceph_fs_monitors", - "type": "TEXT" - }, - { - "name": "ceph_fs_path", - "type": "TEXT" - }, - { - "name": "ceph_fs_user", - "type": "TEXT" - }, - { - "name": "ceph_fs_secret_file", - "type": "TEXT" - }, - { - "name": "flocker_dataset_name", - "type": "TEXT" - }, - { - "name": "flocker_dataset_uuid", - "type": "TEXT" - }, - { - "name": "downward_api_items", - "type": "TEXT" - }, - { - "name": "downward_api_default_mode", - "type": "INTEGER" - }, - { - "name": "fc_target_ww_ns", - "type": "TEXT" - }, - { - "name": "fc_lun", - "type": "INTEGER" - }, - { - "name": "fc_ww_ids", - "type": "TEXT" - }, - { - "name": "azure_file_share_name", - "type": "TEXT" - }, - { - "name": "config_map_name", - "type": "TEXT" - }, - { - "name": "config_map_items", - "type": "TEXT" - }, - { - "name": "config_map_default_mode", - "type": "INTEGER" - }, - { - "name": "config_map_optional", - "type": "INTEGER" - }, - { - "name": "vsphere_volume_volume_path", - "type": "TEXT" - }, - { - "name": "vsphere_volume_storage_policy_name", - "type": "TEXT" - }, - { - "name": "vsphere_volume_storage_policy_id", - "type": "TEXT" - }, - { - "name": "quobyte_registry", - "type": "TEXT" - }, - { - "name": "quobyte_volume", - "type": "TEXT" - }, - { - "name": "quobyte_user", - "type": "TEXT" - }, - { - "name": "quobyte_group", - "type": "TEXT" - }, - { - "name": "quobyte_tenant", - "type": "TEXT" - }, - { - "name": "azure_disk_disk_name", - "type": "TEXT" - }, - { - "name": "azure_disk_data_disk_uri", - "type": "TEXT" - }, - { - "name": "azure_disk_caching_mode", - "type": "TEXT" - }, - { - "name": "azure_disk_kind", - "type": "TEXT" - }, - { - "name": "photon_persistent_disk_pd_id", - "type": "TEXT" - }, - { - "name": "projected_sources", - "type": "TEXT" - }, - { - "name": "projected_default_mode", - "type": "INTEGER" - }, - { - "name": "portworx_volume_id", - "type": "TEXT" - }, - { - "name": "scale_io_gateway", - "type": "TEXT" - }, - { - "name": "scale_io_system", - "type": "TEXT" - }, - { - "name": "scale_iossl_enabled", - "type": "INTEGER" - }, - { - "name": "scale_io_protection_domain", - "type": "TEXT" - }, - { - "name": "scale_io_storage_pool", - "type": "TEXT" - }, - { - "name": "scale_io_storage_mode", - "type": "TEXT" - }, - { - "name": "scale_io_volume_name", - "type": "TEXT" - }, - { - "name": "storage_os_volume_name", - "type": "TEXT" - }, - { - "name": "storage_os_volume_namespace", - "type": "TEXT" - }, - { - "name": "csi_driver", - "type": "TEXT" - }, - { - "name": "csi_volume_attributes", - "type": "TEXT" - }, - { - "name": "ephemeral_volume_claim_template", - "type": "TEXT" - }, - { - "name": "pod_name", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_resource_quotas", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "hard", - "type": "TEXT" - }, - { - "name": "scopes", - "type": "TEXT" - }, - { - "name": "scope_selector", - "type": "TEXT" - }, - { - "name": "status_hard", - "type": "TEXT" - }, - { - "name": "status_used", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_secrets", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "immutable", - "type": "INTEGER" - }, - { - "name": "type", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_service_accounts", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "secrets", - "type": "TEXT" - }, - { - "name": "image_pull_secrets", - "type": "TEXT" - }, - { - "name": "automount_service_account_token", - "type": "INTEGER" - } - ] - }, - { - "name": "kubernetes_services", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "ports", - "type": "TEXT" - }, - { - "name": "selector", - "type": "TEXT" - }, - { - "name": "cluster_ip", - "type": "TEXT" - }, - { - "name": "cluster_ips", - "type": "TEXT" - }, - { - "name": "type", - "type": "TEXT" - }, - { - "name": "external_ips", - "type": "TEXT" - }, - { - "name": "session_affinity", - "type": "TEXT" - }, - { - "name": "load_balancer_ip", - "type": "TEXT" - }, - { - "name": "load_balancer_source_ranges", - "type": "TEXT" - }, - { - "name": "external_name", - "type": "TEXT" - }, - { - "name": "external_traffic_policy", - "type": "TEXT" - }, - { - "name": "health_check_node_port", - "type": "INTEGER" - }, - { - "name": "publish_not_ready_addresses", - "type": "INTEGER" - }, - { - "name": "session_affinity_config", - "type": "TEXT" - }, - { - "name": "ip_families", - "type": "TEXT" - }, - { - "name": "ip_family_policy", - "type": "TEXT" - }, - { - "name": "allocate_load_balancer_node_ports", - "type": "INTEGER" - }, - { - "name": "load_balancer_class", - "type": "TEXT" - }, - { - "name": "internal_traffic_policy", - "type": "TEXT" - }, - { - "name": "load_balancer", - "type": "TEXT" - }, - { - "name": "conditions", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_api_resources", - "columns": [ - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "singular_name", - "type": "TEXT" - }, - { - "name": "namespaced", - "type": "INTEGER" - }, - { - "name": "group", - "type": "TEXT" - }, - { - "name": "version", - "type": "TEXT" - }, - { - "name": "kind", - "type": "TEXT" - }, - { - "name": "verbs", - "type": "TEXT" - }, - { - "name": "short_names", - "type": "TEXT" - }, - { - "name": "categories", - "type": "TEXT" - }, - { - "name": "storage_version_hash", - "type": "TEXT" - }, - { - "name": "group_version", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_info", - "columns": [ - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "major", - "type": "TEXT" - }, - { - "name": "minor", - "type": "TEXT" - }, - { - "name": "git_version", - "type": "TEXT" - }, - { - "name": "git_commit", - "type": "TEXT" - }, - { - "name": "git_tree_state", - "type": "TEXT" - }, - { - "name": "build_date", - "type": "TEXT" - }, - { - "name": "go_version", - "type": "TEXT" - }, - { - "name": "compiler", - "type": "TEXT" - }, - { - "name": "platform", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_events", - "columns": [ - { - "name": "time", - "type": "BIGINT" - }, - { - "name": "event_type", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "reporting_controller", - "type": "TEXT" - }, - { - "name": "reporting_instance", - "type": "TEXT" - }, - { - "name": "action", - "type": "TEXT" - }, - { - "name": "reason", - "type": "TEXT" - }, - { - "name": "note", - "type": "TEXT" - }, - { - "name": "type", - "type": "TEXT" - }, - { - "name": "regarding_kind", - "type": "TEXT" - }, - { - "name": "regarding_namespace", - "type": "TEXT" - }, - { - "name": "regarding_name", - "type": "TEXT" - }, - { - "name": "regarding_uid", - "type": "TEXT" - }, - { - "name": "related_kind", - "type": "TEXT" - }, - { - "name": "related_namespace", - "type": "TEXT" - }, - { - "name": "related_name", - "type": "TEXT" - }, - { - "name": "related_uid", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_ingress_classes", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "controller", - "type": "TEXT" - }, - { - "name": "parameters", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_ingresses", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "ingress_class_name", - "type": "TEXT" - }, - { - "name": "default_backend", - "type": "TEXT" - }, - { - "name": "tls", - "type": "TEXT" - }, - { - "name": "rules", - "type": "TEXT" - }, - { - "name": "load_balancer", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_network_policies", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "pod_selector", - "type": "TEXT" - }, - { - "name": "policy_types", - "type": "TEXT" - }, - { - "name": "type", - "type": "TEXT" - }, - { - "name": "ports", - "type": "TEXT" - }, - { - "name": "from_to", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_pod_disruption_budgets", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "min_available", - "type": "TEXT" - }, - { - "name": "selector", - "type": "TEXT" - }, - { - "name": "max_unavailable", - "type": "TEXT" - }, - { - "name": "observed_generation", - "type": "BIGINT" - }, - { - "name": "disrupted_pods", - "type": "TEXT" - }, - { - "name": "disruptions_allowed", - "type": "INTEGER" - }, - { - "name": "current_healthy", - "type": "INTEGER" - }, - { - "name": "desired_healthy", - "type": "INTEGER" - }, - { - "name": "expected_pods", - "type": "INTEGER" - }, - { - "name": "conditions", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_pod_security_policies", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "privileged", - "type": "INTEGER" - }, - { - "name": "default_add_capabilities", - "type": "TEXT" - }, - { - "name": "required_drop_capabilities", - "type": "TEXT" - }, - { - "name": "allowed_capabilities", - "type": "TEXT" - }, - { - "name": "volumes", - "type": "TEXT" - }, - { - "name": "host_network", - "type": "INTEGER" - }, - { - "name": "host_ports", - "type": "TEXT" - }, - { - "name": "host_pid", - "type": "INTEGER" - }, - { - "name": "host_ipc", - "type": "INTEGER" - }, - { - "name": "se_linux", - "type": "TEXT" - }, - { - "name": "run_as_user", - "type": "TEXT" - }, - { - "name": "run_as_group", - "type": "TEXT" - }, - { - "name": "supplemental_groups", - "type": "TEXT" - }, - { - "name": "fs_group", - "type": "TEXT" - }, - { - "name": "read_only_root_filesystem", - "type": "INTEGER" - }, - { - "name": "default_allow_privilege_escalation", - "type": "INTEGER" - }, - { - "name": "allow_privilege_escalation", - "type": "INTEGER" - }, - { - "name": "allowed_host_paths", - "type": "TEXT" - }, - { - "name": "allowed_flex_volumes", - "type": "TEXT" - }, - { - "name": "allowed_csi_drivers", - "type": "TEXT" - }, - { - "name": "allowed_unsafe_sysctls", - "type": "TEXT" - }, - { - "name": "forbidden_sysctls", - "type": "TEXT" - }, - { - "name": "allowed_proc_mount_types", - "type": "TEXT" - }, - { - "name": "runtime_class", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_cluster_role_binding_subjects", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "role_api_group", - "type": "TEXT" - }, - { - "name": "role_name", - "type": "TEXT" - }, - { - "name": "role_kind", - "type": "TEXT" - }, - { - "name": "subject_name", - "type": "TEXT" - }, - { - "name": "subject_kind", - "type": "TEXT" - }, - { - "name": "subject_namespace", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_cluster_role_policy_rules", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "verbs", - "type": "TEXT" - }, - { - "name": "api_groups", - "type": "TEXT" - }, - { - "name": "resources", - "type": "TEXT" - }, - { - "name": "resource_names", - "type": "TEXT" - }, - { - "name": "non_resource_urls", - "type": "TEXT" - }, - { - "name": "aggregation_rule", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_role_binding_subjects", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "role_name", - "type": "TEXT" - }, - { - "name": "role_kind", - "type": "TEXT" - }, - { - "name": "subject_name", - "type": "TEXT" - }, - { - "name": "subject_kind", - "type": "TEXT" - }, - { - "name": "subject_namespace", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_role_policy_rules", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "namespace", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "verbs", - "type": "TEXT" - }, - { - "name": "api_groups", - "type": "TEXT" - }, - { - "name": "resources", - "type": "TEXT" - }, - { - "name": "resource_names", - "type": "TEXT" - }, - { - "name": "non_resource_urls", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_csi_drivers", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "attach_required", - "type": "INTEGER" - }, - { - "name": "pod_info_on_mount", - "type": "INTEGER" - }, - { - "name": "volume_lifecycle_modes", - "type": "TEXT" - }, - { - "name": "storage_capacity", - "type": "INTEGER" - }, - { - "name": "fs_group_policy", - "type": "TEXT" - }, - { - "name": "token_requests", - "type": "TEXT" - }, - { - "name": "requires_republish", - "type": "INTEGER" - } - ] - }, - { - "name": "kubernetes_csi_node_drivers", - "columns": [ - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "node_id", - "type": "TEXT" - }, - { - "name": "topology_keys", - "type": "TEXT" - }, - { - "name": "allocatable", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_storage_classes", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "provisioner", - "type": "TEXT" - }, - { - "name": "parameters", - "type": "TEXT" - }, - { - "name": "reclaim_policy", - "type": "TEXT" - }, - { - "name": "mount_options", - "type": "TEXT" - }, - { - "name": "allow_volume_expansion", - "type": "INTEGER" - }, - { - "name": "volume_binding_mode", - "type": "TEXT" - }, - { - "name": "allowed_topologies", - "type": "TEXT" - } - ] - }, - { - "name": "kubernetes_volume_attachments", - "columns": [ - { - "name": "uid", - "type": "TEXT" - }, - { - "name": "cluster_name", - "type": "TEXT" - }, - { - "name": "cluster_uid", - "type": "TEXT" - }, - { - "name": "name", - "type": "TEXT" - }, - { - "name": "creation_timestamp", - "type": "BIGINT" - }, - { - "name": "labels", - "type": "TEXT" - }, - { - "name": "annotations", - "type": "TEXT" - }, - { - "name": "attacher", - "type": "TEXT" - }, - { - "name": "source", - "type": "TEXT" - }, - { - "name": "node_name", - "type": "TEXT" - }, - { - "name": "attached", - "type": "INTEGER" - }, - { - "name": "attachment_metadata", - "type": "TEXT" - }, - { - "name": "attach_error", - "type": "TEXT" - }, - { - "name": "detach_error", - "type": "TEXT" - } - ] - } - ] -} \ No newline at end of file diff --git a/infrastructure/kubequery/etc/kubequery.conf b/infrastructure/kubequery/etc/kubequery.conf deleted file mode 100644 index 5edb025ef4..0000000000 --- a/infrastructure/kubequery/etc/kubequery.conf +++ /dev/null @@ -1,206 +0,0 @@ -{ - "schedule": { - "kubernetes_api_resources": { - "query": "SELECT * FROM kubernetes_api_resources", - "interval": 43200 - }, - "kubernetes_cluster_role_policy_rules": { - "query": "SELECT * FROM kubernetes_cluster_role_policy_rules", - "interval": 1800 - }, - "kubernetes_cluster_role_binding_subjects": { - "query": "SELECT * FROM kubernetes_cluster_role_binding_subjects", - "interval": 1800 - }, - "kubernetes_component_statuses": { - "query": "SELECT * FROM kubernetes_component_statuses", - "interval": 3600 - }, - "kubernetes_config_maps": { - "query": "SELECT * FROM kubernetes_config_maps", - "interval": 600 - }, - "kubernetes_cron_jobs": { - "query": "SELECT * FROM kubernetes_cron_jobs", - "interval": 600 - }, - "kubernetes_csi_drivers": { - "query": "SELECT * FROM kubernetes_csi_drivers", - "interval": 43200 - }, - "kubernetes_csi_node_drivers": { - "query": "SELECT * FROM kubernetes_csi_node_drivers", - "interval": 43200 - }, - "kubernetes_daemon_set_containers": { - "query": "SELECT * FROM kubernetes_daemon_set_containers", - "interval": 600 - }, - "kubernetes_daemon_sets": { - "query": "SELECT * FROM kubernetes_daemon_sets", - "interval": 600 - }, - "kubernetes_daemon_set_volumes": { - "query": "SELECT * FROM kubernetes_daemon_set_volumes", - "interval": 600 - }, - "kubernetes_deployments": { - "query": "SELECT * FROM kubernetes_deployments", - "interval": 600 - }, - "kubernetes_deployments_containers": { - "query": "SELECT * FROM kubernetes_deployments_containers", - "interval": 600 - }, - "kubernetes_deployments_volumes": { - "query": "SELECT * FROM kubernetes_deployments_volumes", - "interval": 600 - }, - "kubernetes_endpoint_subsets": { - "query": "SELECT * FROM kubernetes_endpoint_subsets", - "interval": 1800 - }, - "kubernetes_horizontal_pod_autoscalers": { - "query": "SELECT * FROM kubernetes_horizontal_pod_autoscalers", - "interval": 7200 - }, - "kubernetes_info": { - "query": "SELECT * FROM kubernetes_info", - "interval": 43200 - }, - "kubernetes_ingress_classes": { - "query": "SELECT * FROM kubernetes_ingress_classes", - "interval": 21600 - }, - "kubernetes_ingresses": { - "query": "SELECT * FROM kubernetes_ingresses", - "interval": 21600 - }, - "kubernetes_jobs": { - "query": "SELECT * FROM kubernetes_jobs", - "interval": 600 - }, - "kubernetes_limit_ranges": { - "query": "SELECT * FROM kubernetes_limit_ranges", - "interval": 21600 - }, - "kubernetes_mutating_webhooks": { - "query": "SELECT * FROM kubernetes_mutating_webhooks", - "interval": 7200 - }, - "kubernetes_namespaces": { - "query": "SELECT * FROM kubernetes_namespaces", - "interval": 3600 - }, - "kubernetes_network_policies": { - "query": "SELECT * FROM kubernetes_network_policies", - "interval": 1800 - }, - "kubernetes_nodes": { - "query": "SELECT * FROM kubernetes_nodes", - "interval": 600 - }, - "kubernetes_persistent_volume_claims": { - "query": "SELECT * FROM kubernetes_persistent_volume_claims", - "interval": 1800 - }, - "kubernetes_persistent_volumes": { - "query": "SELECT * FROM kubernetes_persistent_volumes", - "interval": 1800 - }, - "kubernetes_pod_containers": { - "query": "SELECT * FROM kubernetes_pod_containers", - "interval": 600 - }, - "kubernetes_pod_disruption_budgets": { - "query": "SELECT * FROM kubernetes_pod_disruption_budgets", - "interval": 1800 - }, - "kubernetes_pods": { - "query": "SELECT * FROM kubernetes_pods", - "interval": 600 - }, - "kubernetes_pod_security_policies": { - "query": "SELECT * FROM kubernetes_pod_security_policies", - "interval": 600 - }, - "kubernetes_pod_template_containers": { - "query": "SELECT * FROM kubernetes_pod_template_containers", - "interval": 1800 - }, - "kubernetes_pod_templates": { - "query": "SELECT * FROM kubernetes_pod_templates", - "interval": 1800 - }, - "kubernetes_pod_templates_volumes": { - "query": "SELECT * FROM kubernetes_pod_templates_volumes", - "interval": 1800 - }, - "kubernetes_pod_volumes": { - "query": "SELECT * FROM kubernetes_pod_volumes", - "interval": 600 - }, - "kubernetes_replica_set_containers": { - "query": "SELECT * FROM kubernetes_replica_set_containers", - "interval": 600 - }, - "kubernetes_replica_sets": { - "query": "SELECT * FROM kubernetes_replica_sets", - "interval": 600 - }, - "kubernetes_replica_set_volumes": { - "query": "SELECT * FROM kubernetes_replica_set_volumes", - "interval": 600 - }, - "kubernetes_resource_quotas": { - "query": "SELECT * FROM kubernetes_resource_quotas", - "interval": 3600 - }, - "kubernetes_role_binding_subjects": { - "query": "SELECT * FROM kubernetes_role_binding_subjects", - "interval": 600 - }, - "kubernetes_role_policy_rules": { - "query": "SELECT * FROM kubernetes_role_policy_rules", - "interval": 600 - }, - "kubernetes_secrets": { - "query": "SELECT * FROM kubernetes_secrets", - "interval": 600 - }, - "kubernetes_service_accounts": { - "query": "SELECT * FROM kubernetes_service_accounts", - "interval": 600 - }, - "kubernetes_services": { - "query": "SELECT * FROM kubernetes_services", - "interval": 900 - }, - "kubernetes_stateful_set_containers": { - "query": "SELECT * FROM kubernetes_stateful_set_containers", - "interval": 600 - }, - "kubernetes_stateful_sets": { - "query": "SELECT * FROM kubernetes_stateful_sets", - "interval": 600 - }, - "kubernetes_stateful_set_volumes": { - "query": "SELECT * FROM kubernetes_stateful_set_volumes", - "interval": 600 - }, - "kubernetes_storage_classes": { - "query": "SELECT * FROM kubernetes_storage_classes", - "interval": 21600 - }, - "kubernetes_validating_webhooks": { - "query": "SELECT * FROM kubernetes_validating_webhooks", - "interval": 7200 - }, - "kubernetes_volume_attachments": { - "query": "SELECT * FROM kubernetes_volume_attachments", - "interval": 3600 - } - }, - "options":{ - } -} diff --git a/infrastructure/kubequery/etc/kubequery.flags b/infrastructure/kubequery/etc/kubequery.flags deleted file mode 100644 index 3cbeacc6a5..0000000000 --- a/infrastructure/kubequery/etc/kubequery.flags +++ /dev/null @@ -1,2 +0,0 @@ ---disable_events_staging=true ---schedule_splay_percent=50 diff --git a/infrastructure/kubequery/etc/kubequery.flags.tls b/infrastructure/kubequery/etc/kubequery.flags.tls deleted file mode 100644 index 6237b08afd..0000000000 --- a/infrastructure/kubequery/etc/kubequery.flags.tls +++ /dev/null @@ -1,9 +0,0 @@ ---schedule_splay_percent=50 ---disable_watchdog=true ---tls_hostname=z6c0b1jca0.execute-api.us-west-2.amazonaws.com ---tls_server_certs=/opt/uptycs/etc/certs.pem ---enroll_secret_path=/opt/uptycs/config/enroll.secret ---enroll_tls_endpoint=/default/enroll ---logger_plugin=tls ---logger_tls_endpoint=/default/log ---logger_tls_compress=true diff --git a/infrastructure/kubequery/go.mod b/infrastructure/kubequery/go.mod deleted file mode 100644 index f152612265..0000000000 --- a/infrastructure/kubequery/go.mod +++ /dev/null @@ -1,58 +0,0 @@ -module github.com/Uptycs/kubequery - -go 1.17 - -require ( - github.com/Uptycs/basequery-go v0.8.0 - github.com/google/uuid v1.3.0 - github.com/iancoleman/strcase v0.2.0 - github.com/stretchr/testify v1.7.0 - golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 - k8s.io/api v0.22.4 - k8s.io/apimachinery v0.22.4 - k8s.io/client-go v0.22.4 -) - -require ( - github.com/Microsoft/go-winio v0.5.1 // indirect - github.com/apache/thrift v0.15.0 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/evanphx/json-patch v4.11.0+incompatible // indirect - github.com/go-logr/logr v1.2.1 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/google/go-cmp v0.5.6 // indirect - github.com/google/gofuzz v1.2.0 // indirect - github.com/googleapis/gnostic v0.5.5 // indirect - github.com/imdario/mergo v0.3.12 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.11.1 // indirect - github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.32.1 // indirect - github.com/prometheus/procfs v0.7.3 // indirect - github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/net v0.24.0 // indirect - golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/term v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect - golang.org/x/tools v0.6.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.33.0 // indirect - gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0 // indirect - k8s.io/klog/v2 v2.30.0 // indirect - k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c // indirect - k8s.io/utils v0.0.0-20211203121628-587287796c64 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.0 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect -) diff --git a/infrastructure/kubequery/go.sum b/infrastructure/kubequery/go.sum deleted file mode 100644 index 2876970659..0000000000 --- a/infrastructure/kubequery/go.sum +++ /dev/null @@ -1,673 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= -github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/Uptycs/basequery-go v0.8.0 h1:a1g1ikKKOCnHGzqxfiXsvquUJU5hNcZkjtfTJXcEKcg= -github.com/Uptycs/basequery-go v0.8.0/go.mod h1:U46Bme4Zi+bKG+wYVw2XFfk3bHs0WWzpWQ2R+ivgEd4= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/apache/thrift v0.14.2/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/apache/thrift v0.15.0 h1:aGvdaR0v1t9XLgjtBYwxcBvBOTMqClzwE26CHOgjW1Y= -github.com/apache/thrift v0.15.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs= -github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= -github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= -github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s= -github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M= -golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.22.4 h1:UvyHW0ezB2oIgHAxlYoo6UJQObYXU7awuNarwoHEOjw= -k8s.io/api v0.22.4/go.mod h1:Rgs+9gIGYC5laXQSZZ9JqT5NevNgoGiOdVWi1BAB3qk= -k8s.io/apimachinery v0.22.4 h1:9uwcvPpukBw/Ri0EUmWz+49cnFtaoiyEhQTK+xOe7Ck= -k8s.io/apimachinery v0.22.4/go.mod h1:yU6oA6Gnax9RrxGzVvPFFJ+mpnW6PBSqp0sx0I0HHW0= -k8s.io/client-go v0.22.4 h1:aAQ1Wk+I3bjCNk35YWUqbaueqrIonkfDPJSPDDe8Kfg= -k8s.io/client-go v0.22.4/go.mod h1:Yzw4e5e7h1LNHA4uqnMVrpEpUs1hJOiuBsJKIlRCHDA= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw= -k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c h1:jvamsI1tn9V0S8jicyX82qaFC0H/NKxv2e5mbqsgR80= -k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20211203121628-587287796c64 h1:EzpFOlqWaj9Qbd/q4TqWSSpaQ/3p30lV1hGvcMYKLWc= -k8s.io/utils v0.0.0-20211203121628-587287796c64/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/structured-merge-diff/v4 v4.2.0 h1:kDvPBbnPk+qYmkHmSo8vKGp438IASWofnbbUKDE/bv0= -sigs.k8s.io/structured-merge-diff/v4 v4.2.0/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/infrastructure/kubequery/integration/index.js b/infrastructure/kubequery/integration/index.js deleted file mode 100644 index b3622c3bbe..0000000000 --- a/infrastructure/kubequery/integration/index.js +++ /dev/null @@ -1,94 +0,0 @@ -'use strict'; - -const util = require('util'); -const execFile = util.promisify(require('child_process').execFile); - -const EXEC_OPTIONS = { timeout: 10000, maxBuffer: 10 * 1024 * 1024 }; - -const TABLES = [ - 'api_resources', - 'cluster_role_binding_subjects', - 'cluster_role_policy_rules', - 'component_statuses', - 'config_maps', - 'cron_jobs', - 'csi_drivers', - 'csi_node_drivers', - 'daemon_set_containers', - 'daemon_set_volumes', - 'daemon_sets', - 'deployments', - 'deployments_containers', - 'deployments_volumes', - 'endpoint_subsets', - 'events', - 'horizontal_pod_autoscalers', - 'info', - 'ingress_classes', - 'ingresses', - 'jobs', - 'limit_ranges', - 'mutating_webhooks', - 'namespaces', - 'network_policies', - 'nodes', - 'persistent_volume_claims', - 'persistent_volumes', - 'pod_containers', - 'pod_disruption_budgets', - 'pod_security_policies', - 'pod_template_containers', - 'pod_templates', - 'pod_templates_volumes', - 'pod_volumes', - 'pods', - 'replica_set_containers', - 'replica_set_volumes', - 'replica_sets', - 'resource_quotas', - 'role_binding_subjects', - 'role_policy_rules', - 'secrets', - 'service_accounts', - 'services', - 'stateful_set_containers', - 'stateful_set_volumes', - 'stateful_sets', - 'storage_classes', - 'validating_webhooks', - 'volume_attachments' -]; - -async function getPodName() { - const { stdout, stderr } = await execFile('kubectl', ['get', 'pods', '-n', 'kubequery', '-o', "jsonpath={.items[0].metadata.name}"], EXEC_OPTIONS); - if (stdout) { - return stdout; - } - throw new Error('Failed to get kubequery pod name'); -} - -async function executeSQL(podName, sql) { - const { stdout, stderr } = await execFile('kubectl', ['exec', '-it', podName, '-n', 'kubequery', '--', 'sh', '-c', `kubequeryi --json '${sql}'`], EXEC_OPTIONS); - if (stdout) { - return stdout; - } - throw new Error('Failed to execute SQL: ' + sql + '. Error: ' + stderr); -} - -(async () => { - try { - const podName = await getPodName(); - for (const table of TABLES) { - const output = await executeSQL(podName, 'SELECT * FROM kubernetes_' + table); - console.assert(output !== '', 'Invalid output for table: ' + table); - - const json = JSON.parse(output); - console.assert(Array.isArray(json), 'Table output is not an array: ' + table); - - console.info(table + ': ' + json.length); - } - } catch (err) { - console.error(err); - process.exit(1); - } -})(); diff --git a/infrastructure/kubequery/internal/k8s/admissionregistration/mutating_webhook.go b/infrastructure/kubequery/internal/k8s/admissionregistration/mutating_webhook.go deleted file mode 100644 index ace121f184..0000000000 --- a/infrastructure/kubequery/internal/k8s/admissionregistration/mutating_webhook.go +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package admissionregistration - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/admissionregistration/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" -) - -type mutatingWebhook struct { - ClusterName string - ClusterUID types.UID - v1.MutatingWebhook -} - -// MutatingWebhookColumns returns kubernetes mutating webhook fields as Osquery table columns. -func MutatingWebhookColumns() []table.ColumnDefinition { - return k8s.GetSchema(&mutatingWebhook{}) -} - -// MutatingWebhooksGenerate generates the mutating webhook Osquery table data. -func MutatingWebhooksGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - mwcs, err := k8s.GetClient().AdmissionregistrationV1().MutatingWebhookConfigurations().List(ctx, options) - if err != nil { - return nil, err - } - - for _, mwc := range mwcs.Items { - for _, mw := range mwc.Webhooks { - item := &mutatingWebhook{ - ClusterName: k8s.GetClusterName(), - ClusterUID: k8s.GetClusterUID(), - MutatingWebhook: mw, - } - results = append(results, k8s.ToMap(item)) - } - } - - if mwcs.Continue == "" { - break - } - options.Continue = mwcs.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/admissionregistration/mutating_webhook_test.go b/infrastructure/kubequery/internal/k8s/admissionregistration/mutating_webhook_test.go deleted file mode 100644 index f9e90f3be5..0000000000 --- a/infrastructure/kubequery/internal/k8s/admissionregistration/mutating_webhook_test.go +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package admissionregistration - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/admissionregistration/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/fake" -) - -func TestMutatingWebhooksGenerate(t *testing.T) { - i32 := int32(123) - url := string("https://www.google.com") - k8s.SetClient(fake.NewSimpleClientset(&v1.MutatingWebhookConfiguration{ - Webhooks: []v1.MutatingWebhook{ - { - Name: "mw1", - TimeoutSeconds: &i32, - ClientConfig: v1.WebhookClientConfig{URL: &url}, - }, - { - Name: "mw2", - TimeoutSeconds: &i32, - ClientConfig: v1.WebhookClientConfig{URL: &url}, - }, - }, - }), types.UID(""), "") - - mws, err := MutatingWebhooksGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "name": "mw1", - "timeout_seconds": "123", - "client_config": "{\"url\":\"https://www.google.com\"}", - }, - { - "name": "mw2", - "timeout_seconds": "123", - "client_config": "{\"url\":\"https://www.google.com\"}", - }, - }, mws) -} diff --git a/infrastructure/kubequery/internal/k8s/admissionregistration/validating_webhook.go b/infrastructure/kubequery/internal/k8s/admissionregistration/validating_webhook.go deleted file mode 100644 index 900c40be87..0000000000 --- a/infrastructure/kubequery/internal/k8s/admissionregistration/validating_webhook.go +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package admissionregistration - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/admissionregistration/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" -) - -type validatingWebhook struct { - ClusterName string - ClusterUID types.UID - v1.ValidatingWebhook -} - -// ValidatingWebhookColumns returns kubernetes validating webhook fields as Osquery table columns. -func ValidatingWebhookColumns() []table.ColumnDefinition { - return k8s.GetSchema(&validatingWebhook{}) -} - -// ValidatingWebhooksGenerate generates the kubernetes validating webhooks as Osquery table data. -func ValidatingWebhooksGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - vwcs, err := k8s.GetClient().AdmissionregistrationV1().ValidatingWebhookConfigurations().List(ctx, options) - if err != nil { - return nil, err - } - - for _, vwc := range vwcs.Items { - for _, vw := range vwc.Webhooks { - item := &validatingWebhook{ - ClusterName: k8s.GetClusterName(), - ClusterUID: k8s.GetClusterUID(), - ValidatingWebhook: vw, - } - results = append(results, k8s.ToMap(item)) - } - } - - if vwcs.Continue == "" { - break - } - options.Continue = vwcs.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/admissionregistration/validating_webhook_test.go b/infrastructure/kubequery/internal/k8s/admissionregistration/validating_webhook_test.go deleted file mode 100644 index 695b314c0e..0000000000 --- a/infrastructure/kubequery/internal/k8s/admissionregistration/validating_webhook_test.go +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package admissionregistration - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/admissionregistration/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/fake" -) - -func TestValidatingWebhooksGenerate(t *testing.T) { - i32 := int32(123) - url := string("https://www.google.com") - k8s.SetClient(fake.NewSimpleClientset(&v1.ValidatingWebhookConfiguration{ - Webhooks: []v1.ValidatingWebhook{ - { - Name: "vw1", - TimeoutSeconds: &i32, - ClientConfig: v1.WebhookClientConfig{URL: &url}, - }, - { - Name: "vw2", - TimeoutSeconds: &i32, - ClientConfig: v1.WebhookClientConfig{URL: &url}, - }, - }, - }), types.UID(""), "") - - mws, err := ValidatingWebhooksGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "name": "vw1", - "timeout_seconds": "123", - "client_config": "{\"url\":\"https://www.google.com\"}", - }, - { - "name": "vw2", - "timeout_seconds": "123", - "client_config": "{\"url\":\"https://www.google.com\"}", - }, - }, mws) -} diff --git a/infrastructure/kubequery/internal/k8s/apps/daemon_set.go b/infrastructure/kubequery/internal/k8s/apps/daemon_set.go deleted file mode 100644 index 77db54c9c7..0000000000 --- a/infrastructure/kubequery/internal/k8s/apps/daemon_set.go +++ /dev/null @@ -1,175 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package apps - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/apps/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type daemonSet struct { - k8s.CommonNamespacedFields - k8s.CommonPodFields - v1.DaemonSetStatus - Selector *metav1.LabelSelector - UpdateStrategy v1.DaemonSetUpdateStrategy - MinReadySeconds int32 - RevisionHistoryLimit *int32 -} - -// DaemonSetColumns returns kubernetes daemon set fields as Osquery table columns. -func DaemonSetColumns() []table.ColumnDefinition { - return k8s.GetSchema(&daemonSet{}) -} - -// DaemonSetsGenerate generates the kubernetes daemon sets as Osquery table data. -func DaemonSetsGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - dss, err := k8s.GetClient().AppsV1().DaemonSets(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, ds := range dss.Items { - item := &daemonSet{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(ds.ObjectMeta), - CommonPodFields: k8s.GetCommonPodFields(ds.Spec.Template.Spec), - DaemonSetStatus: ds.Status, - Selector: ds.Spec.Selector, - UpdateStrategy: ds.Spec.UpdateStrategy, - MinReadySeconds: ds.Spec.MinReadySeconds, - RevisionHistoryLimit: ds.Spec.RevisionHistoryLimit, - } - results = append(results, k8s.ToMap(item)) - } - - if dss.Continue == "" { - break - } - options.Continue = dss.Continue - } - - return results, nil -} - -type daemonSetContainer struct { - k8s.CommonNamespacedFields - k8s.CommonContainerFields - DaemonSetName string - ContainerType string -} - -// DaemonSetContainerColumns returns kubernetes daemon set container fields as Osquery table columns. -func DaemonSetContainerColumns() []table.ColumnDefinition { - return k8s.GetSchema(&daemonSetContainer{}) -} - -// DaemonSetContainersGenerate generates the kubernetes daemon set containers as Osquery table data. -func DaemonSetContainersGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - dss, err := k8s.GetClient().AppsV1().DaemonSets(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, ds := range dss.Items { - for _, c := range ds.Spec.Template.Spec.InitContainers { - item := &daemonSetContainer{ - CommonNamespacedFields: k8s.GetParentCommonNamespacedFields(ds.ObjectMeta, c.Name), - CommonContainerFields: k8s.GetCommonContainerFields(c), - DaemonSetName: ds.Name, - ContainerType: "init", - } - item.Name = c.Name - results = append(results, k8s.ToMap(item)) - } - for _, c := range ds.Spec.Template.Spec.Containers { - item := &daemonSetContainer{ - CommonNamespacedFields: k8s.GetParentCommonNamespacedFields(ds.ObjectMeta, c.Name), - CommonContainerFields: k8s.GetCommonContainerFields(c), - DaemonSetName: ds.Name, - ContainerType: "container", - } - item.Name = c.Name - results = append(results, k8s.ToMap(item)) - } - for _, c := range ds.Spec.Template.Spec.EphemeralContainers { - item := &daemonSetContainer{ - CommonNamespacedFields: k8s.GetParentCommonNamespacedFields(ds.ObjectMeta, c.Name), - CommonContainerFields: k8s.GetCommonEphemeralContainerFields(c), - DaemonSetName: ds.Name, - ContainerType: "ephemeral", - } - item.Name = c.Name - results = append(results, k8s.ToMap(item)) - } - } - - if dss.Continue == "" { - break - } - options.Continue = dss.Continue - } - - return results, nil -} - -type daemonSetVolume struct { - k8s.CommonNamespacedFields - k8s.CommonVolumeFields - DaemonSetName string -} - -// DaemonSetVolumeColumns returns kubernetes daemon set volume fields as Osquery table columns. -func DaemonSetVolumeColumns() []table.ColumnDefinition { - return k8s.GetSchema(&daemonSetVolume{}) -} - -// DaemonSetVolumesGenerate generates the kubernetes daemon set volumes as Osquery table data. -func DaemonSetVolumesGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - dss, err := k8s.GetClient().AppsV1().DaemonSets(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, ds := range dss.Items { - for _, v := range ds.Spec.Template.Spec.Volumes { - item := &daemonSetVolume{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(ds.ObjectMeta), - CommonVolumeFields: k8s.GetCommonVolumeFields(v), - DaemonSetName: ds.Name, - } - item.Name = v.Name - results = append(results, k8s.ToMap(item)) - } - } - - if dss.Continue == "" { - break - } - options.Continue = dss.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/apps/daemon_set_test.go b/infrastructure/kubequery/internal/k8s/apps/daemon_set_test.go deleted file mode 100644 index b51692e2ae..0000000000 --- a/infrastructure/kubequery/internal/k8s/apps/daemon_set_test.go +++ /dev/null @@ -1,324 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package apps - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestDaemonSetsGenerate(t *testing.T) { - dss, err := DaemonSetsGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "annotations": "{\"deprecated.daemonset.template.generation\":\"1\"}", - "cluster_uid": "blah", - "creation_timestamp": "1610476216", - "current_number_scheduled": "1", - "desired_number_scheduled": "1", - "dns_policy": "ClusterFirst", - "host_ipc": "0", - "host_network": "1", - "host_pid": "0", - "labels": "{\"k8s-app\":\"calico-node\"}", - "min_ready_seconds": "0", - "name": "calico-node", - "namespace": "kube-system", - "node_selector": "{\"kubernetes.io/os\":\"linux\"}", - "number_available": "1", - "number_misscheduled": "0", - "number_ready": "1", - "number_unavailable": "0", - "observed_generation": "1", - "priority_class_name": "system-node-critical", - "restart_policy": "Always", - "revision_history_limit": "10", - "scheduler_name": "default-scheduler", - "selector": "{\"matchLabels\":{\"k8s-app\":\"calico-node\"}}", - "service_account_name": "calico-node", - "termination_grace_period_seconds": "0", - "tolerations": "[{\"operator\":\"Exists\",\"effect\":\"NoSchedule\"},{\"key\":\"CriticalAddonsOnly\",\"operator\":\"Exists\"},{\"operator\":\"Exists\",\"effect\":\"NoExecute\"}]", - "uid": "e6fed7f0-f79a-464f-a3d2-b63247a1f590", - "update_strategy": "{\"type\":\"RollingUpdate\",\"rollingUpdate\":{\"maxUnavailable\":1}}", - "updated_number_scheduled": "1", - }, - }, dss) -} - -func TestDaemonSetContainersGenerate(t *testing.T) { - dss, err := DaemonSetContainersGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "annotations": "{\"deprecated.daemonset.template.generation\":\"1\"}", - "cluster_uid": "blah", - "command": "[\"/opt/cni/bin/calico-ipam\",\"-upgrade\"]", - "container_type": "init", - "creation_timestamp": "1610476216", - "daemon_set_name": "calico-node", - "env": "[{\"name\":\"KUBERNETES_NODE_NAME\",\"valueFrom\":{\"fieldRef\":{\"apiVersion\":\"v1\",\"fieldPath\":\"spec.nodeName\"}}},{\"name\":\"CALICO_NETWORKING_BACKEND\",\"valueFrom\":{\"configMapKeyRef\":{\"name\":\"calico-config\",\"key\":\"calico_backend\"}}}]", - "image": "calico/cni:v3.13.2", - "image_pull_policy": "IfNotPresent", - "labels": "{\"k8s-app\":\"calico-node\"}", - "name": "upgrade-ipam", - "namespace": "kube-system", - "privileged": "1", - "stdin": "0", - "stdin_once": "0", - "termination_message_path": "/dev/termination-log", - "termination_message_policy": "File", - "tty": "0", - "uid": "8b0b4bb2-1703-551e-9e14-af10886a5eec", - "volume_mounts": "[{\"name\":\"host-local-net-dir\",\"mountPath\":\"/var/lib/cni/networks\"},{\"name\":\"cni-bin-dir\",\"mountPath\":\"/host/opt/cni/bin\"}]", - }, - { - "annotations": "{\"deprecated.daemonset.template.generation\":\"1\"}", - "cluster_uid": "blah", - "command": "[\"/install-cni.sh\"]", - "container_type": "init", - "creation_timestamp": "1610476216", - "daemon_set_name": "calico-node", - "env": "[{\"name\":\"CNI_CONF_NAME\",\"value\":\"10-calico.conflist\"},{\"name\":\"CNI_NETWORK_CONFIG\",\"valueFrom\":{\"configMapKeyRef\":{\"name\":\"calico-config\",\"key\":\"cni_network_config\"}}},{\"name\":\"KUBERNETES_NODE_NAME\",\"valueFrom\":{\"fieldRef\":{\"apiVersion\":\"v1\",\"fieldPath\":\"spec.nodeName\"}}},{\"name\":\"CNI_MTU\",\"valueFrom\":{\"configMapKeyRef\":{\"name\":\"calico-config\",\"key\":\"veth_mtu\"}}},{\"name\":\"SLEEP\",\"value\":\"false\"},{\"name\":\"CNI_NET_DIR\",\"value\":\"/var/snap/microk8s/current/args/cni-network\"}]", - "image": "calico/cni:v3.13.2", - "image_pull_policy": "IfNotPresent", - "labels": "{\"k8s-app\":\"calico-node\"}", - "name": "install-cni", - "namespace": "kube-system", - "privileged": "1", - "stdin": "0", - "stdin_once": "0", - "termination_message_path": "/dev/termination-log", - "termination_message_policy": "File", - "tty": "0", - "uid": "e773308e-cb75-5c58-9d85-0b71c92f8a24", - "volume_mounts": "[{\"name\":\"cni-bin-dir\",\"mountPath\":\"/host/opt/cni/bin\"},{\"name\":\"cni-net-dir\",\"mountPath\":\"/host/etc/cni/net.d\"}]", - }, - { - "annotations": "{\"deprecated.daemonset.template.generation\":\"1\"}", - "cluster_uid": "blah", - "container_type": "init", - "creation_timestamp": "1610476216", - "daemon_set_name": "calico-node", - "image": "calico/pod2daemon-flexvol:v3.13.2", - "image_pull_policy": "IfNotPresent", - "labels": "{\"k8s-app\":\"calico-node\"}", - "name": "flexvol-driver", - "namespace": "kube-system", - "privileged": "1", - "stdin": "0", - "stdin_once": "0", - "termination_message_path": "/dev/termination-log", - "termination_message_policy": "File", - "tty": "0", - "uid": "8122bba4-1bdc-562f-9a01-96345dbc3e4c", - "volume_mounts": "[{\"name\":\"flexvol-driver-host\",\"mountPath\":\"/host/driver\"}]", - }, - { - "annotations": "{\"deprecated.daemonset.template.generation\":\"1\"}", - "cluster_uid": "blah", - "container_type": "container", - "creation_timestamp": "1610476216", - "daemon_set_name": "calico-node", - "env": "[{\"name\":\"DATASTORE_TYPE\",\"value\":\"kubernetes\"},{\"name\":\"WAIT_FOR_DATASTORE\",\"value\":\"true\"},{\"name\":\"NODENAME\",\"valueFrom\":{\"fieldRef\":{\"apiVersion\":\"v1\",\"fieldPath\":\"spec.nodeName\"}}},{\"name\":\"CALICO_NETWORKING_BACKEND\",\"valueFrom\":{\"configMapKeyRef\":{\"name\":\"calico-config\",\"key\":\"calico_backend\"}}},{\"name\":\"CLUSTER_TYPE\",\"value\":\"k8s,bgp\"},{\"name\":\"IP\",\"value\":\"autodetect\"},{\"name\":\"IP_AUTODETECTION_METHOD\",\"value\":\"first-found\"},{\"name\":\"CALICO_IPV4POOL_VXLAN\",\"value\":\"Always\"},{\"name\":\"FELIX_IPINIPMTU\",\"valueFrom\":{\"configMapKeyRef\":{\"name\":\"calico-config\",\"key\":\"veth_mtu\"}}},{\"name\":\"CALICO_IPV4POOL_CIDR\",\"value\":\"10.1.0.0/16\"},{\"name\":\"CALICO_DISABLE_FILE_LOGGING\",\"value\":\"true\"},{\"name\":\"FELIX_DEFAULTENDPOINTTOHOSTACTION\",\"value\":\"ACCEPT\"},{\"name\":\"FELIX_IPV6SUPPORT\",\"value\":\"false\"},{\"name\":\"FELIX_LOGSEVERITYSCREEN\",\"value\":\"error\"},{\"name\":\"FELIX_HEALTHENABLED\",\"value\":\"true\"}]", - "image": "calico/node:v3.13.2", - "image_pull_policy": "IfNotPresent", - "labels": "{\"k8s-app\":\"calico-node\"}", - "liveness_probe": "{\"exec\":{\"command\":[\"/bin/calico-node\",\"-felix-live\"]},\"initialDelaySeconds\":10,\"timeoutSeconds\":1,\"periodSeconds\":10,\"successThreshold\":1,\"failureThreshold\":6}", - "name": "calico-node", - "namespace": "kube-system", - "privileged": "1", - "readiness_probe": "{\"exec\":{\"command\":[\"/bin/calico-node\",\"-felix-ready\"]},\"timeoutSeconds\":1,\"periodSeconds\":10,\"successThreshold\":1,\"failureThreshold\":3}", - "resource_requests": "{\"cpu\":\"250m\"}", - "stdin": "0", - "stdin_once": "0", - "termination_message_path": "/dev/termination-log", - "termination_message_policy": "File", - "tty": "0", - "uid": "7f7da4e6-2c04-5e4e-aedb-bd1e9e8e5469", - "volume_mounts": "[{\"name\":\"lib-modules\",\"readOnly\":true,\"mountPath\":\"/lib/modules\"},{\"name\":\"xtables-lock\",\"mountPath\":\"/run/xtables.lock\"},{\"name\":\"var-run-calico\",\"mountPath\":\"/var/run/calico\"},{\"name\":\"var-lib-calico\",\"mountPath\":\"/var/lib/calico\"},{\"name\":\"policysync\",\"mountPath\":\"/var/run/nodeagent\"}]", - }, - }, dss) -} - -func TestDaemonSetVolumesGenerate(t *testing.T) { - dss, err := DaemonSetVolumesGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "annotations": "{\"deprecated.daemonset.template.generation\":\"1\"}", - "aws_elastic_block_store_partition": "0", - "cluster_uid": "blah", - "creation_timestamp": "1610476216", - "daemon_set_name": "calico-node", - "gce_persistent_disk_partition": "0", - "host_path_path": "/lib/modules", - "iscsi_discovery_chap_auth": "0", - "iscsi_lun": "0", - "iscsi_session_chap_auth": "0", - "labels": "{\"k8s-app\":\"calico-node\"}", - "name": "lib-modules", - "namespace": "kube-system", - "scale_iossl_enabled": "0", - "uid": "e6fed7f0-f79a-464f-a3d2-b63247a1f590", - "volume_type": "host_path", - }, - { - "annotations": "{\"deprecated.daemonset.template.generation\":\"1\"}", - "aws_elastic_block_store_partition": "0", - "cluster_uid": "blah", - "creation_timestamp": "1610476216", - "daemon_set_name": "calico-node", - "gce_persistent_disk_partition": "0", - "host_path_path": "/var/snap/microk8s/current/var/run/calico", - "iscsi_discovery_chap_auth": "0", - "iscsi_lun": "0", - "iscsi_session_chap_auth": "0", - "labels": "{\"k8s-app\":\"calico-node\"}", - "name": "var-run-calico", - "namespace": "kube-system", - "scale_iossl_enabled": "0", - "uid": "e6fed7f0-f79a-464f-a3d2-b63247a1f590", - "volume_type": "host_path", - }, - { - "annotations": "{\"deprecated.daemonset.template.generation\":\"1\"}", - "aws_elastic_block_store_partition": "0", - "cluster_uid": "blah", - "creation_timestamp": "1610476216", - "daemon_set_name": "calico-node", - "gce_persistent_disk_partition": "0", - "host_path_path": "/var/snap/microk8s/current/var/lib/calico", - "iscsi_discovery_chap_auth": "0", - "iscsi_lun": "0", - "iscsi_session_chap_auth": "0", - "labels": "{\"k8s-app\":\"calico-node\"}", - "name": "var-lib-calico", - "namespace": "kube-system", - "scale_iossl_enabled": "0", - "uid": "e6fed7f0-f79a-464f-a3d2-b63247a1f590", - "volume_type": "host_path", - }, - { - "annotations": "{\"deprecated.daemonset.template.generation\":\"1\"}", - "aws_elastic_block_store_partition": "0", - "cluster_uid": "blah", - "creation_timestamp": "1610476216", - "daemon_set_name": "calico-node", - "gce_persistent_disk_partition": "0", - "host_path_path": "/run/xtables.lock", - "host_path_type": "FileOrCreate", - "iscsi_discovery_chap_auth": "0", - "iscsi_lun": "0", - "iscsi_session_chap_auth": "0", - "labels": "{\"k8s-app\":\"calico-node\"}", - "name": "xtables-lock", - "namespace": "kube-system", - "scale_iossl_enabled": "0", - "uid": "e6fed7f0-f79a-464f-a3d2-b63247a1f590", - "volume_type": "host_path", - }, - { - "annotations": "{\"deprecated.daemonset.template.generation\":\"1\"}", - "aws_elastic_block_store_partition": "0", - "cluster_uid": "blah", - "creation_timestamp": "1610476216", - "daemon_set_name": "calico-node", - "gce_persistent_disk_partition": "0", - "host_path_path": "/var/snap/microk8s/current/opt/cni/bin", - "iscsi_discovery_chap_auth": "0", - "iscsi_lun": "0", - "iscsi_session_chap_auth": "0", - "labels": "{\"k8s-app\":\"calico-node\"}", - "name": "cni-bin-dir", - "namespace": "kube-system", - "scale_iossl_enabled": "0", - "uid": "e6fed7f0-f79a-464f-a3d2-b63247a1f590", - "volume_type": "host_path", - }, - { - "annotations": "{\"deprecated.daemonset.template.generation\":\"1\"}", - "aws_elastic_block_store_partition": "0", - "cluster_uid": "blah", - "creation_timestamp": "1610476216", - "daemon_set_name": "calico-node", - "gce_persistent_disk_partition": "0", - "host_path_path": "/var/snap/microk8s/current/args/cni-network", - "iscsi_discovery_chap_auth": "0", - "iscsi_lun": "0", - "iscsi_session_chap_auth": "0", - "labels": "{\"k8s-app\":\"calico-node\"}", - "name": "cni-net-dir", - "namespace": "kube-system", - "scale_iossl_enabled": "0", - "uid": "e6fed7f0-f79a-464f-a3d2-b63247a1f590", - "volume_type": "host_path", - }, - { - "annotations": "{\"deprecated.daemonset.template.generation\":\"1\"}", - "aws_elastic_block_store_partition": "0", - "cluster_uid": "blah", - "creation_timestamp": "1610476216", - "daemon_set_name": "calico-node", - "gce_persistent_disk_partition": "0", - "host_path_path": "/var/snap/microk8s/current/var/lib/cni/networks", - "iscsi_discovery_chap_auth": "0", - "iscsi_lun": "0", - "iscsi_session_chap_auth": "0", - "labels": "{\"k8s-app\":\"calico-node\"}", - "name": "host-local-net-dir", - "namespace": "kube-system", - "scale_iossl_enabled": "0", - "uid": "e6fed7f0-f79a-464f-a3d2-b63247a1f590", - "volume_type": "host_path", - }, - { - "annotations": "{\"deprecated.daemonset.template.generation\":\"1\"}", - "aws_elastic_block_store_partition": "0", - "cluster_uid": "blah", - "creation_timestamp": "1610476216", - "daemon_set_name": "calico-node", - "gce_persistent_disk_partition": "0", - "host_path_path": "/var/snap/microk8s/current/var/run/nodeagent", - "host_path_type": "DirectoryOrCreate", - "iscsi_discovery_chap_auth": "0", - "iscsi_lun": "0", - "iscsi_session_chap_auth": "0", - "labels": "{\"k8s-app\":\"calico-node\"}", - "name": "policysync", - "namespace": "kube-system", - "scale_iossl_enabled": "0", - "uid": "e6fed7f0-f79a-464f-a3d2-b63247a1f590", - "volume_type": "host_path", - }, - { - "annotations": "{\"deprecated.daemonset.template.generation\":\"1\"}", - "aws_elastic_block_store_partition": "0", - "cluster_uid": "blah", - "creation_timestamp": "1610476216", - "daemon_set_name": "calico-node", - "gce_persistent_disk_partition": "0", - "host_path_path": "/usr/libexec/kubernetes/kubelet-plugins/volume/exec/nodeagent~uds", - "host_path_type": "DirectoryOrCreate", - "iscsi_discovery_chap_auth": "0", - "iscsi_lun": "0", - "iscsi_session_chap_auth": "0", - "labels": "{\"k8s-app\":\"calico-node\"}", - "name": "flexvol-driver-host", - "namespace": "kube-system", - "scale_iossl_enabled": "0", - "uid": "e6fed7f0-f79a-464f-a3d2-b63247a1f590", - "volume_type": "host_path", - }, - }, dss) -} diff --git a/infrastructure/kubequery/internal/k8s/apps/deployment.go b/infrastructure/kubequery/internal/k8s/apps/deployment.go deleted file mode 100644 index 7b243efa8a..0000000000 --- a/infrastructure/kubequery/internal/k8s/apps/deployment.go +++ /dev/null @@ -1,181 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package apps - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/apps/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type deployment struct { - k8s.CommonNamespacedFields - k8s.CommonPodFields - v1.DeploymentStatus - DeploymentReplicas *int32 - Selector *metav1.LabelSelector - Strategy v1.DeploymentStrategy - MinReadySeconds int32 - RevisionHistoryLimit *int32 - Paused bool - ProgressDeadlineSeconds *int32 -} - -// DeploymentColumns returns kubernetes deployment fields as Osquery table columns. -func DeploymentColumns() []table.ColumnDefinition { - return k8s.GetSchema(&deployment{}) -} - -// DeploymentsGenerate generates the kubernetes deployments as Osquery table data. -func DeploymentsGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - ds, err := k8s.GetClient().AppsV1().Deployments(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, d := range ds.Items { - item := &deployment{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(d.ObjectMeta), - CommonPodFields: k8s.GetCommonPodFields(d.Spec.Template.Spec), - DeploymentReplicas: d.Spec.Replicas, - Selector: d.Spec.Selector, - Strategy: d.Spec.Strategy, - MinReadySeconds: d.Spec.MinReadySeconds, - RevisionHistoryLimit: d.Spec.RevisionHistoryLimit, - Paused: d.Spec.Paused, - ProgressDeadlineSeconds: d.Spec.ProgressDeadlineSeconds, - DeploymentStatus: d.Status, - } - results = append(results, k8s.ToMap(item)) - } - - if ds.Continue == "" { - break - } - options.Continue = ds.Continue - } - - return results, nil -} - -type deploymentContainer struct { - k8s.CommonNamespacedFields - k8s.CommonContainerFields - DeploymentName string - ContainerType string -} - -// DeploymentContainerColumns returns kubernetes deployment container fields as Osquery table columns. -func DeploymentContainerColumns() []table.ColumnDefinition { - return k8s.GetSchema(&deploymentContainer{}) -} - -// DeploymentContainersGenerate generates the kubernetes deployment containers as Osquery table data. -func DeploymentContainersGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - ds, err := k8s.GetClient().AppsV1().Deployments(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, d := range ds.Items { - for _, c := range d.Spec.Template.Spec.InitContainers { - item := &deploymentContainer{ - CommonNamespacedFields: k8s.GetParentCommonNamespacedFields(d.ObjectMeta, c.Name), - CommonContainerFields: k8s.GetCommonContainerFields(c), - DeploymentName: d.Name, - ContainerType: "init", - } - item.Name = c.Name - results = append(results, k8s.ToMap(item)) - } - for _, c := range d.Spec.Template.Spec.Containers { - item := &deploymentContainer{ - CommonNamespacedFields: k8s.GetParentCommonNamespacedFields(d.ObjectMeta, c.Name), - CommonContainerFields: k8s.GetCommonContainerFields(c), - DeploymentName: d.Name, - ContainerType: "container", - } - item.Name = c.Name - results = append(results, k8s.ToMap(item)) - } - for _, c := range d.Spec.Template.Spec.EphemeralContainers { - item := &deploymentContainer{ - CommonNamespacedFields: k8s.GetParentCommonNamespacedFields(d.ObjectMeta, c.Name), - CommonContainerFields: k8s.GetCommonEphemeralContainerFields(c), - DeploymentName: d.Name, - ContainerType: "ephemeral", - } - item.Name = c.Name - results = append(results, k8s.ToMap(item)) - } - } - - if ds.Continue == "" { - break - } - options.Continue = ds.Continue - } - - return results, nil -} - -type deploymentVolume struct { - k8s.CommonNamespacedFields - k8s.CommonVolumeFields - DeploymentName string -} - -// DeploymentVolumeColumns returns kubernetes deployment volume fields as Osquery table columns. -func DeploymentVolumeColumns() []table.ColumnDefinition { - return k8s.GetSchema(&deploymentVolume{}) -} - -// DeploymentVolumesGenerate generates the kubernetes deployment volumes as Osquery table data. -func DeploymentVolumesGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - ds, err := k8s.GetClient().AppsV1().Deployments(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, d := range ds.Items { - for _, v := range d.Spec.Template.Spec.Volumes { - item := &deploymentVolume{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(d.ObjectMeta), - CommonVolumeFields: k8s.GetCommonVolumeFields(v), - DeploymentName: d.Name, - } - item.Name = v.Name - results = append(results, k8s.ToMap(item)) - } - } - - if ds.Continue == "" { - break - } - options.Continue = ds.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/apps/deployment_test.go b/infrastructure/kubequery/internal/k8s/apps/deployment_test.go deleted file mode 100644 index adaf4f3a23..0000000000 --- a/infrastructure/kubequery/internal/k8s/apps/deployment_test.go +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package apps - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestDeploymentsGenerate(t *testing.T) { - ds, err := DeploymentsGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "available_replicas": "0", - "cluster_uid": "blah", - "creation_timestamp": "0", - "host_ipc": "0", - "host_network": "0", - "host_pid": "0", - "min_ready_seconds": "0", - "observed_generation": "0", - "paused": "0", - "ready_replicas": "0", - "replicas": "0", - "strategy": "{}", - "unavailable_replicas": "0", - "updated_replicas": "0", - }, - }, ds) -} - -func TestDeploymentContainersGenerate(t *testing.T) { - ds, err := DeploymentContainersGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{}, ds) -} - -func TestDeploymentVolumesGenerate(t *testing.T) { - ds, err := DeploymentVolumesGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{}, ds) -} diff --git a/infrastructure/kubequery/internal/k8s/apps/init_test.go b/infrastructure/kubequery/internal/k8s/apps/init_test.go deleted file mode 100644 index 714afdb28c..0000000000 --- a/infrastructure/kubequery/internal/k8s/apps/init_test.go +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package apps - -import ( - "encoding/json" - "io/ioutil" - "path/filepath" - - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/apps/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/fake" -) - -func loadTestResource(name string, v interface{}) { - path := filepath.Join("testdata", name) - data, err := ioutil.ReadFile(path) - if err != nil { - panic(err) - } - - err = json.Unmarshal(data, v) - if err != nil { - panic(err) - } -} - -func init() { - ds := &v1.DaemonSet{} - loadTestResource("daemon_set_test.json", ds) - d := &v1.Deployment{} - loadTestResource("deployment_test.json", d) - rs := &v1.ReplicaSet{} - loadTestResource("replica_set_test.json", rs) - ss := &v1.StatefulSet{} - loadTestResource("stateful_set_test.json", ss) - - k8s.SetClient(fake.NewSimpleClientset(ds, d, rs, ss), types.UID("blah"), "") -} diff --git a/infrastructure/kubequery/internal/k8s/apps/replica_set.go b/infrastructure/kubequery/internal/k8s/apps/replica_set.go deleted file mode 100644 index bc54b83c95..0000000000 --- a/infrastructure/kubequery/internal/k8s/apps/replica_set.go +++ /dev/null @@ -1,173 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package apps - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/apps/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type replicaSet struct { - k8s.CommonNamespacedFields - k8s.CommonPodFields - v1.ReplicaSetStatus - ReplicaSetReplicas *int32 - MinReadySeconds int32 - Selector *metav1.LabelSelector -} - -// ReplicaSetColumns returns kubernetes replica set fields as Osquery table columns. -func ReplicaSetColumns() []table.ColumnDefinition { - return k8s.GetSchema(&replicaSet{}) -} - -// ReplicaSetsGenerate generates the kubernetes replica sets as Osquery table data. -func ReplicaSetsGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - rss, err := k8s.GetClient().AppsV1().ReplicaSets(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, rs := range rss.Items { - item := &replicaSet{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(rs.ObjectMeta), - CommonPodFields: k8s.GetCommonPodFields(rs.Spec.Template.Spec), - ReplicaSetStatus: rs.Status, - ReplicaSetReplicas: rs.Spec.Replicas, - MinReadySeconds: rs.Spec.MinReadySeconds, - Selector: rs.Spec.Selector, - } - results = append(results, k8s.ToMap(item)) - } - - if rss.Continue == "" { - break - } - options.Continue = rss.Continue - } - - return results, nil -} - -type replicaSetContainer struct { - k8s.CommonNamespacedFields - k8s.CommonContainerFields - ReplicaSetName string - ContainerType string -} - -// ReplicaSetContainerColumns returns kubernetes replica set container fields as Osquery table columns. -func ReplicaSetContainerColumns() []table.ColumnDefinition { - return k8s.GetSchema(&replicaSetContainer{}) -} - -// ReplicaSetContainersGenerate generates the kubernetes replica set containers as Osquery table data. -func ReplicaSetContainersGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - rss, err := k8s.GetClient().AppsV1().ReplicaSets(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, rs := range rss.Items { - for _, c := range rs.Spec.Template.Spec.InitContainers { - item := &replicaSetContainer{ - CommonNamespacedFields: k8s.GetParentCommonNamespacedFields(rs.ObjectMeta, c.Name), - CommonContainerFields: k8s.GetCommonContainerFields(c), - ReplicaSetName: rs.Name, - ContainerType: "init", - } - item.Name = c.Name - results = append(results, k8s.ToMap(item)) - } - for _, c := range rs.Spec.Template.Spec.Containers { - item := &replicaSetContainer{ - CommonNamespacedFields: k8s.GetParentCommonNamespacedFields(rs.ObjectMeta, c.Name), - CommonContainerFields: k8s.GetCommonContainerFields(c), - ReplicaSetName: rs.Name, - ContainerType: "container", - } - item.Name = c.Name - results = append(results, k8s.ToMap(item)) - } - for _, c := range rs.Spec.Template.Spec.EphemeralContainers { - item := &replicaSetContainer{ - CommonNamespacedFields: k8s.GetParentCommonNamespacedFields(rs.ObjectMeta, c.Name), - CommonContainerFields: k8s.GetCommonEphemeralContainerFields(c), - ReplicaSetName: rs.Name, - ContainerType: "ephemeral", - } - item.Name = c.Name - results = append(results, k8s.ToMap(item)) - } - } - - if rss.Continue == "" { - break - } - options.Continue = rss.Continue - } - - return results, nil -} - -type replicaSetVolume struct { - k8s.CommonNamespacedFields - k8s.CommonVolumeFields - ReplicaSetName string -} - -// ReplicaSetVolumeColumns returns kubernetes replica set volume fields as Osquery table columns. -func ReplicaSetVolumeColumns() []table.ColumnDefinition { - return k8s.GetSchema(&replicaSetVolume{}) -} - -// ReplicaSetVolumesGenerate generates the kubernetes replica set volumes as Osquery table data. -func ReplicaSetVolumesGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - rss, err := k8s.GetClient().AppsV1().ReplicaSets(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, rs := range rss.Items { - for _, v := range rs.Spec.Template.Spec.Volumes { - item := &replicaSetVolume{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(rs.ObjectMeta), - CommonVolumeFields: k8s.GetCommonVolumeFields(v), - ReplicaSetName: rs.Name, - } - item.Name = v.Name - results = append(results, k8s.ToMap(item)) - } - } - - if rss.Continue == "" { - break - } - options.Continue = rss.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/apps/replica_set_test.go b/infrastructure/kubequery/internal/k8s/apps/replica_set_test.go deleted file mode 100644 index 46028eddcf..0000000000 --- a/infrastructure/kubequery/internal/k8s/apps/replica_set_test.go +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package apps - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestReplicaSetsGenerate(t *testing.T) { - rss, err := ReplicaSetsGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "annotations": "{\"deployment.kubernetes.io/desired-replicas\":\"1\",\"deployment.kubernetes.io/max-replicas\":\"2\",\"deployment.kubernetes.io/revision\":\"1\"}", - "available_replicas": "1", - "cluster_uid": "blah", - "creation_timestamp": "1611191304", - "dns_policy": "ClusterFirst", - "fully_labeled_replicas": "1", - "host_ipc": "0", - "host_network": "0", - "host_pid": "0", - "labels": "{\"name\":\"jaeger-operator\",\"pod-template-hash\":\"5db4f9d996\"}", - "min_ready_seconds": "0", - "name": "jaeger-operator-5db4f9d996", - "namespace": "default", - "observed_generation": "1", - "ready_replicas": "1", - "replica_set_replicas": "1", - "replicas": "1", - "restart_policy": "Always", - "scheduler_name": "default-scheduler", - "selector": "{\"matchLabels\":{\"name\":\"jaeger-operator\",\"pod-template-hash\":\"5db4f9d996\"}}", - "service_account_name": "jaeger-operator", - "termination_grace_period_seconds": "30", - "uid": "2efeb411-ff99-434b-a5a2-4e06c2b0afaa", - }, - }, rss) -} - -func TestReplicaSetContainersGenerate(t *testing.T) { - rss, err := ReplicaSetContainersGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "annotations": "{\"deployment.kubernetes.io/desired-replicas\":\"1\",\"deployment.kubernetes.io/max-replicas\":\"2\",\"deployment.kubernetes.io/revision\":\"1\"}", - "args": "[\"start\"]", - "cluster_uid": "blah", - "container_type": "container", - "creation_timestamp": "1611191304", - "env": "[{\"name\":\"WATCH_NAMESPACE\"},{\"name\":\"POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"apiVersion\":\"v1\",\"fieldPath\":\"metadata.name\"}}},{\"name\":\"POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"apiVersion\":\"v1\",\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"OPERATOR_NAME\",\"value\":\"jaeger-operator\"}]", - "image": "jaegertracing/jaeger-operator:1.14.0", - "image_pull_policy": "Always", - "labels": "{\"name\":\"jaeger-operator\",\"pod-template-hash\":\"5db4f9d996\"}", - "name": "jaeger-operator", - "namespace": "default", - "ports": "[{\"name\":\"metrics\",\"containerPort\":8383,\"protocol\":\"TCP\"}]", - "replica_set_name": "jaeger-operator-5db4f9d996", - "stdin": "0", - "stdin_once": "0", - "termination_message_path": "/dev/termination-log", - "termination_message_policy": "File", - "tty": "0", - "uid": "a9c84883-3d97-5b99-8b20-9fcd5e626a02", - }, - }, rss) -} - -func TestReplicaSetVolumesGenerate(t *testing.T) { - rss, err := ReplicaSetVolumesGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{}, rss) -} diff --git a/infrastructure/kubequery/internal/k8s/apps/stateful_set.go b/infrastructure/kubequery/internal/k8s/apps/stateful_set.go deleted file mode 100644 index 2afa143c22..0000000000 --- a/infrastructure/kubequery/internal/k8s/apps/stateful_set.go +++ /dev/null @@ -1,182 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authoss - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package apps - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type statefulSet struct { - k8s.CommonNamespacedFields - k8s.CommonPodFields - v1.StatefulSetStatus - StatefulSetReplicas *int32 - Selector *metav1.LabelSelector - VolumeClaimTemplates []corev1.PersistentVolumeClaim - ServiceName string - PodManagementPolicy v1.PodManagementPolicyType - UpdateStrategy v1.StatefulSetUpdateStrategy - RevisionHistoryLimit *int32 -} - -// StatefulSetColumns returns kubernetes stateful set fields as Osquery table columns. -func StatefulSetColumns() []table.ColumnDefinition { - return k8s.GetSchema(&statefulSet{}) -} - -// StatefulSetsGenerate generates the kubernetes stateful sets as Osquery table data. -func StatefulSetsGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - sss, err := k8s.GetClient().AppsV1().StatefulSets(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, ss := range sss.Items { - item := &statefulSet{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(ss.ObjectMeta), - CommonPodFields: k8s.GetCommonPodFields(ss.Spec.Template.Spec), - StatefulSetStatus: ss.Status, - StatefulSetReplicas: ss.Spec.Replicas, - Selector: ss.Spec.Selector, - VolumeClaimTemplates: ss.Spec.VolumeClaimTemplates, - ServiceName: ss.Spec.ServiceName, - PodManagementPolicy: ss.Spec.PodManagementPolicy, - UpdateStrategy: ss.Spec.UpdateStrategy, - RevisionHistoryLimit: ss.Spec.RevisionHistoryLimit, - } - results = append(results, k8s.ToMap(item)) - } - - if sss.Continue == "" { - break - } - options.Continue = sss.Continue - } - - return results, nil -} - -type statefulSetContainer struct { - k8s.CommonNamespacedFields - k8s.CommonContainerFields - StatefulSetName string - ContainerType string -} - -// StatefulSetContainerColumns returns kubernetes stateful set container fields as Osquery table columns. -func StatefulSetContainerColumns() []table.ColumnDefinition { - return k8s.GetSchema(&statefulSetContainer{}) -} - -// StatefulSetContainersGenerate generates the kubernetes stateful set containers as Osquery table data. -func StatefulSetContainersGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - sss, err := k8s.GetClient().AppsV1().StatefulSets(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, ss := range sss.Items { - for _, c := range ss.Spec.Template.Spec.InitContainers { - item := &statefulSetContainer{ - CommonNamespacedFields: k8s.GetParentCommonNamespacedFields(ss.ObjectMeta, c.Name), - CommonContainerFields: k8s.GetCommonContainerFields(c), - StatefulSetName: ss.Name, - ContainerType: "init", - } - item.Name = c.Name - results = append(results, k8s.ToMap(item)) - } - for _, c := range ss.Spec.Template.Spec.Containers { - item := &statefulSetContainer{ - CommonNamespacedFields: k8s.GetParentCommonNamespacedFields(ss.ObjectMeta, c.Name), - CommonContainerFields: k8s.GetCommonContainerFields(c), - StatefulSetName: ss.Name, - ContainerType: "container", - } - item.Name = c.Name - results = append(results, k8s.ToMap(item)) - } - for _, c := range ss.Spec.Template.Spec.EphemeralContainers { - item := &statefulSetContainer{ - CommonNamespacedFields: k8s.GetParentCommonNamespacedFields(ss.ObjectMeta, c.Name), - CommonContainerFields: k8s.GetCommonEphemeralContainerFields(c), - StatefulSetName: ss.Name, - ContainerType: "ephemeral", - } - item.Name = c.Name - results = append(results, k8s.ToMap(item)) - } - } - - if sss.Continue == "" { - break - } - options.Continue = sss.Continue - } - - return results, nil -} - -type statefulSetVolume struct { - k8s.CommonNamespacedFields - k8s.CommonVolumeFields - StatefulSetName string -} - -// StatefulSetVolumeColumns returns kubernetes stateful set volume fields as Osquery table columns. -func StatefulSetVolumeColumns() []table.ColumnDefinition { - return k8s.GetSchema(&statefulSetVolume{}) -} - -// StatefulSetVolumesGenerate generates the kubernetes stateful set volumes as Osquery table data. -func StatefulSetVolumesGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - sss, err := k8s.GetClient().AppsV1().StatefulSets(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, ss := range sss.Items { - for _, v := range ss.Spec.Template.Spec.Volumes { - item := &statefulSetVolume{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(ss.ObjectMeta), - CommonVolumeFields: k8s.GetCommonVolumeFields(v), - StatefulSetName: ss.Name, - } - item.Name = v.Name - results = append(results, k8s.ToMap(item)) - } - } - - if sss.Continue == "" { - break - } - options.Continue = sss.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/apps/stateful_set_test.go b/infrastructure/kubequery/internal/k8s/apps/stateful_set_test.go deleted file mode 100644 index cb9495e45e..0000000000 --- a/infrastructure/kubequery/internal/k8s/apps/stateful_set_test.go +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package apps - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestStatefulSetsGenerate(t *testing.T) { - sss, err := StatefulSetsGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "available_replicas": "0", - "cluster_uid": "blah", - "collision_count": "0", - "creation_timestamp": "1611191592", - "current_replicas": "1", - "current_revision": "alertmanager-main-6674894c9d", - "dns_policy": "ClusterFirst", - "fs_group": "2000", - "host_ipc": "0", - "host_network": "0", - "host_pid": "0", - "labels": "{\"alertmanager\":\"main\"}", - "name": "alertmanager-main", - "namespace": "monitoring", - "node_selector": "{\"kubernetes.io/os\":\"linux\"}", - "observed_generation": "1", - "pod_management_policy": "Parallel", - "ready_replicas": "1", - "replicas": "1", - "restart_policy": "Always", - "revision_history_limit": "10", - "run_as_non_root": "1", - "run_as_user": "1000", - "scheduler_name": "default-scheduler", - "selector": "{\"matchLabels\":{\"alertmanager\":\"main\",\"app\":\"alertmanager\"}}", - "service_account_name": "alertmanager-main", - "service_name": "alertmanager-operated", - "stateful_set_replicas": "1", - "termination_grace_period_seconds": "120", - "uid": "3c488e7e-420c-4515-b377-5dc3ee082744", - "update_revision": "alertmanager-main-6674894c9d", - "update_strategy": "{\"type\":\"RollingUpdate\"}", - "updated_replicas": "1", - }, - }, sss) -} - -func TestStatefulSetContainersGenerate(t *testing.T) { - sss, err := StatefulSetContainersGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "args": "[\"--config.file=/etc/alertmanager/config/alertmanager.yaml\",\"--storage.path=/alertmanager\",\"--data.retention=120h\",\"--cluster.listen-address=\",\"--web.listen-address=:9093\",\"--web.route-prefix=/\",\"--cluster.peer=alertmanager-main-0.alertmanager-operated:9094\"]", - "cluster_uid": "blah", - "container_type": "container", - "creation_timestamp": "1611191592", - "env": "[{\"name\":\"POD_IP\",\"valueFrom\":{\"fieldRef\":{\"apiVersion\":\"v1\",\"fieldPath\":\"status.podIP\"}}}]", - "image": "quay.io/prometheus/alertmanager:v0.21.0", - "image_pull_policy": "IfNotPresent", - "labels": "{\"alertmanager\":\"main\"}", - "liveness_probe": "{\"httpGet\":{\"path\":\"/-/healthy\",\"port\":\"web\",\"scheme\":\"HTTP\"},\"timeoutSeconds\":3,\"periodSeconds\":10,\"successThreshold\":1,\"failureThreshold\":10}", - "name": "alertmanager", - "namespace": "monitoring", - "ports": "[{\"name\":\"web\",\"containerPort\":9093,\"protocol\":\"TCP\"},{\"name\":\"mesh-tcp\",\"containerPort\":9094,\"protocol\":\"TCP\"},{\"name\":\"mesh-udp\",\"containerPort\":9094,\"protocol\":\"UDP\"}]", - "readiness_probe": "{\"httpGet\":{\"path\":\"/-/ready\",\"port\":\"web\",\"scheme\":\"HTTP\"},\"initialDelaySeconds\":3,\"timeoutSeconds\":3,\"periodSeconds\":5,\"successThreshold\":1,\"failureThreshold\":10}", - "resource_requests": "{\"memory\":\"200Mi\"}", - "stateful_set_name": "alertmanager-main", - "stdin": "0", - "stdin_once": "0", - "termination_message_path": "/dev/termination-log", - "termination_message_policy": "FallbackToLogsOnError", - "tty": "0", - "uid": "da9bb224-1bf1-5960-a83c-b77a73ea6e79", - "volume_mounts": "[{\"name\":\"config-volume\",\"mountPath\":\"/etc/alertmanager/config\"},{\"name\":\"alertmanager-main-db\",\"mountPath\":\"/alertmanager\"}]", - }, - { - "args": "[\"-webhook-url=http://localhost:9093/-/reload\",\"-volume-dir=/etc/alertmanager/config\"]", - "cluster_uid": "blah", - "container_type": "container", - "creation_timestamp": "1611191592", - "image": "jimmidyson/configmap-reload:v0.3.0", - "image_pull_policy": "IfNotPresent", - "labels": "{\"alertmanager\":\"main\"}", - "name": "config-reloader", - "namespace": "monitoring", - "resource_limits": "{\"cpu\":\"100m\",\"memory\":\"25Mi\"}", - "stateful_set_name": "alertmanager-main", - "stdin": "0", - "stdin_once": "0", - "termination_message_path": "/dev/termination-log", - "termination_message_policy": "FallbackToLogsOnError", - "tty": "0", - "uid": "69afdc5a-a3de-59b5-8151-4103b933f2cf", - "volume_mounts": "[{\"name\":\"config-volume\",\"readOnly\":true,\"mountPath\":\"/etc/alertmanager/config\"}]", - }, - }, sss) -} - -func TestStatefulSetVolumesGenerate(t *testing.T) { - sss, err := StatefulSetVolumesGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "aws_elastic_block_store_partition": "0", - "cluster_uid": "blah", - "creation_timestamp": "1611191592", - "gce_persistent_disk_partition": "0", - "iscsi_discovery_chap_auth": "0", - "iscsi_lun": "0", - "iscsi_session_chap_auth": "0", - "labels": "{\"alertmanager\":\"main\"}", - "name": "config-volume", - "namespace": "monitoring", - "scale_iossl_enabled": "0", - "secret_default_mode": "420", - "secret_name": "alertmanager-main", - "stateful_set_name": "alertmanager-main", - "uid": "3c488e7e-420c-4515-b377-5dc3ee082744", - "volume_type": "secret", - }, - { - "aws_elastic_block_store_partition": "0", - "cluster_uid": "blah", - "creation_timestamp": "1611191592", - "empty_dir_size_limit": "", - "gce_persistent_disk_partition": "0", - "iscsi_discovery_chap_auth": "0", - "iscsi_lun": "0", - "iscsi_session_chap_auth": "0", - "labels": "{\"alertmanager\":\"main\"}", - "name": "alertmanager-main-db", - "namespace": "monitoring", - "scale_iossl_enabled": "0", - "stateful_set_name": "alertmanager-main", - "uid": "3c488e7e-420c-4515-b377-5dc3ee082744", - "volume_type": "empty_dir", - }, - }, sss) -} diff --git a/infrastructure/kubequery/internal/k8s/apps/testdata/daemon_set_test.json b/infrastructure/kubequery/internal/k8s/apps/testdata/daemon_set_test.json deleted file mode 100644 index 33da4e3874..0000000000 --- a/infrastructure/kubequery/internal/k8s/apps/testdata/daemon_set_test.json +++ /dev/null @@ -1,910 +0,0 @@ -{ - "apiVersion": "apps/v1", - "kind": "DaemonSet", - "metadata": { - "annotations": { - "deprecated.daemonset.template.generation": "1" - }, - "creationTimestamp": "2021-01-12T18:30:16Z", - "generation": 1, - "labels": { - "k8s-app": "calico-node" - }, - "managedFields": [ - { - "apiVersion": "apps/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:deprecated.daemonset.template.generation": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:k8s-app": {} - } - }, - "f:spec": { - "f:revisionHistoryLimit": {}, - "f:selector": {}, - "f:template": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:scheduler.alpha.kubernetes.io/critical-pod": {} - }, - "f:labels": { - ".": {}, - "f:k8s-app": {} - } - }, - "f:spec": { - "f:containers": { - "k:{\"name\":\"calico-node\"}": { - ".": {}, - "f:env": { - ".": {}, - "k:{\"name\":\"CALICO_DISABLE_FILE_LOGGING\"}": { - ".": {}, - "f:name": {}, - "f:value": {} - }, - "k:{\"name\":\"CALICO_IPV4POOL_CIDR\"}": { - ".": {}, - "f:name": {}, - "f:value": {} - }, - "k:{\"name\":\"CALICO_IPV4POOL_VXLAN\"}": { - ".": {}, - "f:name": {}, - "f:value": {} - }, - "k:{\"name\":\"CALICO_NETWORKING_BACKEND\"}": { - ".": {}, - "f:name": {}, - "f:valueFrom": { - ".": {}, - "f:configMapKeyRef": { - ".": {}, - "f:key": {}, - "f:name": {} - } - } - }, - "k:{\"name\":\"CLUSTER_TYPE\"}": { - ".": {}, - "f:name": {}, - "f:value": {} - }, - "k:{\"name\":\"DATASTORE_TYPE\"}": { - ".": {}, - "f:name": {}, - "f:value": {} - }, - "k:{\"name\":\"FELIX_DEFAULTENDPOINTTOHOSTACTION\"}": { - ".": {}, - "f:name": {}, - "f:value": {} - }, - "k:{\"name\":\"FELIX_HEALTHENABLED\"}": { - ".": {}, - "f:name": {}, - "f:value": {} - }, - "k:{\"name\":\"FELIX_IPINIPMTU\"}": { - ".": {}, - "f:name": {}, - "f:valueFrom": { - ".": {}, - "f:configMapKeyRef": { - ".": {}, - "f:key": {}, - "f:name": {} - } - } - }, - "k:{\"name\":\"FELIX_IPV6SUPPORT\"}": { - ".": {}, - "f:name": {}, - "f:value": {} - }, - "k:{\"name\":\"FELIX_LOGSEVERITYSCREEN\"}": { - ".": {}, - "f:name": {}, - "f:value": {} - }, - "k:{\"name\":\"IP\"}": { - ".": {}, - "f:name": {}, - "f:value": {} - }, - "k:{\"name\":\"IP_AUTODETECTION_METHOD\"}": { - ".": {}, - "f:name": {}, - "f:value": {} - }, - "k:{\"name\":\"NODENAME\"}": { - ".": {}, - "f:name": {}, - "f:valueFrom": { - ".": {}, - "f:fieldRef": { - ".": {}, - "f:apiVersion": {}, - "f:fieldPath": {} - } - } - }, - "k:{\"name\":\"WAIT_FOR_DATASTORE\"}": { - ".": {}, - "f:name": {}, - "f:value": {} - } - }, - "f:image": {}, - "f:imagePullPolicy": {}, - "f:livenessProbe": { - ".": {}, - "f:exec": { - ".": {}, - "f:command": {} - }, - "f:failureThreshold": {}, - "f:initialDelaySeconds": {}, - "f:periodSeconds": {}, - "f:successThreshold": {}, - "f:timeoutSeconds": {} - }, - "f:name": {}, - "f:readinessProbe": { - ".": {}, - "f:exec": { - ".": {}, - "f:command": {} - }, - "f:failureThreshold": {}, - "f:periodSeconds": {}, - "f:successThreshold": {}, - "f:timeoutSeconds": {} - }, - "f:resources": { - ".": {}, - "f:requests": { - ".": {}, - "f:cpu": {} - } - }, - "f:securityContext": { - ".": {}, - "f:privileged": {} - }, - "f:terminationMessagePath": {}, - "f:terminationMessagePolicy": {}, - "f:volumeMounts": { - ".": {}, - "k:{\"mountPath\":\"/lib/modules\"}": { - ".": {}, - "f:mountPath": {}, - "f:name": {}, - "f:readOnly": {} - }, - "k:{\"mountPath\":\"/run/xtables.lock\"}": { - ".": {}, - "f:mountPath": {}, - "f:name": {} - }, - "k:{\"mountPath\":\"/var/lib/calico\"}": { - ".": {}, - "f:mountPath": {}, - "f:name": {} - }, - "k:{\"mountPath\":\"/var/run/calico\"}": { - ".": {}, - "f:mountPath": {}, - "f:name": {} - }, - "k:{\"mountPath\":\"/var/run/nodeagent\"}": { - ".": {}, - "f:mountPath": {}, - "f:name": {} - } - } - } - }, - "f:dnsPolicy": {}, - "f:hostNetwork": {}, - "f:initContainers": { - ".": {}, - "k:{\"name\":\"flexvol-driver\"}": { - ".": {}, - "f:image": {}, - "f:imagePullPolicy": {}, - "f:name": {}, - "f:resources": {}, - "f:securityContext": { - ".": {}, - "f:privileged": {} - }, - "f:terminationMessagePath": {}, - "f:terminationMessagePolicy": {}, - "f:volumeMounts": { - ".": {}, - "k:{\"mountPath\":\"/host/driver\"}": { - ".": {}, - "f:mountPath": {}, - "f:name": {} - } - } - }, - "k:{\"name\":\"install-cni\"}": { - ".": {}, - "f:command": {}, - "f:env": { - ".": {}, - "k:{\"name\":\"CNI_CONF_NAME\"}": { - ".": {}, - "f:name": {}, - "f:value": {} - }, - "k:{\"name\":\"CNI_MTU\"}": { - ".": {}, - "f:name": {}, - "f:valueFrom": { - ".": {}, - "f:configMapKeyRef": { - ".": {}, - "f:key": {}, - "f:name": {} - } - } - }, - "k:{\"name\":\"CNI_NETWORK_CONFIG\"}": { - ".": {}, - "f:name": {}, - "f:valueFrom": { - ".": {}, - "f:configMapKeyRef": { - ".": {}, - "f:key": {}, - "f:name": {} - } - } - }, - "k:{\"name\":\"CNI_NET_DIR\"}": { - ".": {}, - "f:name": {}, - "f:value": {} - }, - "k:{\"name\":\"KUBERNETES_NODE_NAME\"}": { - ".": {}, - "f:name": {}, - "f:valueFrom": { - ".": {}, - "f:fieldRef": { - ".": {}, - "f:apiVersion": {}, - "f:fieldPath": {} - } - } - }, - "k:{\"name\":\"SLEEP\"}": { - ".": {}, - "f:name": {}, - "f:value": {} - } - }, - "f:image": {}, - "f:imagePullPolicy": {}, - "f:name": {}, - "f:resources": {}, - "f:securityContext": { - ".": {}, - "f:privileged": {} - }, - "f:terminationMessagePath": {}, - "f:terminationMessagePolicy": {}, - "f:volumeMounts": { - ".": {}, - "k:{\"mountPath\":\"/host/etc/cni/net.d\"}": { - ".": {}, - "f:mountPath": {}, - "f:name": {} - }, - "k:{\"mountPath\":\"/host/opt/cni/bin\"}": { - ".": {}, - "f:mountPath": {}, - "f:name": {} - } - } - }, - "k:{\"name\":\"upgrade-ipam\"}": { - ".": {}, - "f:command": {}, - "f:env": { - ".": {}, - "k:{\"name\":\"CALICO_NETWORKING_BACKEND\"}": { - ".": {}, - "f:name": {}, - "f:valueFrom": { - ".": {}, - "f:configMapKeyRef": { - ".": {}, - "f:key": {}, - "f:name": {} - } - } - }, - "k:{\"name\":\"KUBERNETES_NODE_NAME\"}": { - ".": {}, - "f:name": {}, - "f:valueFrom": { - ".": {}, - "f:fieldRef": { - ".": {}, - "f:apiVersion": {}, - "f:fieldPath": {} - } - } - } - }, - "f:image": {}, - "f:imagePullPolicy": {}, - "f:name": {}, - "f:resources": {}, - "f:securityContext": { - ".": {}, - "f:privileged": {} - }, - "f:terminationMessagePath": {}, - "f:terminationMessagePolicy": {}, - "f:volumeMounts": { - ".": {}, - "k:{\"mountPath\":\"/host/opt/cni/bin\"}": { - ".": {}, - "f:mountPath": {}, - "f:name": {} - }, - "k:{\"mountPath\":\"/var/lib/cni/networks\"}": { - ".": {}, - "f:mountPath": {}, - "f:name": {} - } - } - } - }, - "f:nodeSelector": { - ".": {}, - "f:kubernetes.io/os": {} - }, - "f:priorityClassName": {}, - "f:restartPolicy": {}, - "f:schedulerName": {}, - "f:securityContext": {}, - "f:serviceAccount": {}, - "f:serviceAccountName": {}, - "f:terminationGracePeriodSeconds": {}, - "f:tolerations": {}, - "f:volumes": { - ".": {}, - "k:{\"name\":\"cni-bin-dir\"}": { - ".": {}, - "f:hostPath": { - ".": {}, - "f:path": {}, - "f:type": {} - }, - "f:name": {} - }, - "k:{\"name\":\"cni-net-dir\"}": { - ".": {}, - "f:hostPath": { - ".": {}, - "f:path": {}, - "f:type": {} - }, - "f:name": {} - }, - "k:{\"name\":\"flexvol-driver-host\"}": { - ".": {}, - "f:hostPath": { - ".": {}, - "f:path": {}, - "f:type": {} - }, - "f:name": {} - }, - "k:{\"name\":\"host-local-net-dir\"}": { - ".": {}, - "f:hostPath": { - ".": {}, - "f:path": {}, - "f:type": {} - }, - "f:name": {} - }, - "k:{\"name\":\"lib-modules\"}": { - ".": {}, - "f:hostPath": { - ".": {}, - "f:path": {}, - "f:type": {} - }, - "f:name": {} - }, - "k:{\"name\":\"policysync\"}": { - ".": {}, - "f:hostPath": { - ".": {}, - "f:path": {}, - "f:type": {} - }, - "f:name": {} - }, - "k:{\"name\":\"var-lib-calico\"}": { - ".": {}, - "f:hostPath": { - ".": {}, - "f:path": {}, - "f:type": {} - }, - "f:name": {} - }, - "k:{\"name\":\"var-run-calico\"}": { - ".": {}, - "f:hostPath": { - ".": {}, - "f:path": {}, - "f:type": {} - }, - "f:name": {} - }, - "k:{\"name\":\"xtables-lock\"}": { - ".": {}, - "f:hostPath": { - ".": {}, - "f:path": {}, - "f:type": {} - }, - "f:name": {} - } - } - } - }, - "f:updateStrategy": { - "f:rollingUpdate": { - ".": {}, - "f:maxUnavailable": {} - }, - "f:type": {} - } - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-12T18:30:16Z" - }, - { - "apiVersion": "apps/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:status": { - "f:currentNumberScheduled": {}, - "f:desiredNumberScheduled": {}, - "f:numberAvailable": {}, - "f:numberReady": {}, - "f:observedGeneration": {}, - "f:updatedNumberScheduled": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-21T01:05:37Z" - } - ], - "name": "calico-node", - "namespace": "kube-system", - "resourceVersion": "450274", - "selfLink": "/apis/apps/v1/namespaces/kube-system/daemonsets/calico-node", - "uid": "e6fed7f0-f79a-464f-a3d2-b63247a1f590" - }, - "spec": { - "revisionHistoryLimit": 10, - "selector": { - "matchLabels": { - "k8s-app": "calico-node" - } - }, - "template": { - "metadata": { - "annotations": { - "scheduler.alpha.kubernetes.io/critical-pod": "" - }, - "creationTimestamp": null, - "labels": { - "k8s-app": "calico-node" - } - }, - "spec": { - "containers": [ - { - "env": [ - { - "name": "DATASTORE_TYPE", - "value": "kubernetes" - }, - { - "name": "WAIT_FOR_DATASTORE", - "value": "true" - }, - { - "name": "NODENAME", - "valueFrom": { - "fieldRef": { - "apiVersion": "v1", - "fieldPath": "spec.nodeName" - } - } - }, - { - "name": "CALICO_NETWORKING_BACKEND", - "valueFrom": { - "configMapKeyRef": { - "key": "calico_backend", - "name": "calico-config" - } - } - }, - { - "name": "CLUSTER_TYPE", - "value": "k8s,bgp" - }, - { - "name": "IP", - "value": "autodetect" - }, - { - "name": "IP_AUTODETECTION_METHOD", - "value": "first-found" - }, - { - "name": "CALICO_IPV4POOL_VXLAN", - "value": "Always" - }, - { - "name": "FELIX_IPINIPMTU", - "valueFrom": { - "configMapKeyRef": { - "key": "veth_mtu", - "name": "calico-config" - } - } - }, - { - "name": "CALICO_IPV4POOL_CIDR", - "value": "10.1.0.0/16" - }, - { - "name": "CALICO_DISABLE_FILE_LOGGING", - "value": "true" - }, - { - "name": "FELIX_DEFAULTENDPOINTTOHOSTACTION", - "value": "ACCEPT" - }, - { - "name": "FELIX_IPV6SUPPORT", - "value": "false" - }, - { - "name": "FELIX_LOGSEVERITYSCREEN", - "value": "error" - }, - { - "name": "FELIX_HEALTHENABLED", - "value": "true" - } - ], - "image": "calico/node:v3.13.2", - "imagePullPolicy": "IfNotPresent", - "livenessProbe": { - "exec": { - "command": [ - "/bin/calico-node", - "-felix-live" - ] - }, - "failureThreshold": 6, - "initialDelaySeconds": 10, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "name": "calico-node", - "readinessProbe": { - "exec": { - "command": [ - "/bin/calico-node", - "-felix-ready" - ] - }, - "failureThreshold": 3, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "resources": { - "requests": { - "cpu": "250m" - } - }, - "securityContext": { - "privileged": true - }, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/lib/modules", - "name": "lib-modules", - "readOnly": true - }, - { - "mountPath": "/run/xtables.lock", - "name": "xtables-lock" - }, - { - "mountPath": "/var/run/calico", - "name": "var-run-calico" - }, - { - "mountPath": "/var/lib/calico", - "name": "var-lib-calico" - }, - { - "mountPath": "/var/run/nodeagent", - "name": "policysync" - } - ] - } - ], - "dnsPolicy": "ClusterFirst", - "hostNetwork": true, - "initContainers": [ - { - "command": [ - "/opt/cni/bin/calico-ipam", - "-upgrade" - ], - "env": [ - { - "name": "KUBERNETES_NODE_NAME", - "valueFrom": { - "fieldRef": { - "apiVersion": "v1", - "fieldPath": "spec.nodeName" - } - } - }, - { - "name": "CALICO_NETWORKING_BACKEND", - "valueFrom": { - "configMapKeyRef": { - "key": "calico_backend", - "name": "calico-config" - } - } - } - ], - "image": "calico/cni:v3.13.2", - "imagePullPolicy": "IfNotPresent", - "name": "upgrade-ipam", - "resources": {}, - "securityContext": { - "privileged": true - }, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/var/lib/cni/networks", - "name": "host-local-net-dir" - }, - { - "mountPath": "/host/opt/cni/bin", - "name": "cni-bin-dir" - } - ] - }, - { - "command": [ - "/install-cni.sh" - ], - "env": [ - { - "name": "CNI_CONF_NAME", - "value": "10-calico.conflist" - }, - { - "name": "CNI_NETWORK_CONFIG", - "valueFrom": { - "configMapKeyRef": { - "key": "cni_network_config", - "name": "calico-config" - } - } - }, - { - "name": "KUBERNETES_NODE_NAME", - "valueFrom": { - "fieldRef": { - "apiVersion": "v1", - "fieldPath": "spec.nodeName" - } - } - }, - { - "name": "CNI_MTU", - "valueFrom": { - "configMapKeyRef": { - "key": "veth_mtu", - "name": "calico-config" - } - } - }, - { - "name": "SLEEP", - "value": "false" - }, - { - "name": "CNI_NET_DIR", - "value": "/var/snap/microk8s/current/args/cni-network" - } - ], - "image": "calico/cni:v3.13.2", - "imagePullPolicy": "IfNotPresent", - "name": "install-cni", - "resources": {}, - "securityContext": { - "privileged": true - }, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/host/opt/cni/bin", - "name": "cni-bin-dir" - }, - { - "mountPath": "/host/etc/cni/net.d", - "name": "cni-net-dir" - } - ] - }, - { - "image": "calico/pod2daemon-flexvol:v3.13.2", - "imagePullPolicy": "IfNotPresent", - "name": "flexvol-driver", - "resources": {}, - "securityContext": { - "privileged": true - }, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/host/driver", - "name": "flexvol-driver-host" - } - ] - } - ], - "nodeSelector": { - "kubernetes.io/os": "linux" - }, - "priorityClassName": "system-node-critical", - "restartPolicy": "Always", - "schedulerName": "default-scheduler", - "securityContext": {}, - "serviceAccount": "calico-node", - "serviceAccountName": "calico-node", - "terminationGracePeriodSeconds": 0, - "tolerations": [ - { - "effect": "NoSchedule", - "operator": "Exists" - }, - { - "key": "CriticalAddonsOnly", - "operator": "Exists" - }, - { - "effect": "NoExecute", - "operator": "Exists" - } - ], - "volumes": [ - { - "hostPath": { - "path": "/lib/modules", - "type": "" - }, - "name": "lib-modules" - }, - { - "hostPath": { - "path": "/var/snap/microk8s/current/var/run/calico", - "type": "" - }, - "name": "var-run-calico" - }, - { - "hostPath": { - "path": "/var/snap/microk8s/current/var/lib/calico", - "type": "" - }, - "name": "var-lib-calico" - }, - { - "hostPath": { - "path": "/run/xtables.lock", - "type": "FileOrCreate" - }, - "name": "xtables-lock" - }, - { - "hostPath": { - "path": "/var/snap/microk8s/current/opt/cni/bin", - "type": "" - }, - "name": "cni-bin-dir" - }, - { - "hostPath": { - "path": "/var/snap/microk8s/current/args/cni-network", - "type": "" - }, - "name": "cni-net-dir" - }, - { - "hostPath": { - "path": "/var/snap/microk8s/current/var/lib/cni/networks", - "type": "" - }, - "name": "host-local-net-dir" - }, - { - "hostPath": { - "path": "/var/snap/microk8s/current/var/run/nodeagent", - "type": "DirectoryOrCreate" - }, - "name": "policysync" - }, - { - "hostPath": { - "path": "/usr/libexec/kubernetes/kubelet-plugins/volume/exec/nodeagent~uds", - "type": "DirectoryOrCreate" - }, - "name": "flexvol-driver-host" - } - ] - } - }, - "updateStrategy": { - "rollingUpdate": { - "maxUnavailable": 1 - }, - "type": "RollingUpdate" - } - }, - "status": { - "currentNumberScheduled": 1, - "desiredNumberScheduled": 1, - "numberAvailable": 1, - "numberMisscheduled": 0, - "numberReady": 1, - "observedGeneration": 1, - "updatedNumberScheduled": 1 - } -} diff --git a/infrastructure/kubequery/internal/k8s/apps/testdata/deployment_test.json b/infrastructure/kubequery/internal/k8s/apps/testdata/deployment_test.json deleted file mode 100644 index 7a0d219048..0000000000 --- a/infrastructure/kubequery/internal/k8s/apps/testdata/deployment_test.json +++ /dev/null @@ -1,739 +0,0 @@ -{ - "apiVersion": "v1", - "items": [ - { - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "annotations": { - "deployment.kubernetes.io/revision": "1", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"annotations\":{},\"name\":\"jaeger-operator\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"name\":\"jaeger-operator\"}},\"template\":{\"metadata\":{\"labels\":{\"name\":\"jaeger-operator\"}},\"spec\":{\"containers\":[{\"args\":[\"start\"],\"env\":[{\"name\":\"WATCH_NAMESPACE\",\"value\":\"\"},{\"name\":\"POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}},{\"name\":\"POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"OPERATOR_NAME\",\"value\":\"jaeger-operator\"}],\"image\":\"jaegertracing/jaeger-operator:1.14.0\",\"imagePullPolicy\":\"Always\",\"name\":\"jaeger-operator\",\"ports\":[{\"containerPort\":8383,\"name\":\"metrics\"}]}],\"serviceAccountName\":\"jaeger-operator\"}}}}\n" - }, - "creationTimestamp": "2021-01-21T01:08:24Z", - "generation": 1, - "managedFields": [ - { - "apiVersion": "apps/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - } - }, - "f:spec": { - "f:progressDeadlineSeconds": {}, - "f:replicas": {}, - "f:revisionHistoryLimit": {}, - "f:selector": {}, - "f:strategy": { - "f:rollingUpdate": { - ".": {}, - "f:maxSurge": {}, - "f:maxUnavailable": {} - }, - "f:type": {} - }, - "f:template": { - "f:metadata": { - "f:labels": { - ".": {}, - "f:name": {} - } - }, - "f:spec": { - "f:containers": { - "k:{\"name\":\"jaeger-operator\"}": { - ".": {}, - "f:args": {}, - "f:env": { - ".": {}, - "k:{\"name\":\"OPERATOR_NAME\"}": { - ".": {}, - "f:name": {}, - "f:value": {} - }, - "k:{\"name\":\"POD_NAME\"}": { - ".": {}, - "f:name": {}, - "f:valueFrom": { - ".": {}, - "f:fieldRef": { - ".": {}, - "f:apiVersion": {}, - "f:fieldPath": {} - } - } - }, - "k:{\"name\":\"POD_NAMESPACE\"}": { - ".": {}, - "f:name": {}, - "f:valueFrom": { - ".": {}, - "f:fieldRef": { - ".": {}, - "f:apiVersion": {}, - "f:fieldPath": {} - } - } - }, - "k:{\"name\":\"WATCH_NAMESPACE\"}": { - ".": {}, - "f:name": {} - } - }, - "f:image": {}, - "f:imagePullPolicy": {}, - "f:name": {}, - "f:ports": { - ".": {}, - "k:{\"containerPort\":8383,\"protocol\":\"TCP\"}": { - ".": {}, - "f:containerPort": {}, - "f:name": {}, - "f:protocol": {} - } - }, - "f:resources": {}, - "f:terminationMessagePath": {}, - "f:terminationMessagePolicy": {} - } - }, - "f:dnsPolicy": {}, - "f:restartPolicy": {}, - "f:schedulerName": {}, - "f:securityContext": {}, - "f:serviceAccount": {}, - "f:serviceAccountName": {}, - "f:terminationGracePeriodSeconds": {} - } - } - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-21T01:08:24Z" - }, - { - "apiVersion": "apps/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - "f:deployment.kubernetes.io/revision": {} - } - }, - "f:status": { - "f:availableReplicas": {}, - "f:conditions": { - ".": {}, - "k:{\"type\":\"Available\"}": { - ".": {}, - "f:lastTransitionTime": {}, - "f:lastUpdateTime": {}, - "f:message": {}, - "f:reason": {}, - "f:status": {}, - "f:type": {} - }, - "k:{\"type\":\"Progressing\"}": { - ".": {}, - "f:lastTransitionTime": {}, - "f:lastUpdateTime": {}, - "f:message": {}, - "f:reason": {}, - "f:status": {}, - "f:type": {} - } - }, - "f:observedGeneration": {}, - "f:readyReplicas": {}, - "f:replicas": {}, - "f:updatedReplicas": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-21T01:08:52Z" - } - ], - "name": "jaeger-operator", - "namespace": "default", - "resourceVersion": "451812", - "selfLink": "/apis/apps/v1/namespaces/default/deployments/jaeger-operator", - "uid": "baa856ea-2d04-4bbb-b9be-aa5b89c58087" - }, - "spec": { - "progressDeadlineSeconds": 600, - "replicas": 1, - "revisionHistoryLimit": 10, - "selector": { - "matchLabels": { - "name": "jaeger-operator" - } - }, - "strategy": { - "rollingUpdate": { - "maxSurge": "25%", - "maxUnavailable": "25%" - }, - "type": "RollingUpdate" - }, - "template": { - "metadata": { - "creationTimestamp": null, - "labels": { - "name": "jaeger-operator" - } - }, - "spec": { - "containers": [ - { - "args": [ - "start" - ], - "env": [ - { - "name": "WATCH_NAMESPACE" - }, - { - "name": "POD_NAME", - "valueFrom": { - "fieldRef": { - "apiVersion": "v1", - "fieldPath": "metadata.name" - } - } - }, - { - "name": "POD_NAMESPACE", - "valueFrom": { - "fieldRef": { - "apiVersion": "v1", - "fieldPath": "metadata.namespace" - } - } - }, - { - "name": "OPERATOR_NAME", - "value": "jaeger-operator" - } - ], - "image": "jaegertracing/jaeger-operator:1.14.0", - "imagePullPolicy": "Always", - "name": "jaeger-operator", - "ports": [ - { - "containerPort": 8383, - "name": "metrics", - "protocol": "TCP" - } - ], - "resources": {}, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File" - } - ], - "dnsPolicy": "ClusterFirst", - "restartPolicy": "Always", - "schedulerName": "default-scheduler", - "securityContext": {}, - "serviceAccount": "jaeger-operator", - "serviceAccountName": "jaeger-operator", - "terminationGracePeriodSeconds": 30 - } - } - }, - "status": { - "availableReplicas": 1, - "conditions": [ - { - "lastTransitionTime": "2021-01-21T01:08:52Z", - "lastUpdateTime": "2021-01-21T01:08:52Z", - "message": "Deployment has minimum availability.", - "reason": "MinimumReplicasAvailable", - "status": "True", - "type": "Available" - }, - { - "lastTransitionTime": "2021-01-21T01:08:24Z", - "lastUpdateTime": "2021-01-21T01:08:52Z", - "message": "ReplicaSet \"jaeger-operator-5db4f9d996\" has successfully progressed.", - "reason": "NewReplicaSetAvailable", - "status": "True", - "type": "Progressing" - } - ], - "observedGeneration": 1, - "readyReplicas": 1, - "replicas": 1, - "updatedReplicas": 1 - } - }, - { - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "annotations": { - "deployment.kubernetes.io/revision": "1", - "linkerd.io/inject": "disabled", - "prometheus.io/port": "14269", - "prometheus.io/scrape": "true", - "sidecar.istio.io/inject": "false" - }, - "creationTimestamp": "2021-01-21T01:08:53Z", - "generation": 1, - "labels": { - "app": "jaeger", - "app.kubernetes.io/component": "all-in-one", - "app.kubernetes.io/instance": "simplest", - "app.kubernetes.io/managed-by": "jaeger-operator", - "app.kubernetes.io/name": "simplest", - "app.kubernetes.io/part-of": "jaeger" - }, - "managedFields": [ - { - "apiVersion": "apps/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:linkerd.io/inject": {}, - "f:prometheus.io/port": {}, - "f:prometheus.io/scrape": {}, - "f:sidecar.istio.io/inject": {} - }, - "f:labels": { - ".": {}, - "f:app": {}, - "f:app.kubernetes.io/component": {}, - "f:app.kubernetes.io/instance": {}, - "f:app.kubernetes.io/managed-by": {}, - "f:app.kubernetes.io/name": {}, - "f:app.kubernetes.io/part-of": {} - }, - "f:ownerReferences": { - ".": {}, - "k:{\"uid\":\"95d303e8-d347-4f8f-b008-a4da3e44b847\"}": { - ".": {}, - "f:apiVersion": {}, - "f:controller": {}, - "f:kind": {}, - "f:name": {}, - "f:uid": {} - } - } - }, - "f:spec": { - "f:progressDeadlineSeconds": {}, - "f:replicas": {}, - "f:revisionHistoryLimit": {}, - "f:selector": {}, - "f:strategy": { - "f:rollingUpdate": { - ".": {}, - "f:maxSurge": {}, - "f:maxUnavailable": {} - }, - "f:type": {} - }, - "f:template": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:linkerd.io/inject": {}, - "f:prometheus.io/port": {}, - "f:prometheus.io/scrape": {}, - "f:sidecar.istio.io/inject": {} - }, - "f:labels": { - ".": {}, - "f:app": {}, - "f:app.kubernetes.io/component": {}, - "f:app.kubernetes.io/instance": {}, - "f:app.kubernetes.io/managed-by": {}, - "f:app.kubernetes.io/name": {}, - "f:app.kubernetes.io/part-of": {} - } - }, - "f:spec": { - "f:containers": { - "k:{\"name\":\"jaeger\"}": { - ".": {}, - "f:args": {}, - "f:env": { - ".": {}, - "k:{\"name\":\"COLLECTOR_ZIPKIN_HTTP_PORT\"}": { - ".": {}, - "f:name": {}, - "f:value": {} - }, - "k:{\"name\":\"SPAN_STORAGE_TYPE\"}": { - ".": {}, - "f:name": {}, - "f:value": {} - } - }, - "f:image": {}, - "f:imagePullPolicy": {}, - "f:name": {}, - "f:ports": { - ".": {}, - "k:{\"containerPort\":14267,\"protocol\":\"TCP\"}": { - ".": {}, - "f:containerPort": {}, - "f:name": {}, - "f:protocol": {} - }, - "k:{\"containerPort\":14268,\"protocol\":\"TCP\"}": { - ".": {}, - "f:containerPort": {}, - "f:name": {}, - "f:protocol": {} - }, - "k:{\"containerPort\":14269,\"protocol\":\"TCP\"}": { - ".": {}, - "f:containerPort": {}, - "f:name": {}, - "f:protocol": {} - }, - "k:{\"containerPort\":16686,\"protocol\":\"TCP\"}": { - ".": {}, - "f:containerPort": {}, - "f:name": {}, - "f:protocol": {} - }, - "k:{\"containerPort\":5775,\"protocol\":\"UDP\"}": { - ".": {}, - "f:containerPort": {}, - "f:name": {}, - "f:protocol": {} - }, - "k:{\"containerPort\":5778,\"protocol\":\"TCP\"}": { - ".": {}, - "f:containerPort": {}, - "f:name": {}, - "f:protocol": {} - }, - "k:{\"containerPort\":6831,\"protocol\":\"UDP\"}": { - ".": {}, - "f:containerPort": {}, - "f:name": {}, - "f:protocol": {} - }, - "k:{\"containerPort\":6832,\"protocol\":\"UDP\"}": { - ".": {}, - "f:containerPort": {}, - "f:name": {}, - "f:protocol": {} - }, - "k:{\"containerPort\":9411,\"protocol\":\"TCP\"}": { - ".": {}, - "f:containerPort": {}, - "f:name": {}, - "f:protocol": {} - } - }, - "f:readinessProbe": { - ".": {}, - "f:failureThreshold": {}, - "f:httpGet": { - ".": {}, - "f:path": {}, - "f:port": {}, - "f:scheme": {} - }, - "f:initialDelaySeconds": {}, - "f:periodSeconds": {}, - "f:successThreshold": {}, - "f:timeoutSeconds": {} - }, - "f:resources": {}, - "f:terminationMessagePath": {}, - "f:terminationMessagePolicy": {}, - "f:volumeMounts": { - ".": {}, - "k:{\"mountPath\":\"/etc/jaeger/sampling\"}": { - ".": {}, - "f:mountPath": {}, - "f:name": {}, - "f:readOnly": {} - } - } - } - }, - "f:dnsPolicy": {}, - "f:restartPolicy": {}, - "f:schedulerName": {}, - "f:securityContext": {}, - "f:serviceAccount": {}, - "f:serviceAccountName": {}, - "f:terminationGracePeriodSeconds": {}, - "f:volumes": { - ".": {}, - "k:{\"name\":\"simplest-sampling-configuration-volume\"}": { - ".": {}, - "f:configMap": { - ".": {}, - "f:defaultMode": {}, - "f:items": {}, - "f:name": {} - }, - "f:name": {} - } - } - } - } - } - }, - "manager": "jaeger-operator", - "operation": "Update", - "time": "2021-01-21T01:08:53Z" - }, - { - "apiVersion": "apps/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - "f:deployment.kubernetes.io/revision": {} - } - }, - "f:status": { - "f:availableReplicas": {}, - "f:conditions": { - ".": {}, - "k:{\"type\":\"Available\"}": { - ".": {}, - "f:lastTransitionTime": {}, - "f:lastUpdateTime": {}, - "f:message": {}, - "f:reason": {}, - "f:status": {}, - "f:type": {} - }, - "k:{\"type\":\"Progressing\"}": { - ".": {}, - "f:lastTransitionTime": {}, - "f:lastUpdateTime": {}, - "f:message": {}, - "f:reason": {}, - "f:status": {}, - "f:type": {} - } - }, - "f:observedGeneration": {}, - "f:readyReplicas": {}, - "f:replicas": {}, - "f:updatedReplicas": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-21T01:09:04Z" - } - ], - "name": "simplest", - "namespace": "default", - "ownerReferences": [ - { - "apiVersion": "jaegertracing.io/v1", - "controller": true, - "kind": "Jaeger", - "name": "simplest", - "uid": "95d303e8-d347-4f8f-b008-a4da3e44b847" - } - ], - "resourceVersion": "451923", - "selfLink": "/apis/apps/v1/namespaces/default/deployments/simplest", - "uid": "ef190adf-c268-409d-8684-3775e59385bf" - }, - "spec": { - "progressDeadlineSeconds": 600, - "replicas": 1, - "revisionHistoryLimit": 10, - "selector": { - "matchLabels": { - "app": "jaeger", - "app.kubernetes.io/component": "all-in-one", - "app.kubernetes.io/instance": "simplest", - "app.kubernetes.io/managed-by": "jaeger-operator", - "app.kubernetes.io/name": "simplest", - "app.kubernetes.io/part-of": "jaeger" - } - }, - "strategy": { - "rollingUpdate": { - "maxSurge": "25%", - "maxUnavailable": "25%" - }, - "type": "RollingUpdate" - }, - "template": { - "metadata": { - "annotations": { - "linkerd.io/inject": "disabled", - "prometheus.io/port": "14269", - "prometheus.io/scrape": "true", - "sidecar.istio.io/inject": "false" - }, - "creationTimestamp": null, - "labels": { - "app": "jaeger", - "app.kubernetes.io/component": "all-in-one", - "app.kubernetes.io/instance": "simplest", - "app.kubernetes.io/managed-by": "jaeger-operator", - "app.kubernetes.io/name": "simplest", - "app.kubernetes.io/part-of": "jaeger" - } - }, - "spec": { - "containers": [ - { - "args": [ - "--sampling.strategies-file=/etc/jaeger/sampling/sampling.json" - ], - "env": [ - { - "name": "SPAN_STORAGE_TYPE", - "value": "memory" - }, - { - "name": "COLLECTOR_ZIPKIN_HTTP_PORT", - "value": "9411" - } - ], - "image": "jaegertracing/all-in-one:1.14.0", - "imagePullPolicy": "IfNotPresent", - "name": "jaeger", - "ports": [ - { - "containerPort": 5775, - "name": "zk-compact-trft", - "protocol": "UDP" - }, - { - "containerPort": 5778, - "name": "config-rest", - "protocol": "TCP" - }, - { - "containerPort": 6831, - "name": "jg-compact-trft", - "protocol": "UDP" - }, - { - "containerPort": 6832, - "name": "jg-binary-trft", - "protocol": "UDP" - }, - { - "containerPort": 9411, - "name": "zipkin", - "protocol": "TCP" - }, - { - "containerPort": 14267, - "name": "c-tchan-trft", - "protocol": "TCP" - }, - { - "containerPort": 14268, - "name": "c-binary-trft", - "protocol": "TCP" - }, - { - "containerPort": 16686, - "name": "query", - "protocol": "TCP" - }, - { - "containerPort": 14269, - "name": "admin-http", - "protocol": "TCP" - } - ], - "readinessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/", - "port": 14269, - "scheme": "HTTP" - }, - "initialDelaySeconds": 1, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "resources": {}, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/etc/jaeger/sampling", - "name": "simplest-sampling-configuration-volume", - "readOnly": true - } - ] - } - ], - "dnsPolicy": "ClusterFirst", - "restartPolicy": "Always", - "schedulerName": "default-scheduler", - "securityContext": {}, - "serviceAccount": "simplest", - "serviceAccountName": "simplest", - "terminationGracePeriodSeconds": 30, - "volumes": [ - { - "configMap": { - "defaultMode": 420, - "items": [ - { - "key": "sampling", - "path": "sampling.json" - } - ], - "name": "simplest-sampling-configuration" - }, - "name": "simplest-sampling-configuration-volume" - } - ] - } - } - }, - "status": { - "availableReplicas": 1, - "conditions": [ - { - "lastTransitionTime": "2021-01-21T01:09:04Z", - "lastUpdateTime": "2021-01-21T01:09:04Z", - "message": "Deployment has minimum availability.", - "reason": "MinimumReplicasAvailable", - "status": "True", - "type": "Available" - }, - { - "lastTransitionTime": "2021-01-21T01:08:53Z", - "lastUpdateTime": "2021-01-21T01:09:04Z", - "message": "ReplicaSet \"simplest-85d9df868\" has successfully progressed.", - "reason": "NewReplicaSetAvailable", - "status": "True", - "type": "Progressing" - } - ], - "observedGeneration": 1, - "readyReplicas": 1, - "replicas": 1, - "updatedReplicas": 1 - } - } - ], - "kind": "List", - "metadata": { - "resourceVersion": "", - "selfLink": "" - } -} diff --git a/infrastructure/kubequery/internal/k8s/apps/testdata/replica_set_test.json b/infrastructure/kubequery/internal/k8s/apps/testdata/replica_set_test.json deleted file mode 100644 index 49d5e2835a..0000000000 --- a/infrastructure/kubequery/internal/k8s/apps/testdata/replica_set_test.json +++ /dev/null @@ -1,235 +0,0 @@ -{ - "apiVersion": "apps/v1", - "kind": "ReplicaSet", - "metadata": { - "annotations": { - "deployment.kubernetes.io/desired-replicas": "1", - "deployment.kubernetes.io/max-replicas": "2", - "deployment.kubernetes.io/revision": "1" - }, - "creationTimestamp": "2021-01-21T01:08:24Z", - "generation": 1, - "labels": { - "name": "jaeger-operator", - "pod-template-hash": "5db4f9d996" - }, - "managedFields": [ - { - "apiVersion": "apps/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:deployment.kubernetes.io/desired-replicas": {}, - "f:deployment.kubernetes.io/max-replicas": {}, - "f:deployment.kubernetes.io/revision": {} - }, - "f:labels": { - ".": {}, - "f:name": {}, - "f:pod-template-hash": {} - }, - "f:ownerReferences": { - ".": {}, - "k:{\"uid\":\"baa856ea-2d04-4bbb-b9be-aa5b89c58087\"}": { - ".": {}, - "f:apiVersion": {}, - "f:blockOwnerDeletion": {}, - "f:controller": {}, - "f:kind": {}, - "f:name": {}, - "f:uid": {} - } - } - }, - "f:spec": { - "f:replicas": {}, - "f:selector": {}, - "f:template": { - "f:metadata": { - "f:labels": { - ".": {}, - "f:name": {}, - "f:pod-template-hash": {} - } - }, - "f:spec": { - "f:containers": { - "k:{\"name\":\"jaeger-operator\"}": { - ".": {}, - "f:args": {}, - "f:env": { - ".": {}, - "k:{\"name\":\"OPERATOR_NAME\"}": { - ".": {}, - "f:name": {}, - "f:value": {} - }, - "k:{\"name\":\"POD_NAME\"}": { - ".": {}, - "f:name": {}, - "f:valueFrom": { - ".": {}, - "f:fieldRef": { - ".": {}, - "f:apiVersion": {}, - "f:fieldPath": {} - } - } - }, - "k:{\"name\":\"POD_NAMESPACE\"}": { - ".": {}, - "f:name": {}, - "f:valueFrom": { - ".": {}, - "f:fieldRef": { - ".": {}, - "f:apiVersion": {}, - "f:fieldPath": {} - } - } - }, - "k:{\"name\":\"WATCH_NAMESPACE\"}": { - ".": {}, - "f:name": {} - } - }, - "f:image": {}, - "f:imagePullPolicy": {}, - "f:name": {}, - "f:ports": { - ".": {}, - "k:{\"containerPort\":8383,\"protocol\":\"TCP\"}": { - ".": {}, - "f:containerPort": {}, - "f:name": {}, - "f:protocol": {} - } - }, - "f:resources": {}, - "f:terminationMessagePath": {}, - "f:terminationMessagePolicy": {} - } - }, - "f:dnsPolicy": {}, - "f:restartPolicy": {}, - "f:schedulerName": {}, - "f:securityContext": {}, - "f:serviceAccount": {}, - "f:serviceAccountName": {}, - "f:terminationGracePeriodSeconds": {} - } - } - }, - "f:status": { - "f:availableReplicas": {}, - "f:fullyLabeledReplicas": {}, - "f:observedGeneration": {}, - "f:readyReplicas": {}, - "f:replicas": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-21T01:08:52Z" - } - ], - "name": "jaeger-operator-5db4f9d996", - "namespace": "default", - "ownerReferences": [ - { - "apiVersion": "apps/v1", - "blockOwnerDeletion": true, - "controller": true, - "kind": "Deployment", - "name": "jaeger-operator", - "uid": "baa856ea-2d04-4bbb-b9be-aa5b89c58087" - } - ], - "resourceVersion": "451811", - "selfLink": "/apis/apps/v1/namespaces/default/replicasets/jaeger-operator-5db4f9d996", - "uid": "2efeb411-ff99-434b-a5a2-4e06c2b0afaa" - }, - "spec": { - "replicas": 1, - "selector": { - "matchLabels": { - "name": "jaeger-operator", - "pod-template-hash": "5db4f9d996" - } - }, - "template": { - "metadata": { - "creationTimestamp": null, - "labels": { - "name": "jaeger-operator", - "pod-template-hash": "5db4f9d996" - } - }, - "spec": { - "containers": [ - { - "args": [ - "start" - ], - "env": [ - { - "name": "WATCH_NAMESPACE" - }, - { - "name": "POD_NAME", - "valueFrom": { - "fieldRef": { - "apiVersion": "v1", - "fieldPath": "metadata.name" - } - } - }, - { - "name": "POD_NAMESPACE", - "valueFrom": { - "fieldRef": { - "apiVersion": "v1", - "fieldPath": "metadata.namespace" - } - } - }, - { - "name": "OPERATOR_NAME", - "value": "jaeger-operator" - } - ], - "image": "jaegertracing/jaeger-operator:1.14.0", - "imagePullPolicy": "Always", - "name": "jaeger-operator", - "ports": [ - { - "containerPort": 8383, - "name": "metrics", - "protocol": "TCP" - } - ], - "resources": {}, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File" - } - ], - "dnsPolicy": "ClusterFirst", - "restartPolicy": "Always", - "schedulerName": "default-scheduler", - "securityContext": {}, - "serviceAccount": "jaeger-operator", - "serviceAccountName": "jaeger-operator", - "terminationGracePeriodSeconds": 30 - } - } - }, - "status": { - "availableReplicas": 1, - "fullyLabeledReplicas": 1, - "observedGeneration": 1, - "readyReplicas": 1, - "replicas": 1 - } -} diff --git a/infrastructure/kubequery/internal/k8s/apps/testdata/stateful_set_test.json b/infrastructure/kubequery/internal/k8s/apps/testdata/stateful_set_test.json deleted file mode 100644 index 1fce755560..0000000000 --- a/infrastructure/kubequery/internal/k8s/apps/testdata/stateful_set_test.json +++ /dev/null @@ -1,403 +0,0 @@ -{ - "apiVersion": "apps/v1", - "kind": "StatefulSet", - "metadata": { - "creationTimestamp": "2021-01-21T01:13:12Z", - "generation": 1, - "labels": { - "alertmanager": "main" - }, - "managedFields": [ - { - "apiVersion": "apps/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:labels": { - ".": {}, - "f:alertmanager": {} - }, - "f:ownerReferences": { - ".": {}, - "k:{\"uid\":\"e6a6358b-646a-4171-8737-beb6c2b9ffaf\"}": { - ".": {}, - "f:apiVersion": {}, - "f:blockOwnerDeletion": {}, - "f:controller": {}, - "f:kind": {}, - "f:name": {}, - "f:uid": {} - } - } - }, - "f:spec": { - "f:podManagementPolicy": {}, - "f:replicas": {}, - "f:revisionHistoryLimit": {}, - "f:selector": {}, - "f:serviceName": {}, - "f:template": { - "f:metadata": { - "f:labels": { - ".": {}, - "f:alertmanager": {}, - "f:app": {} - } - }, - "f:spec": { - "f:containers": { - "k:{\"name\":\"alertmanager\"}": { - ".": {}, - "f:args": {}, - "f:env": { - ".": {}, - "k:{\"name\":\"POD_IP\"}": { - ".": {}, - "f:name": {}, - "f:valueFrom": { - ".": {}, - "f:fieldRef": { - ".": {}, - "f:apiVersion": {}, - "f:fieldPath": {} - } - } - } - }, - "f:image": {}, - "f:imagePullPolicy": {}, - "f:livenessProbe": { - ".": {}, - "f:failureThreshold": {}, - "f:httpGet": { - ".": {}, - "f:path": {}, - "f:port": {}, - "f:scheme": {} - }, - "f:periodSeconds": {}, - "f:successThreshold": {}, - "f:timeoutSeconds": {} - }, - "f:name": {}, - "f:ports": { - ".": {}, - "k:{\"containerPort\":9093,\"protocol\":\"TCP\"}": { - ".": {}, - "f:containerPort": {}, - "f:name": {}, - "f:protocol": {} - }, - "k:{\"containerPort\":9094,\"protocol\":\"TCP\"}": { - ".": {}, - "f:containerPort": {}, - "f:name": {}, - "f:protocol": {} - }, - "k:{\"containerPort\":9094,\"protocol\":\"UDP\"}": { - ".": {}, - "f:containerPort": {}, - "f:name": {}, - "f:protocol": {} - } - }, - "f:readinessProbe": { - ".": {}, - "f:failureThreshold": {}, - "f:httpGet": { - ".": {}, - "f:path": {}, - "f:port": {}, - "f:scheme": {} - }, - "f:initialDelaySeconds": {}, - "f:periodSeconds": {}, - "f:successThreshold": {}, - "f:timeoutSeconds": {} - }, - "f:resources": { - ".": {}, - "f:requests": { - ".": {}, - "f:memory": {} - } - }, - "f:terminationMessagePath": {}, - "f:terminationMessagePolicy": {}, - "f:volumeMounts": { - ".": {}, - "k:{\"mountPath\":\"/alertmanager\"}": { - ".": {}, - "f:mountPath": {}, - "f:name": {} - }, - "k:{\"mountPath\":\"/etc/alertmanager/config\"}": { - ".": {}, - "f:mountPath": {}, - "f:name": {} - } - } - }, - "k:{\"name\":\"config-reloader\"}": { - ".": {}, - "f:args": {}, - "f:image": {}, - "f:imagePullPolicy": {}, - "f:name": {}, - "f:resources": { - ".": {}, - "f:limits": { - ".": {}, - "f:cpu": {}, - "f:memory": {} - } - }, - "f:terminationMessagePath": {}, - "f:terminationMessagePolicy": {}, - "f:volumeMounts": { - ".": {}, - "k:{\"mountPath\":\"/etc/alertmanager/config\"}": { - ".": {}, - "f:mountPath": {}, - "f:name": {}, - "f:readOnly": {} - } - } - } - }, - "f:dnsPolicy": {}, - "f:nodeSelector": { - ".": {}, - "f:kubernetes.io/os": {} - }, - "f:restartPolicy": {}, - "f:schedulerName": {}, - "f:securityContext": { - ".": {}, - "f:fsGroup": {}, - "f:runAsNonRoot": {}, - "f:runAsUser": {} - }, - "f:serviceAccount": {}, - "f:serviceAccountName": {}, - "f:terminationGracePeriodSeconds": {}, - "f:volumes": { - ".": {}, - "k:{\"name\":\"alertmanager-main-db\"}": { - ".": {}, - "f:emptyDir": {}, - "f:name": {} - }, - "k:{\"name\":\"config-volume\"}": { - ".": {}, - "f:name": {}, - "f:secret": { - ".": {}, - "f:defaultMode": {}, - "f:secretName": {} - } - } - } - } - }, - "f:updateStrategy": { - "f:type": {} - } - }, - "f:status": { - "f:replicas": {} - } - }, - "manager": "operator", - "operation": "Update", - "time": "2021-01-21T01:13:12Z" - } - ], - "name": "alertmanager-main", - "namespace": "monitoring", - "ownerReferences": [ - { - "apiVersion": "monitoring.coreos.com/v1", - "blockOwnerDeletion": true, - "controller": true, - "kind": "Alertmanager", - "name": "main", - "uid": "e6a6358b-646a-4171-8737-beb6c2b9ffaf" - } - ], - "resourceVersion": "452929", - "selfLink": "/apis/apps/v1/namespaces/monitoring/statefulsets/alertmanager-main", - "uid": "3c488e7e-420c-4515-b377-5dc3ee082744" - }, - "spec": { - "podManagementPolicy": "Parallel", - "replicas": 1, - "revisionHistoryLimit": 10, - "selector": { - "matchLabels": { - "alertmanager": "main", - "app": "alertmanager" - } - }, - "serviceName": "alertmanager-operated", - "template": { - "metadata": { - "creationTimestamp": null, - "labels": { - "alertmanager": "main", - "app": "alertmanager" - } - }, - "spec": { - "containers": [ - { - "args": [ - "--config.file=/etc/alertmanager/config/alertmanager.yaml", - "--storage.path=/alertmanager", - "--data.retention=120h", - "--cluster.listen-address=", - "--web.listen-address=:9093", - "--web.route-prefix=/", - "--cluster.peer=alertmanager-main-0.alertmanager-operated:9094" - ], - "env": [ - { - "name": "POD_IP", - "valueFrom": { - "fieldRef": { - "apiVersion": "v1", - "fieldPath": "status.podIP" - } - } - } - ], - "image": "quay.io/prometheus/alertmanager:v0.21.0", - "imagePullPolicy": "IfNotPresent", - "livenessProbe": { - "failureThreshold": 10, - "httpGet": { - "path": "/-/healthy", - "port": "web", - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 3 - }, - "name": "alertmanager", - "ports": [ - { - "containerPort": 9093, - "name": "web", - "protocol": "TCP" - }, - { - "containerPort": 9094, - "name": "mesh-tcp", - "protocol": "TCP" - }, - { - "containerPort": 9094, - "name": "mesh-udp", - "protocol": "UDP" - } - ], - "readinessProbe": { - "failureThreshold": 10, - "httpGet": { - "path": "/-/ready", - "port": "web", - "scheme": "HTTP" - }, - "initialDelaySeconds": 3, - "periodSeconds": 5, - "successThreshold": 1, - "timeoutSeconds": 3 - }, - "resources": { - "requests": { - "memory": "200Mi" - } - }, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "FallbackToLogsOnError", - "volumeMounts": [ - { - "mountPath": "/etc/alertmanager/config", - "name": "config-volume" - }, - { - "mountPath": "/alertmanager", - "name": "alertmanager-main-db" - } - ] - }, - { - "args": [ - "-webhook-url=http://localhost:9093/-/reload", - "-volume-dir=/etc/alertmanager/config" - ], - "image": "jimmidyson/configmap-reload:v0.3.0", - "imagePullPolicy": "IfNotPresent", - "name": "config-reloader", - "resources": { - "limits": { - "cpu": "100m", - "memory": "25Mi" - } - }, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "FallbackToLogsOnError", - "volumeMounts": [ - { - "mountPath": "/etc/alertmanager/config", - "name": "config-volume", - "readOnly": true - } - ] - } - ], - "dnsPolicy": "ClusterFirst", - "nodeSelector": { - "kubernetes.io/os": "linux" - }, - "restartPolicy": "Always", - "schedulerName": "default-scheduler", - "securityContext": { - "fsGroup": 2000, - "runAsNonRoot": true, - "runAsUser": 1000 - }, - "serviceAccount": "alertmanager-main", - "serviceAccountName": "alertmanager-main", - "terminationGracePeriodSeconds": 120, - "volumes": [ - { - "name": "config-volume", - "secret": { - "defaultMode": 420, - "secretName": "alertmanager-main" - } - }, - { - "emptyDir": {}, - "name": "alertmanager-main-db" - } - ] - } - }, - "updateStrategy": { - "type": "RollingUpdate" - } - }, - "status": { - "collisionCount": 0, - "currentReplicas": 1, - "currentRevision": "alertmanager-main-6674894c9d", - "observedGeneration": 1, - "readyReplicas": 1, - "replicas": 1, - "updateRevision": "alertmanager-main-6674894c9d", - "updatedReplicas": 1 - } -} diff --git a/infrastructure/kubequery/internal/k8s/autoscaling/horizontal_pod_autoscaler.go b/infrastructure/kubequery/internal/k8s/autoscaling/horizontal_pod_autoscaler.go deleted file mode 100644 index 2a038c6855..0000000000 --- a/infrastructure/kubequery/internal/k8s/autoscaling/horizontal_pod_autoscaler.go +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package autoscaling - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/autoscaling/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type horizontalPodAutoscaler struct { - k8s.CommonNamespacedFields - v1.HorizontalPodAutoscalerSpec - v1.HorizontalPodAutoscalerStatus -} - -// HorizontalPodAutoscalersColumns returns kubernetes horizontal pod autoscaler fields as Osquery table columns. -func HorizontalPodAutoscalersColumns() []table.ColumnDefinition { - return k8s.GetSchema(&horizontalPodAutoscaler{}) -} - -// HorizontalPodAutoscalerGenerate generates the kubernetes horizontal pod autoscalers as Osquery table data. -func HorizontalPodAutoscalerGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - hpas, err := k8s.GetClient().AutoscalingV1().HorizontalPodAutoscalers(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, hpa := range hpas.Items { - item := &horizontalPodAutoscaler{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(hpa.ObjectMeta), - HorizontalPodAutoscalerSpec: hpa.Spec, - HorizontalPodAutoscalerStatus: hpa.Status, - } - results = append(results, k8s.ToMap(item)) - } - - if hpas.Continue == "" { - break - } - options.Continue = hpas.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/autoscaling/horizontal_pod_autoscaler_test.go b/infrastructure/kubequery/internal/k8s/autoscaling/horizontal_pod_autoscaler_test.go deleted file mode 100644 index 80226b15ea..0000000000 --- a/infrastructure/kubequery/internal/k8s/autoscaling/horizontal_pod_autoscaler_test.go +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package autoscaling - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/autoscaling/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/fake" -) - -func TestHorizontalPodAutoscalerGenerate(t *testing.T) { - i32 := int32(456) - i64 := int64(123) - k8s.SetClient(fake.NewSimpleClientset(&v1.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Name: "hpa1", - Namespace: "n123", - UID: types.UID("1234"), - Labels: map[string]string{"a": "b"}, - }, - Spec: v1.HorizontalPodAutoscalerSpec{ - MinReplicas: &i32, - MaxReplicas: i32, - TargetCPUUtilizationPercentage: &i32, - ScaleTargetRef: v1.CrossVersionObjectReference{ - Name: "blah", - }, - }, - Status: v1.HorizontalPodAutoscalerStatus{ - ObservedGeneration: &i64, - LastScaleTime: &metav1.Time{}, - CurrentReplicas: i32, - DesiredReplicas: i32, - CurrentCPUUtilizationPercentage: &i32, - }, - }), types.UID("hello"), "") - - hpas, err := HorizontalPodAutoscalerGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "cluster_uid": "hello", - "creation_timestamp": "0", - "current_cpu_utilization_percentage": "456", - "current_replicas": "456", - "desired_replicas": "456", - "labels": "{\"a\":\"b\"}", - "last_scale_time": "0", - "max_replicas": "456", - "min_replicas": "456", - "name": "hpa1", - "namespace": "n123", - "observed_generation": "123", - "scale_target_ref": "{\"kind\":\"\",\"name\":\"blah\"}", - "target_cpu_utilization_percentage": "456", - "uid": "1234", - }, - }, hpas) -} diff --git a/infrastructure/kubequery/internal/k8s/batch/cron_job.go b/infrastructure/kubequery/internal/k8s/batch/cron_job.go deleted file mode 100644 index cb20e6ed93..0000000000 --- a/infrastructure/kubequery/internal/k8s/batch/cron_job.go +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package batch - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/batch/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type cronJob struct { - k8s.CommonNamespacedFields - k8s.CommonPodFields - v1.CronJobStatus - Schedule string - StartingDeadlineSeconds *int64 - ConcurrencyPolicy v1.ConcurrencyPolicy - Suspend *bool - SuccessfulJobsHistoryLimit *int32 - FailedJobsHistoryLimit *int32 - Parallelism *int32 - Completions *int32 - JobActiveDeadlineSeconds *int64 - BackoffLimit *int32 - Selector *metav1.LabelSelector - ManualSelector *bool - TTLSecondsAfterFinished *int32 -} - -// CronJobColumns returns kubernetes cron job fields as Osquery table columns. -func CronJobColumns() []table.ColumnDefinition { - return k8s.GetSchema(&cronJob{}) -} - -// CronJobsGenerate generates the kubernetes cron jobs as Osquery table data. -func CronJobsGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - cjs, err := k8s.GetClient().BatchV1().CronJobs(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, cj := range cjs.Items { - item := &cronJob{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(cj.ObjectMeta), - CommonPodFields: k8s.GetCommonPodFields(cj.Spec.JobTemplate.Spec.Template.Spec), - CronJobStatus: cj.Status, - Schedule: cj.Spec.Schedule, - StartingDeadlineSeconds: cj.Spec.StartingDeadlineSeconds, - ConcurrencyPolicy: cj.Spec.ConcurrencyPolicy, - Suspend: cj.Spec.Suspend, - SuccessfulJobsHistoryLimit: cj.Spec.SuccessfulJobsHistoryLimit, - FailedJobsHistoryLimit: cj.Spec.FailedJobsHistoryLimit, - Parallelism: cj.Spec.JobTemplate.Spec.Parallelism, - Completions: cj.Spec.JobTemplate.Spec.Completions, - JobActiveDeadlineSeconds: cj.Spec.JobTemplate.Spec.ActiveDeadlineSeconds, - BackoffLimit: cj.Spec.JobTemplate.Spec.BackoffLimit, - Selector: cj.Spec.JobTemplate.Spec.Selector, - ManualSelector: cj.Spec.JobTemplate.Spec.ManualSelector, - TTLSecondsAfterFinished: cj.Spec.JobTemplate.Spec.TTLSecondsAfterFinished, - } - results = append(results, k8s.ToMap(item)) - } - - if cjs.Continue == "" { - break - } - options.Continue = cjs.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/batch/cron_job_test.go b/infrastructure/kubequery/internal/k8s/batch/cron_job_test.go deleted file mode 100644 index fe9c5fe0d8..0000000000 --- a/infrastructure/kubequery/internal/k8s/batch/cron_job_test.go +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package batch - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/batch/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/fake" -) - -func TestCronJobsGenerate(t *testing.T) { - i32 := int32(456) - i64 := int64(123) - b := bool(true) - k8s.SetClient(fake.NewSimpleClientset(&v1.CronJob{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cj1", - Namespace: "n123", - UID: types.UID("1234"), - Labels: map[string]string{"a": "b"}, - }, - Spec: v1.CronJobSpec{ - Schedule: "s1", - StartingDeadlineSeconds: &i64, - ConcurrencyPolicy: v1.AllowConcurrent, - Suspend: &b, - SuccessfulJobsHistoryLimit: &i32, - FailedJobsHistoryLimit: &i32, - JobTemplate: v1.JobTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Name: "job1", - Namespace: "n123", - UID: types.UID("1234"), - Labels: map[string]string{"a": "b"}, - }, - Spec: v1.JobSpec{ - Parallelism: &i32, - Completions: &i32, - ActiveDeadlineSeconds: &i64, - BackoffLimit: &i32, - ManualSelector: &b, - TTLSecondsAfterFinished: &i32, - }, - }, - }, - Status: v1.CronJobStatus{ - LastScheduleTime: &metav1.Time{}, - }, - }), types.UID("hello"), "") - - cjs, err := CronJobsGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "backoff_limit": "456", - "cluster_uid": "hello", - "completions": "456", - "concurrency_policy": "Allow", - "creation_timestamp": "0", - "failed_jobs_history_limit": "456", - "host_ipc": "0", - "host_network": "0", - "host_pid": "0", - "job_active_deadline_seconds": "123", - "labels": "{\"a\":\"b\"}", - "last_schedule_time": "0", - "manual_selector": "1", - "name": "cj1", - "namespace": "n123", - "parallelism": "456", - "schedule": "s1", - "starting_deadline_seconds": "123", - "successful_jobs_history_limit": "456", - "suspend": "1", - "ttl_seconds_after_finished": "456", - "uid": "1234", - }, - }, cjs) -} diff --git a/infrastructure/kubequery/internal/k8s/batch/job.go b/infrastructure/kubequery/internal/k8s/batch/job.go deleted file mode 100644 index 4e2b2fbd22..0000000000 --- a/infrastructure/kubequery/internal/k8s/batch/job.go +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package batch - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/batch/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type job struct { - k8s.CommonNamespacedFields - k8s.CommonPodFields - v1.JobStatus - Parallelism *int32 - Completions *int32 - JobActiveDeadlineSeconds *int64 - BackoffLimit *int32 - Selector *metav1.LabelSelector - ManualSelector *bool - TTLSecondsAfterFinished *int32 -} - -// JobColumns returns kubernetes job fields as Osquery table columns. -func JobColumns() []table.ColumnDefinition { - return k8s.GetSchema(&job{}) -} - -// JobsGenerate generates the kubernetes jobs as Osquery table data. -func JobsGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - jobs, err := k8s.GetClient().BatchV1().Jobs(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, j := range jobs.Items { - item := &job{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(j.ObjectMeta), - CommonPodFields: k8s.GetCommonPodFields(j.Spec.Template.Spec), - JobStatus: j.Status, - Parallelism: j.Spec.Parallelism, - Completions: j.Spec.Completions, - JobActiveDeadlineSeconds: j.Spec.ActiveDeadlineSeconds, - BackoffLimit: j.Spec.BackoffLimit, - Selector: j.Spec.Selector, - ManualSelector: j.Spec.ManualSelector, - TTLSecondsAfterFinished: j.Spec.TTLSecondsAfterFinished, - } - results = append(results, k8s.ToMap(item)) - } - - if jobs.Continue == "" { - break - } - options.Continue = jobs.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/batch/job_test.go b/infrastructure/kubequery/internal/k8s/batch/job_test.go deleted file mode 100644 index 84a6e012c6..0000000000 --- a/infrastructure/kubequery/internal/k8s/batch/job_test.go +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package batch - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/batch/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/fake" -) - -func TestJobsGenerate(t *testing.T) { - i32 := int32(456) - i64 := int64(123) - b := bool(true) - k8s.SetClient(fake.NewSimpleClientset(&v1.Job{ - ObjectMeta: metav1.ObjectMeta{ - Name: "job1", - Namespace: "n123", - UID: types.UID("1234"), - Labels: map[string]string{"a": "b"}, - }, - Spec: v1.JobSpec{ - Parallelism: &i32, - Completions: &i32, - ActiveDeadlineSeconds: &i64, - BackoffLimit: &i32, - ManualSelector: &b, - TTLSecondsAfterFinished: &i32, - }, - Status: v1.JobStatus{ - StartTime: &metav1.Time{}, - Active: i32, - Succeeded: i32, - Failed: i32, - CompletionTime: nil, - Conditions: []v1.JobCondition{ - { - Type: v1.JobComplete, - Status: metav1.DryRunAll, - LastTransitionTime: metav1.Time{}, - Reason: "reason", - Message: "message", - }, - }, - }, - }), types.UID("hello"), "") - - js, err := JobsGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "active": "456", - "backoff_limit": "456", - "cluster_uid": "hello", - "completions": "456", - "conditions": "[{\"type\":\"Complete\",\"status\":\"All\",\"lastProbeTime\":null,\"lastTransitionTime\":null,\"reason\":\"reason\",\"message\":\"message\"}]", - "creation_timestamp": "0", - "failed": "456", - "host_ipc": "0", - "host_network": "0", - "host_pid": "0", - "job_active_deadline_seconds": "123", - "labels": "{\"a\":\"b\"}", - "manual_selector": "1", - "name": "job1", - "namespace": "n123", - "parallelism": "456", - "start_time": "0", - "succeeded": "456", - "ttl_seconds_after_finished": "456", - "uid": "1234", - }, - }, js) -} diff --git a/infrastructure/kubequery/internal/k8s/client.go b/infrastructure/kubequery/internal/k8s/client.go deleted file mode 100644 index d2d2081e0b..0000000000 --- a/infrastructure/kubequery/internal/k8s/client.go +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package k8s - -import ( - "context" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "sync" - - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/util/homedir" -) - -var ( - lock sync.Mutex - clientset kubernetes.Interface - clusterUID types.UID - clusterName string -) - -func initClientset(config *rest.Config) error { - if config == nil { - conf, err := rest.InClusterConfig() - if err != nil { - if home := homedir.HomeDir(); home != "" { - content, ferr := ioutil.ReadFile(filepath.Join(home, ".kube", "config")) - if content != nil && ferr == nil { - conf, err = clientcmd.RESTConfigFromKubeConfig(content) - if err != nil { - return err - } - } - } - } - config = conf - } - - // Suppress deprecation warnings - config.WarningHandler = rest.NoWarnings{} - - var err error - clientset, err = kubernetes.NewForConfig(config) - if err != nil { - return err - } - return nil -} - -func initUID() error { - ks, err := GetClient().CoreV1().Namespaces().Get(context.TODO(), "kube-system", v1.GetOptions{}) - if err != nil { - return err - } - - clusterUID = ks.UID - if ks.ClusterName == "" { - clusterName, err = os.Hostname() - if err != nil { - fmt.Println("Unable to determine hostname: ", err.Error()) - } - } else { - clusterName = ks.ClusterName - } - - return nil -} - -// Init creates in-cluster kubernetes configuration and a client set using the configuration. -// This returns error if KUBERNETES_SERVICE_HOST or KUBERNETES_SERVICE_PORT environment variables are not set. -func Init() error { - lock.Lock() - defer lock.Unlock() - - err := initClientset(nil) - if err != nil { - return err - } - err = initUID() - if err != nil { - return err - } - - return nil -} - -// GetClient returns kubernetes interface that can be used to communicate with API server. -func GetClient() kubernetes.Interface { - return clientset -} - -// GetClusterUID returns unique identifier for the current kubernetes cluster. -// This is same as the kube-system namespace UID. -func GetClusterUID() types.UID { - return clusterUID -} - -// GetClusterName returns cluster name provided by the kubernates API. -// If it is empty, it uses the pod hostname which should be set to the cluster name. -func GetClusterName() string { - return clusterName -} - -// SetClient is helper function to override the kubernetes interface with fake one for testing. -func SetClient(client kubernetes.Interface, uid types.UID, name string) { - lock.Lock() - defer lock.Unlock() - - clientset = client - clusterUID = uid - clusterName = name -} diff --git a/infrastructure/kubequery/internal/k8s/client_test.go b/infrastructure/kubequery/internal/k8s/client_test.go deleted file mode 100644 index f46f3b37ce..0000000000 --- a/infrastructure/kubequery/internal/k8s/client_test.go +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package k8s - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/fake" -) - -func TestGetClient(t *testing.T) { - SetClient(fake.NewSimpleClientset(), types.UID("uid"), "cluster-name") - assert.NotNil(t, GetClient(), "Clientset should be valid") -} - -func TestGetClusterUID(t *testing.T) { - SetClient(fake.NewSimpleClientset(), types.UID("uid"), "cluster-name") - assert.Equal(t, types.UID("uid"), GetClusterUID()) -} - -func TestGetClusterName(t *testing.T) { - SetClient(fake.NewSimpleClientset(), types.UID("uid"), "cluster-name") - assert.Equal(t, "cluster-name", GetClusterName()) -} diff --git a/infrastructure/kubequery/internal/k8s/common.go b/infrastructure/kubequery/internal/k8s/common.go deleted file mode 100644 index b0d4476d2f..0000000000 --- a/infrastructure/kubequery/internal/k8s/common.go +++ /dev/null @@ -1,694 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package k8s - -import ( - "github.com/google/uuid" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" -) - -// CommonFields contains fields common to most tables. Contents are derived from kubernetes ObjectMeta. -// This is used for kubernetes resources that are not namespaced. -type CommonFields struct { - UID types.UID - ClusterName string - ClusterUID types.UID - Name string - CreationTimestamp metav1.Time - Labels map[string]string - Annotations map[string]string -} - -// GetCommonFields returns CommonFields struct from the provided kubernetes ObjectMeta. -func GetCommonFields(obj metav1.ObjectMeta) CommonFields { - return CommonFields{ - UID: obj.UID, - ClusterName: GetClusterName(), - ClusterUID: GetClusterUID(), - Name: obj.Name, - CreationTimestamp: obj.CreationTimestamp, - Labels: obj.Labels, - Annotations: obj.Annotations, - } -} - -// CommonNamespacedFields contains fields common to most tables. Contents are derived from kubernetes ObjectMeta. -// This is used for kubernetes resources that are namespaced. -type CommonNamespacedFields struct { - UID types.UID - ClusterName string - ClusterUID types.UID - Name string - Namespace string - CreationTimestamp metav1.Time - Labels map[string]string - Annotations map[string]string -} - -// GetCommonNamespacedFields returns CommonNamespacedFields struct from the provided kubernetes ObjectMeta. -func GetCommonNamespacedFields(obj metav1.ObjectMeta) CommonNamespacedFields { - return CommonNamespacedFields{ - UID: obj.UID, - ClusterName: GetClusterName(), - ClusterUID: GetClusterUID(), - Name: obj.Name, - Namespace: obj.Namespace, - CreationTimestamp: obj.CreationTimestamp, - Labels: obj.Labels, - Annotations: obj.Annotations, - } -} - -// GetParentCommonNamespacedFields returns CommonNamespacedFields struct from the parent ObjectMeta creating a UID using parent UID + provided name. -func GetParentCommonNamespacedFields(parent metav1.ObjectMeta, name string) CommonNamespacedFields { - uid := uuid.NewSHA1(uuid.NameSpaceDNS, []byte(string(parent.UID)+name)).String() - return CommonNamespacedFields{ - UID: types.UID(uid), - ClusterName: GetClusterName(), - ClusterUID: GetClusterUID(), - Name: name, - Namespace: parent.Namespace, - CreationTimestamp: parent.CreationTimestamp, - Labels: parent.Labels, - Annotations: parent.Annotations, - } -} - -// SELinuxOptionsFields contains SELinux options as a flat structure. -type SELinuxOptionsFields struct { - SELinuxOptionsUser string - SELinuxOptionsRole string - SELinuxOptionsType string - SELinuxOptionsLevel string -} - -// WindowsOptionsFields contains Windows options as a flat structure. -type WindowsOptionsFields struct { - WindowsOptionsGMSACredentialSpecName *string - WindowsOptionsGMSACredentialSpec *string - WindowsOptionsRunAsUserName *string -} - -// SeccompProfileFields contains Seccomp profile options as a flat structure. -type SeccompProfileFields struct { - SeccompProfileType v1.SeccompProfileType - SeccompProfileLocalhostProfile *string -} - -// CommonSecurityContextFields contains all security options common to a pod and container. -type CommonSecurityContextFields struct { - SELinuxOptionsFields - WindowsOptionsFields - SeccompProfileFields - RunAsUser *int64 - RunAsGroup *int64 - RunAsNonRoot *bool -} - -// PodSecurityContextFields contains all security options specific to a pod. -type PodSecurityContextFields struct { - CommonSecurityContextFields - SupplementalGroups []int64 - FSGroup *int64 - Sysctls []v1.Sysctl - FSGroupChangePolicy *v1.PodFSGroupChangePolicy -} - -// SecurityContextFields contains all securoty options specific to a container. -type SecurityContextFields struct { - CommonSecurityContextFields - CapabilitiesAdd []v1.Capability - CapabilitiesDrop []v1.Capability - Privileged *bool - ReadOnlyRootFilesystem *bool - AllowPrivilegeEscalation *bool - ProcMount *v1.ProcMountType -} - -// AffinityFields struct holds flat affinity fields. -type AffinityFields struct { - NodeAffinity *v1.NodeAffinity - PodAffinity *v1.PodAffinity - PodAntiAffinity *v1.PodAntiAffinity -} - -// DNSConfigFields struct holds DNS configuration fields. -type DNSConfigFields struct { - DNSConfigNameservers []string - DNSConfigSearches []string - DNSConfigOptions []v1.PodDNSConfigOption -} - -// CommonPodFields contains relevant fields from pod specification. -// This flattens some of the embedded structures like security context, DNS config etc. -type CommonPodFields struct { - PodSecurityContextFields - AffinityFields - DNSConfigFields - - NodeSelector map[string]string - RestartPolicy v1.RestartPolicy - TerminationGracePeriodSeconds *int64 - ActiveDeadlineSeconds *int64 - DNSPolicy v1.DNSPolicy - ServiceAccountName string - AutomountServiceAccountToken *bool - NodeName string - HostNetwork bool - HostPID bool - HostIPC bool - ShareProcessNamespace *bool - ImagePullSecrets []v1.LocalObjectReference - Hostname string - Subdomain string - SchedulerName string - Tolerations []v1.Toleration - HostAliases []v1.HostAlias - PriorityClassName string - Priority *int32 - ReadinessGates []v1.PodReadinessGate - RuntimeClassName *string - EnableServiceLinks *bool - PreemptionPolicy *v1.PreemptionPolicy - Overhead v1.ResourceList - TopologySpreadConstraints []v1.TopologySpreadConstraint - SetHostnameAsFQDN *bool -} - -// GetCommonPodFields converts pod specification to CommonPodFields structure. -// This flattens some of the embedded structures like security context, DNS config etc. -func GetCommonPodFields(p v1.PodSpec) CommonPodFields { - item := CommonPodFields{ - NodeSelector: p.NodeSelector, - RestartPolicy: p.RestartPolicy, - TerminationGracePeriodSeconds: p.TerminationGracePeriodSeconds, - ActiveDeadlineSeconds: p.ActiveDeadlineSeconds, - DNSPolicy: p.DNSPolicy, - ServiceAccountName: p.ServiceAccountName, - AutomountServiceAccountToken: p.AutomountServiceAccountToken, - NodeName: p.NodeName, - HostNetwork: p.HostNetwork, - HostPID: p.HostPID, - HostIPC: p.HostIPC, - ShareProcessNamespace: p.ShareProcessNamespace, - ImagePullSecrets: p.ImagePullSecrets, - Hostname: p.Hostname, - Subdomain: p.Subdomain, - SchedulerName: p.SchedulerName, - Tolerations: p.Tolerations, - HostAliases: p.HostAliases, - PriorityClassName: p.PriorityClassName, - Priority: p.Priority, - ReadinessGates: p.ReadinessGates, - RuntimeClassName: p.RuntimeClassName, - EnableServiceLinks: p.EnableServiceLinks, - PreemptionPolicy: p.PreemptionPolicy, - Overhead: p.Overhead, - TopologySpreadConstraints: p.TopologySpreadConstraints, - SetHostnameAsFQDN: p.SetHostnameAsFQDN, - } - if p.Affinity != nil { - item.NodeAffinity = p.Affinity.NodeAffinity - item.PodAffinity = p.Affinity.PodAffinity - item.PodAntiAffinity = p.Affinity.PodAntiAffinity - } - if p.DNSConfig != nil { - item.DNSConfigNameservers = p.DNSConfig.Nameservers - item.DNSConfigSearches = p.DNSConfig.Searches - item.DNSConfigOptions = p.DNSConfig.Options - } - if p.SecurityContext != nil { - item.RunAsUser = p.SecurityContext.RunAsUser - item.RunAsGroup = p.SecurityContext.RunAsGroup - item.RunAsNonRoot = p.SecurityContext.RunAsNonRoot - item.SupplementalGroups = p.SecurityContext.SupplementalGroups - item.Sysctls = p.SecurityContext.Sysctls - item.FSGroup = p.SecurityContext.FSGroup - item.FSGroupChangePolicy = p.SecurityContext.FSGroupChangePolicy - if p.SecurityContext.SeccompProfile != nil { - item.SeccompProfileType = p.SecurityContext.SeccompProfile.Type - item.SeccompProfileLocalhostProfile = p.SecurityContext.SeccompProfile.LocalhostProfile - } - if p.SecurityContext.SELinuxOptions != nil { - item.SELinuxOptionsLevel = p.SecurityContext.SELinuxOptions.Level - item.SELinuxOptionsRole = p.SecurityContext.SELinuxOptions.Role - item.SELinuxOptionsType = p.SecurityContext.SELinuxOptions.Type - item.SELinuxOptionsUser = p.SecurityContext.SELinuxOptions.User - } - if p.SecurityContext.WindowsOptions != nil { - item.WindowsOptionsRunAsUserName = p.SecurityContext.WindowsOptions.RunAsUserName - item.WindowsOptionsGMSACredentialSpec = p.SecurityContext.WindowsOptions.GMSACredentialSpec - item.WindowsOptionsGMSACredentialSpecName = p.SecurityContext.WindowsOptions.GMSACredentialSpecName - } - } - return item -} - -// CommonContainerFields contains relevant fields from container specification. -// This flattens some of the embedded structures like security context. -type CommonContainerFields struct { - SecurityContextFields - TargetContainerName string - Image string - Command []string - Args []string - WorkingDir string - Ports []v1.ContainerPort - EnvFrom []v1.EnvFromSource - Env []v1.EnvVar - ResourceLimits v1.ResourceList - ResourceRequests v1.ResourceList - VolumeMounts []v1.VolumeMount - VolumeDevices []v1.VolumeDevice - LivenessProbe *v1.Probe - ReadinessProbe *v1.Probe - StartupProbe *v1.Probe - Lifecycle *v1.Lifecycle - TerminationMessagePath string - TerminationMessagePolicy v1.TerminationMessagePolicy - ImagePullPolicy v1.PullPolicy - Stdin bool - StdinOnce bool - TTY bool -} - -// GetCommonContainerFields converts container specification to CommonContainerFields structure. -// This flattens some of the embedded structures like security context. -func GetCommonContainerFields(c v1.Container) CommonContainerFields { - item := CommonContainerFields{ - Image: c.Image, - Command: c.Command, - Args: c.Args, - WorkingDir: c.WorkingDir, - Ports: c.Ports, - EnvFrom: c.EnvFrom, - Env: c.Env, - ResourceLimits: c.Resources.Limits, - ResourceRequests: c.Resources.Requests, - VolumeMounts: c.VolumeMounts, - VolumeDevices: c.VolumeDevices, - LivenessProbe: c.LivenessProbe, - ReadinessProbe: c.ReadinessProbe, - StartupProbe: c.StartupProbe, - Lifecycle: c.Lifecycle, - TerminationMessagePath: c.TerminationMessagePath, - TerminationMessagePolicy: c.TerminationMessagePolicy, - ImagePullPolicy: c.ImagePullPolicy, - Stdin: c.Stdin, - StdinOnce: c.StdinOnce, - TTY: c.TTY, - } - copyContainerSecurityContext(&item, c.SecurityContext) - return item -} - -// GetCommonEphemeralContainerFields converts ephemeral container specification to CommonContainerFields. -// This flattens some of the embedded structures like security context. -// Ephemeral container contains one additional field (TargetContainerName) on top of container. -func GetCommonEphemeralContainerFields(c v1.EphemeralContainer) CommonContainerFields { - item := CommonContainerFields{ - TargetContainerName: c.TargetContainerName, - Image: c.Image, - Command: c.Command, - Args: c.Args, - WorkingDir: c.WorkingDir, - Ports: c.Ports, - EnvFrom: c.EnvFrom, - Env: c.Env, - ResourceLimits: c.Resources.Limits, - ResourceRequests: c.Resources.Requests, - VolumeMounts: c.VolumeMounts, - VolumeDevices: c.VolumeDevices, - LivenessProbe: c.LivenessProbe, - ReadinessProbe: c.ReadinessProbe, - StartupProbe: c.StartupProbe, - Lifecycle: c.Lifecycle, - TerminationMessagePath: c.TerminationMessagePath, - TerminationMessagePolicy: c.TerminationMessagePolicy, - ImagePullPolicy: c.ImagePullPolicy, - Stdin: c.Stdin, - StdinOnce: c.StdinOnce, - TTY: c.TTY, - } - copyContainerSecurityContext(&item, c.SecurityContext) - return item -} - -func copyContainerSecurityContext(item *CommonContainerFields, sc *v1.SecurityContext) { - if sc != nil { - item.Privileged = sc.Privileged - item.RunAsUser = sc.RunAsUser - item.RunAsGroup = sc.RunAsGroup - item.RunAsNonRoot = sc.RunAsNonRoot - item.ReadOnlyRootFilesystem = sc.ReadOnlyRootFilesystem - item.AllowPrivilegeEscalation = sc.AllowPrivilegeEscalation - item.ProcMount = sc.ProcMount - - if sc.Capabilities != nil { - item.CapabilitiesAdd = sc.Capabilities.Add - item.CapabilitiesDrop = sc.Capabilities.Drop - } - if sc.SeccompProfile != nil { - item.SeccompProfileType = sc.SeccompProfile.Type - item.SeccompProfileLocalhostProfile = sc.SeccompProfile.LocalhostProfile - } - if sc.SELinuxOptions != nil { - item.SELinuxOptionsLevel = sc.SELinuxOptions.Level - item.SELinuxOptionsRole = sc.SELinuxOptions.Role - item.SELinuxOptionsType = sc.SELinuxOptions.Type - item.SELinuxOptionsUser = sc.SELinuxOptions.User - } - if sc.WindowsOptions != nil { - item.WindowsOptionsRunAsUserName = sc.WindowsOptions.RunAsUserName - item.WindowsOptionsGMSACredentialSpec = sc.WindowsOptions.GMSACredentialSpec - item.WindowsOptionsGMSACredentialSpecName = sc.WindowsOptions.GMSACredentialSpecName - } - } -} - -// CommonVolumeFields contains flattened fields from volume specification. -type CommonVolumeFields struct { - VolumeType string - FSType *string - ReadOnly *bool - SecretName string - HostPathPath string - HostPathType *v1.HostPathType - EmptyDirMedium v1.StorageMedium - EmptyDirSizeLimit string - GCEPersistentDiskPDName string - GCEPersistentDiskPartition int32 - AWSElasticBlockStoreVolumeID string - AWSElasticBlockStorePartition int32 - GitRepoRepository string - GitRepoRevision string - GitRepoDirectory string - SecretItems []v1.KeyToPath - SecretDefaultMode *int32 - SecretOptional *bool - NFSServer string - NFSPath string - ISCSITargetPortal string - ISCSIIqn string - ISCSILun int32 - ISCSIInterface string - ISCSIPortals []string - ISCSIDiscoveryCHAPAuth bool - ISCSISessionCHAPAuth bool - ISCSIInitiatorName *string - GlusterfsEndpointsName string - GlusterfsPath string - PersistentVolumeClaimName string - RBDCephMonitors []string - RBDImage string - RBDPool string - RBDRadosUser string - RBDKeyring string - FlexVolumeDriver string - FlexVolumeOptions map[string]string - CinderVolumeID string - CephFSMonitors []string - CephFSPath string - CephFSUser string - CephFSSecretFile string - FlockerDatasetName string - FlockerDatasetUUID string - DownwardAPIItems []v1.DownwardAPIVolumeFile - DownwardAPIDefaultMode *int32 - FCTargetWWNs []string - FCLun *int32 - FcWWIDs []string - AzureFileShareName string - ConfigMapName string - ConfigMapItems []v1.KeyToPath - ConfigMapDefaultMode *int32 - ConfigMapOptional *bool - VsphereVolumeVolumePath string - VsphereVolumeStoragePolicyName string - VsphereVolumeStoragePolicyID string - QuobyteRegistry string - QuobyteVolume string - QuobyteUser string - QuobyteGroup string - QuobyteTenant string - AzureDiskDiskName string - AzureDiskDataDiskURI string - AzureDiskCachingMode *v1.AzureDataDiskCachingMode - AzureDiskKind *v1.AzureDataDiskKind - PhotonPersistentDiskPdID string - ProjectedSources []v1.VolumeProjection - ProjectedDefaultMode *int32 - PortworxVolumeID string - ScaleIOGateway string - ScaleIOSystem string - ScaleIOSSLEnabled bool - ScaleIOProtectionDomain string - ScaleIOStoragePool string - ScaleIOStorageMode string - ScaleIOVolumeName string - StorageOSVolumeName string - StorageOSVolumeNamespace string - CSIDriver string - CSIVolumeAttributes map[string]string - EphemeralVolumeClaimTemplate *v1.PersistentVolumeClaimTemplate -} - -// GetCommonVolumeFields converts volume specification to CommonVolumeFields. -// This flattens most of the embedded structures like AWSElasticBlockStore, AzureDisk, etc. -func GetCommonVolumeFields(from v1.Volume) CommonVolumeFields { - to := CommonVolumeFields{} - if from.AWSElasticBlockStore != nil { - to.VolumeType = "aws_elastic_block_store" - to.AWSElasticBlockStoreVolumeID = from.AWSElasticBlockStore.VolumeID - to.AWSElasticBlockStorePartition = from.AWSElasticBlockStore.Partition - to.FSType = &from.AWSElasticBlockStore.FSType - to.ReadOnly = &from.AWSElasticBlockStore.ReadOnly - } - if from.AzureDisk != nil { - to.VolumeType = "azure_disk" - to.AzureDiskCachingMode = from.AzureDisk.CachingMode - to.AzureDiskDataDiskURI = from.AzureDisk.DataDiskURI - to.AzureDiskDiskName = from.AzureDisk.DiskName - to.AzureDiskKind = from.AzureDisk.Kind - to.FSType = from.AzureDisk.FSType - to.ReadOnly = from.AzureDisk.ReadOnly - } - if from.AzureFile != nil { - to.VolumeType = "azure_file" - to.AzureFileShareName = from.AzureFile.ShareName - to.SecretName = from.AzureFile.SecretName - to.ReadOnly = &from.AzureFile.ReadOnly - } - if from.CSI != nil { - to.VolumeType = "csi" - to.CSIDriver = from.CSI.Driver - to.CSIVolumeAttributes = from.CSI.VolumeAttributes - to.FSType = from.CSI.FSType - to.ReadOnly = from.CSI.ReadOnly - if from.CSI.NodePublishSecretRef != nil { - to.SecretName = from.CSI.NodePublishSecretRef.Name - } - } - if from.CephFS != nil { - to.VolumeType = "ceph_fs" - to.CephFSMonitors = from.CephFS.Monitors - to.CephFSPath = from.CephFS.Path - to.CephFSSecretFile = from.CephFS.SecretFile - to.CephFSUser = from.CephFS.User - to.ReadOnly = &from.CephFS.ReadOnly - if from.CephFS.SecretRef != nil { - to.SecretName = from.CephFS.SecretRef.Name - } - } - if from.Cinder != nil { - to.VolumeType = "cinder" - to.CinderVolumeID = from.Cinder.VolumeID - to.FSType = &from.Cinder.FSType - to.ReadOnly = &from.Cinder.ReadOnly - if from.Cinder.SecretRef != nil { - to.SecretName = from.Cinder.SecretRef.Name - } - } - if from.ConfigMap != nil { - to.VolumeType = "config_map" - to.ConfigMapDefaultMode = from.ConfigMap.DefaultMode - to.ConfigMapItems = from.ConfigMap.Items - to.ConfigMapName = from.ConfigMap.Name - to.ConfigMapOptional = from.ConfigMap.Optional - } - if from.DownwardAPI != nil { - to.VolumeType = "downward_api" - to.DownwardAPIDefaultMode = from.DownwardAPI.DefaultMode - to.DownwardAPIItems = from.DownwardAPI.Items - } - if from.EmptyDir != nil { - to.VolumeType = "empty_dir" - to.EmptyDirMedium = from.EmptyDir.Medium - to.EmptyDirSizeLimit = from.EmptyDir.SizeLimit.String() - } - if from.Ephemeral != nil { - to.VolumeType = "ephemeral" - to.EphemeralVolumeClaimTemplate = from.Ephemeral.VolumeClaimTemplate - } - if from.FC != nil { - to.VolumeType = "fc" - to.FCLun = from.FC.Lun - to.FCTargetWWNs = from.FC.TargetWWNs - to.FcWWIDs = from.FC.WWIDs - to.FSType = &from.FC.FSType - to.ReadOnly = &from.FC.ReadOnly - } - if from.FlexVolume != nil { - to.VolumeType = "flex_volume" - to.FlexVolumeDriver = from.FlexVolume.Driver - to.FlexVolumeOptions = from.FlexVolume.Options - to.FSType = &from.FlexVolume.FSType - to.ReadOnly = &from.FlexVolume.ReadOnly - if from.FlexVolume.SecretRef != nil { - to.SecretName = from.FlexVolume.SecretRef.Name - } - } - if from.Flocker != nil { - to.VolumeType = "flocker" - to.FlockerDatasetName = from.Flocker.DatasetName - to.FlockerDatasetUUID = from.Flocker.DatasetUUID - } - if from.GCEPersistentDisk != nil { - to.VolumeType = "gce_persistent_disk" - to.GCEPersistentDiskPDName = from.GCEPersistentDisk.PDName - to.GCEPersistentDiskPartition = from.GCEPersistentDisk.Partition - to.FSType = &from.GCEPersistentDisk.FSType - to.ReadOnly = &from.GCEPersistentDisk.ReadOnly - } - if from.GitRepo != nil { - to.VolumeType = "git_repo" - to.GitRepoDirectory = from.GitRepo.Directory - to.GitRepoRepository = from.GitRepo.Repository - to.GitRepoRevision = from.GitRepo.Revision - } - if from.Glusterfs != nil { - to.VolumeType = "gluster_fs" - to.GlusterfsPath = from.Glusterfs.Path - to.GlusterfsEndpointsName = from.Glusterfs.EndpointsName - to.ReadOnly = &from.Glusterfs.ReadOnly - } - if from.HostPath != nil { - to.VolumeType = "host_path" - to.HostPathPath = from.HostPath.Path - to.HostPathType = from.HostPath.Type - } - if from.ISCSI != nil { - to.VolumeType = "iscsci" - to.ISCSITargetPortal = from.ISCSI.TargetPortal - to.ISCSIIqn = from.ISCSI.IQN - to.ISCSILun = from.ISCSI.Lun - to.ISCSIInterface = from.ISCSI.ISCSIInterface - to.ISCSIPortals = from.ISCSI.Portals - to.ISCSIDiscoveryCHAPAuth = from.ISCSI.DiscoveryCHAPAuth - to.ISCSISessionCHAPAuth = from.ISCSI.SessionCHAPAuth - to.ISCSIInitiatorName = from.ISCSI.InitiatorName - to.FSType = &from.ISCSI.FSType - to.ReadOnly = &from.ISCSI.ReadOnly - if from.ISCSI.SecretRef != nil { - to.SecretName = from.ISCSI.SecretRef.Name - } - } - if from.NFS != nil { - to.VolumeType = "nfs" - to.NFSPath = from.NFS.Path - to.NFSServer = from.NFS.Server - to.ReadOnly = &from.NFS.ReadOnly - } - if from.PersistentVolumeClaim != nil { - to.VolumeType = "persistent_volume_claim" - to.PersistentVolumeClaimName = from.PersistentVolumeClaim.ClaimName - to.ReadOnly = &from.PersistentVolumeClaim.ReadOnly - } - if from.PhotonPersistentDisk != nil { - to.VolumeType = "photon_persistent_disk" - to.PhotonPersistentDiskPdID = from.PhotonPersistentDisk.PdID - to.FSType = &from.PhotonPersistentDisk.FSType - } - if from.PortworxVolume != nil { - to.VolumeType = "portworx_volume" - to.PortworxVolumeID = from.PortworxVolume.VolumeID - to.FSType = &from.PortworxVolume.FSType - to.ReadOnly = &from.PortworxVolume.ReadOnly - } - if from.Projected != nil { - to.VolumeType = "projected" - to.ProjectedDefaultMode = from.Projected.DefaultMode - to.ProjectedSources = from.Projected.Sources - } - if from.Quobyte != nil { - to.VolumeType = "quobyte" - to.QuobyteGroup = from.Quobyte.Group - to.QuobyteRegistry = from.Quobyte.Registry - to.QuobyteTenant = from.Quobyte.Tenant - to.QuobyteUser = from.Quobyte.User - to.QuobyteVolume = from.Quobyte.Volume - to.ReadOnly = &from.Quobyte.ReadOnly - } - if from.RBD != nil { - to.VolumeType = "rbd" - to.RBDCephMonitors = from.RBD.CephMonitors - to.RBDImage = from.RBD.RBDImage - to.RBDPool = from.RBD.RBDPool - to.RBDRadosUser = from.RBD.RadosUser - to.RBDKeyring = from.RBD.Keyring - to.FSType = &from.RBD.FSType - to.ReadOnly = &from.RBD.ReadOnly - if from.RBD.SecretRef != nil { - to.SecretName = from.RBD.SecretRef.Name - } - } - if from.ScaleIO != nil { - to.VolumeType = "scaleio" - to.ScaleIOGateway = from.ScaleIO.Gateway - to.ScaleIOSystem = from.ScaleIO.System - to.ScaleIOSSLEnabled = from.ScaleIO.SSLEnabled - to.ScaleIOProtectionDomain = from.ScaleIO.ProtectionDomain - to.ScaleIOStoragePool = from.ScaleIO.StoragePool - to.ScaleIOStorageMode = from.ScaleIO.StorageMode - to.ScaleIOVolumeName = from.ScaleIO.VolumeName - to.FSType = &from.ScaleIO.FSType - to.ReadOnly = &from.ScaleIO.ReadOnly - if from.ScaleIO.SecretRef != nil { - to.SecretName = from.ScaleIO.SecretRef.Name - } - } - if from.Secret != nil { - to.VolumeType = "secret" - to.SecretName = from.Secret.SecretName - to.SecretItems = from.Secret.Items - to.SecretDefaultMode = from.Secret.DefaultMode - to.SecretOptional = from.Secret.Optional - } - if from.StorageOS != nil { - to.VolumeType = "storage_os" - to.StorageOSVolumeName = from.StorageOS.VolumeName - to.StorageOSVolumeNamespace = from.StorageOS.VolumeNamespace - to.FSType = &from.StorageOS.FSType - to.ReadOnly = &from.StorageOS.ReadOnly - if from.StorageOS.SecretRef != nil { - to.SecretName = from.StorageOS.SecretRef.Name - } - } - if from.VsphereVolume != nil { - to.VolumeType = "vsphere_volume" - to.VsphereVolumeStoragePolicyID = from.VsphereVolume.StoragePolicyID - to.VsphereVolumeStoragePolicyName = from.VsphereVolume.StoragePolicyName - to.VsphereVolumeVolumePath = from.VsphereVolume.VolumePath - to.FSType = &from.VsphereVolume.FSType - } - return to -} diff --git a/infrastructure/kubequery/internal/k8s/common_test.go b/infrastructure/kubequery/internal/k8s/common_test.go deleted file mode 100644 index ca133d6469..0000000000 --- a/infrastructure/kubequery/internal/k8s/common_test.go +++ /dev/null @@ -1,428 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package k8s - -import ( - "testing" - - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" -) - -func TestGetCommonFields(t *testing.T) { - meta := metav1.ObjectMeta{ - Name: "n123", - GenerateName: "g123", - Namespace: "kube-system", - SelfLink: "/", - UID: types.UID("u123"), - ResourceVersion: "r123", - Generation: 1, - CreationTimestamp: metav1.Time{}, - DeletionGracePeriodSeconds: nil, - Labels: map[string]string{"a": "b"}, - ClusterName: "", - } - assert.Equal(t, GetCommonFields(meta), CommonFields{ - UID: meta.UID, - Name: meta.Name, - ClusterName: "cluster-name", - ClusterUID: types.UID("uid"), - CreationTimestamp: meta.CreationTimestamp, - Labels: meta.Labels, - Annotations: meta.Annotations, - }, "Common fields should match") -} - -func TestGetNamespaceCommonFields(t *testing.T) { - meta := metav1.ObjectMeta{ - Name: "n123", - GenerateName: "g123", - Namespace: "kube-system", - SelfLink: "/", - UID: types.UID("u123"), - ResourceVersion: "r123", - Generation: 1, - CreationTimestamp: metav1.Time{}, - DeletionGracePeriodSeconds: nil, - Annotations: map[string]string{"a": "b"}, - ClusterName: "", - } - assert.Equal(t, GetCommonNamespacedFields(meta), CommonNamespacedFields{ - UID: meta.UID, - Name: meta.Name, - Namespace: meta.Namespace, - ClusterName: "cluster-name", - ClusterUID: types.UID("uid"), - CreationTimestamp: meta.CreationTimestamp, - Labels: meta.Labels, - Annotations: meta.Annotations, - }, "Common namespace fields should match") -} - -func TestGetCommonPodFields(t *testing.T) { - i32 := int32(456) - i64 := int64(123) - b := bool(true) - s := string("s123") - pod := v1.PodSpec{ - RestartPolicy: v1.RestartPolicyAlways, - TerminationGracePeriodSeconds: &i64, - ActiveDeadlineSeconds: &i64, - DNSPolicy: v1.DNSClusterFirst, - ServiceAccountName: "s123", - AutomountServiceAccountToken: &b, - NodeSelector: make(map[string]string), - NodeName: "n123", - HostNetwork: true, - HostPID: true, - HostIPC: true, - ShareProcessNamespace: &b, - ImagePullSecrets: make([]v1.LocalObjectReference, 3), - Hostname: "h123", - Subdomain: "sub123", - Affinity: &v1.Affinity{NodeAffinity: &v1.NodeAffinity{RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{}}}, - SchedulerName: "sn123", - Tolerations: make([]v1.Toleration, 2), - HostAliases: make([]v1.HostAlias, 1), - PriorityClassName: "p123", - Priority: &i32, - ReadinessGates: []v1.PodReadinessGate{}, - RuntimeClassName: &s, - EnableServiceLinks: &b, - PreemptionPolicy: nil, - Overhead: make(v1.ResourceList), - TopologySpreadConstraints: make([]v1.TopologySpreadConstraint, 4), - SetHostnameAsFQDN: &b, - DNSConfig: &v1.PodDNSConfig{ - Nameservers: make([]string, 1), - Searches: make([]string, 2), - Options: make([]v1.PodDNSConfigOption, 3), - }, - SecurityContext: &v1.PodSecurityContext{ - RunAsUser: &i64, - RunAsGroup: &i64, - RunAsNonRoot: &b, - FSGroup: &i64, - FSGroupChangePolicy: (*v1.PodFSGroupChangePolicy)(&s), - Sysctls: []v1.Sysctl{{Name: "n1", Value: "v1"}}, - SELinuxOptions: &v1.SELinuxOptions{User: "u123", Role: "r123", Type: "t123", Level: "l123"}, - SupplementalGroups: make([]int64, 1), - SeccompProfile: &v1.SeccompProfile{Type: "t123"}, - }, - } - assert.Equal(t, GetCommonPodFields(pod), CommonPodFields{ - PodSecurityContextFields: PodSecurityContextFields{ - CommonSecurityContextFields: CommonSecurityContextFields{ - SELinuxOptionsFields: SELinuxOptionsFields{ - SELinuxOptionsUser: pod.SecurityContext.SELinuxOptions.User, - SELinuxOptionsRole: pod.SecurityContext.SELinuxOptions.Role, - SELinuxOptionsType: pod.SecurityContext.SELinuxOptions.Type, - SELinuxOptionsLevel: pod.SecurityContext.SELinuxOptions.Level, - }, - WindowsOptionsFields: WindowsOptionsFields{ - WindowsOptionsGMSACredentialSpecName: nil, - WindowsOptionsGMSACredentialSpec: nil, - WindowsOptionsRunAsUserName: nil, - }, - SeccompProfileFields: SeccompProfileFields{ - SeccompProfileType: pod.SecurityContext.SeccompProfile.Type, - SeccompProfileLocalhostProfile: pod.SecurityContext.SeccompProfile.LocalhostProfile, - }, - RunAsUser: pod.SecurityContext.RunAsUser, - RunAsGroup: pod.SecurityContext.RunAsGroup, - RunAsNonRoot: pod.SecurityContext.RunAsNonRoot, - }, - SupplementalGroups: pod.SecurityContext.SupplementalGroups, - FSGroup: pod.SecurityContext.FSGroup, - Sysctls: pod.SecurityContext.Sysctls, - FSGroupChangePolicy: pod.SecurityContext.FSGroupChangePolicy, - }, - DNSConfigFields: DNSConfigFields{ - DNSConfigNameservers: pod.DNSConfig.Nameservers, - DNSConfigSearches: pod.DNSConfig.Searches, - DNSConfigOptions: pod.DNSConfig.Options, - }, - AffinityFields: AffinityFields{ - NodeAffinity: pod.Affinity.NodeAffinity, - PodAffinity: pod.Affinity.PodAffinity, - PodAntiAffinity: pod.Affinity.PodAntiAffinity, - }, - NodeSelector: pod.NodeSelector, - RestartPolicy: pod.RestartPolicy, - TerminationGracePeriodSeconds: pod.TerminationGracePeriodSeconds, - ActiveDeadlineSeconds: pod.ActiveDeadlineSeconds, - DNSPolicy: pod.DNSPolicy, - ServiceAccountName: pod.ServiceAccountName, - AutomountServiceAccountToken: pod.AutomountServiceAccountToken, - NodeName: pod.NodeName, - HostNetwork: pod.HostNetwork, - HostPID: pod.HostPID, - HostIPC: pod.HostIPC, - ShareProcessNamespace: pod.ShareProcessNamespace, - ImagePullSecrets: pod.ImagePullSecrets, - Hostname: pod.Hostname, - Subdomain: pod.Subdomain, - SchedulerName: pod.SchedulerName, - Tolerations: pod.Tolerations, - HostAliases: pod.HostAliases, - PriorityClassName: pod.PriorityClassName, - Priority: pod.Priority, - ReadinessGates: pod.ReadinessGates, - RuntimeClassName: pod.RuntimeClassName, - EnableServiceLinks: pod.EnableServiceLinks, - PreemptionPolicy: pod.PreemptionPolicy, - Overhead: pod.Overhead, - TopologySpreadConstraints: pod.TopologySpreadConstraints, - SetHostnameAsFQDN: pod.SetHostnameAsFQDN, - }, "Common pod fields should match") -} - -func TestGetCommonContainerFields(t *testing.T) { - i64 := int64(456) - b := bool(true) - s := string("str123") - c := v1.Container{ - Name: "n123", - Image: "i123", - Command: []string{"c123"}, - Args: []string{"a1", "a2"}, - WorkingDir: "w123", - Ports: make([]v1.ContainerPort, 1), - EnvFrom: []v1.EnvFromSource{{Prefix: "p123"}}, - Env: []v1.EnvVar{{Name: "n1", Value: "v1"}}, - Resources: v1.ResourceRequirements{Limits: v1.ResourceList{}}, - VolumeMounts: make([]v1.VolumeMount, 2), - VolumeDevices: []v1.VolumeDevice{{Name: "vn1"}}, - LivenessProbe: &v1.Probe{Handler: v1.Handler{Exec: &v1.ExecAction{Command: []string{"curl"}}}}, - ReadinessProbe: &v1.Probe{}, - StartupProbe: nil, - Lifecycle: &v1.Lifecycle{PostStart: &v1.Handler{Exec: &v1.ExecAction{Command: []string{"curl"}}}}, - TerminationMessagePath: "t123", - TerminationMessagePolicy: v1.TerminationMessageFallbackToLogsOnError, - ImagePullPolicy: v1.PullAlways, - Stdin: true, - StdinOnce: false, - TTY: true, - SecurityContext: &v1.SecurityContext{ - Capabilities: &v1.Capabilities{Add: []v1.Capability{"a"}, Drop: []v1.Capability{"b", "c"}}, - Privileged: &b, - RunAsUser: &i64, - RunAsGroup: nil, - RunAsNonRoot: &b, - ReadOnlyRootFilesystem: nil, - AllowPrivilegeEscalation: &b, - ProcMount: nil, - SELinuxOptions: &v1.SELinuxOptions{ - User: "u123", - Role: "r123", - Type: "t123", - Level: "", - }, - SeccompProfile: &v1.SeccompProfile{ - Type: v1.SeccompProfileType("abc"), - LocalhostProfile: &s, - }, - WindowsOptions: nil, - }, - } - assert.Equal(t, GetCommonContainerFields(c), CommonContainerFields{ - SecurityContextFields: SecurityContextFields{ - CommonSecurityContextFields: CommonSecurityContextFields{ - SELinuxOptionsFields: SELinuxOptionsFields{ - SELinuxOptionsUser: c.SecurityContext.SELinuxOptions.User, - SELinuxOptionsRole: c.SecurityContext.SELinuxOptions.Role, - SELinuxOptionsType: c.SecurityContext.SELinuxOptions.Type, - SELinuxOptionsLevel: c.SecurityContext.SELinuxOptions.Level, - }, - WindowsOptionsFields: WindowsOptionsFields{ - WindowsOptionsGMSACredentialSpecName: nil, - WindowsOptionsGMSACredentialSpec: nil, - WindowsOptionsRunAsUserName: nil, - }, - SeccompProfileFields: SeccompProfileFields{ - SeccompProfileType: c.SecurityContext.SeccompProfile.Type, - SeccompProfileLocalhostProfile: c.SecurityContext.SeccompProfile.LocalhostProfile, - }, - RunAsUser: c.SecurityContext.RunAsUser, - RunAsGroup: c.SecurityContext.RunAsGroup, - RunAsNonRoot: c.SecurityContext.RunAsNonRoot, - }, - CapabilitiesAdd: c.SecurityContext.Capabilities.Add, - CapabilitiesDrop: c.SecurityContext.Capabilities.Drop, - Privileged: c.SecurityContext.Privileged, - ReadOnlyRootFilesystem: c.SecurityContext.ReadOnlyRootFilesystem, - AllowPrivilegeEscalation: c.SecurityContext.AllowPrivilegeEscalation, - ProcMount: c.SecurityContext.ProcMount, - }, - TargetContainerName: "", - Image: c.Image, - Command: c.Command, - Args: c.Args, - WorkingDir: c.WorkingDir, - Ports: c.Ports, - EnvFrom: c.EnvFrom, - Env: c.Env, - ResourceLimits: c.Resources.Limits, - ResourceRequests: c.Resources.Requests, - VolumeMounts: c.VolumeMounts, - VolumeDevices: c.VolumeDevices, - LivenessProbe: c.LivenessProbe, - ReadinessProbe: c.ReadinessProbe, - StartupProbe: c.StartupProbe, - Lifecycle: c.Lifecycle, - TerminationMessagePath: c.TerminationMessagePath, - TerminationMessagePolicy: c.TerminationMessagePolicy, - ImagePullPolicy: c.ImagePullPolicy, - Stdin: c.Stdin, - StdinOnce: c.StdinOnce, - TTY: c.TTY, - }, "Common container fields should match") -} - -func TestGetCommonEphemeralContainerFields(t *testing.T) { - i64 := int64(456) - b := bool(true) - s := string("str123") - c := v1.EphemeralContainer{ - TargetContainerName: "t123", - EphemeralContainerCommon: v1.EphemeralContainerCommon{ - Name: "n123", - Image: "i123", - Command: []string{"c123"}, - Args: []string{"a1", "a2"}, - WorkingDir: "w123", - Ports: make([]v1.ContainerPort, 1), - EnvFrom: []v1.EnvFromSource{{Prefix: "p123"}}, - Env: []v1.EnvVar{{Name: "n1", Value: "v1"}}, - Resources: v1.ResourceRequirements{Limits: v1.ResourceList{}}, - VolumeMounts: make([]v1.VolumeMount, 2), - VolumeDevices: []v1.VolumeDevice{{Name: "vn1"}}, - LivenessProbe: &v1.Probe{Handler: v1.Handler{Exec: &v1.ExecAction{Command: []string{"curl"}}}}, - ReadinessProbe: &v1.Probe{}, - StartupProbe: nil, - Lifecycle: &v1.Lifecycle{PostStart: &v1.Handler{Exec: &v1.ExecAction{Command: []string{"curl"}}}}, - TerminationMessagePath: "t123", - TerminationMessagePolicy: v1.TerminationMessageFallbackToLogsOnError, - ImagePullPolicy: v1.PullAlways, - Stdin: true, - StdinOnce: false, - TTY: true, - SecurityContext: &v1.SecurityContext{ - Capabilities: &v1.Capabilities{Add: []v1.Capability{"a"}, Drop: []v1.Capability{"b", "c"}}, - Privileged: &b, - RunAsUser: &i64, - RunAsGroup: nil, - RunAsNonRoot: &b, - ReadOnlyRootFilesystem: nil, - AllowPrivilegeEscalation: &b, - ProcMount: nil, - SELinuxOptions: &v1.SELinuxOptions{ - User: "u123", - Role: "r123", - Type: "t123", - Level: "", - }, - SeccompProfile: &v1.SeccompProfile{ - Type: v1.SeccompProfileType("abc"), - LocalhostProfile: &s, - }, - WindowsOptions: nil, - }, - }, - } - assert.Equal(t, GetCommonEphemeralContainerFields(c), CommonContainerFields{ - SecurityContextFields: SecurityContextFields{ - CommonSecurityContextFields: CommonSecurityContextFields{ - SELinuxOptionsFields: SELinuxOptionsFields{ - SELinuxOptionsUser: c.SecurityContext.SELinuxOptions.User, - SELinuxOptionsRole: c.SecurityContext.SELinuxOptions.Role, - SELinuxOptionsType: c.SecurityContext.SELinuxOptions.Type, - SELinuxOptionsLevel: c.SecurityContext.SELinuxOptions.Level, - }, - WindowsOptionsFields: WindowsOptionsFields{ - WindowsOptionsGMSACredentialSpecName: nil, - WindowsOptionsGMSACredentialSpec: nil, - WindowsOptionsRunAsUserName: nil, - }, - SeccompProfileFields: SeccompProfileFields{ - SeccompProfileType: c.SecurityContext.SeccompProfile.Type, - SeccompProfileLocalhostProfile: c.SecurityContext.SeccompProfile.LocalhostProfile, - }, - RunAsUser: c.SecurityContext.RunAsUser, - RunAsGroup: c.SecurityContext.RunAsGroup, - RunAsNonRoot: c.SecurityContext.RunAsNonRoot, - }, - CapabilitiesAdd: c.SecurityContext.Capabilities.Add, - CapabilitiesDrop: c.SecurityContext.Capabilities.Drop, - Privileged: c.SecurityContext.Privileged, - ReadOnlyRootFilesystem: c.SecurityContext.ReadOnlyRootFilesystem, - AllowPrivilegeEscalation: c.SecurityContext.AllowPrivilegeEscalation, - ProcMount: c.SecurityContext.ProcMount, - }, - TargetContainerName: c.TargetContainerName, - Image: c.Image, - Command: c.Command, - Args: c.Args, - WorkingDir: c.WorkingDir, - Ports: c.Ports, - EnvFrom: c.EnvFrom, - Env: c.Env, - ResourceLimits: c.Resources.Limits, - ResourceRequests: c.Resources.Requests, - VolumeMounts: c.VolumeMounts, - VolumeDevices: c.VolumeDevices, - LivenessProbe: c.LivenessProbe, - ReadinessProbe: c.ReadinessProbe, - StartupProbe: c.StartupProbe, - Lifecycle: c.Lifecycle, - TerminationMessagePath: c.TerminationMessagePath, - TerminationMessagePolicy: c.TerminationMessagePolicy, - ImagePullPolicy: c.ImagePullPolicy, - Stdin: c.Stdin, - StdinOnce: c.StdinOnce, - TTY: c.TTY, - }, "Common ephemeral container fields should match") -} - -func TestGetCommonVolumeFields(t *testing.T) { - v := v1.Volume{ - VolumeSource: v1.VolumeSource{ - HostPath: &v1.HostPathVolumeSource{ - Path: "p123", - Type: nil, - }, - }, - } - assert.Equal(t, GetCommonVolumeFields(v), CommonVolumeFields{ - VolumeType: "host_path", - HostPathPath: v.HostPath.Path, - HostPathType: v.HostPath.Type, - }, "Common volume HostPath fields should match") - - v = v1.Volume{ - VolumeSource: v1.VolumeSource{ - GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{ - PDName: "p123", - FSType: "gce", - Partition: 123, - }, - }, - } - assert.Equal(t, GetCommonVolumeFields(v), CommonVolumeFields{ - VolumeType: "gce_persistent_disk", - FSType: &v.GCEPersistentDisk.FSType, - ReadOnly: &v.GCEPersistentDisk.ReadOnly, - GCEPersistentDiskPDName: v.GCEPersistentDisk.PDName, - GCEPersistentDiskPartition: v.GCEPersistentDisk.Partition, - }, "Common volume GCE fields should match") -} diff --git a/infrastructure/kubequery/internal/k8s/core/component_status.go b/infrastructure/kubequery/internal/k8s/core/component_status.go deleted file mode 100644 index a129329070..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/component_status.go +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" -) - -type componentStatus struct { - ClusterName string - ClusterUID types.UID - Name string - v1.ComponentCondition -} - -// ComponentStatusColumns returns kubernetes component status fields as Osquery table columns. -func ComponentStatusColumns() []table.ColumnDefinition { - return k8s.GetSchema(&componentStatus{}) -} - -// ComponentStatusesGenerate generates the kubernetes config maps as Osquery table data. -func ComponentStatusesGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - css, err := k8s.GetClient().CoreV1().ComponentStatuses().List(ctx, options) - if err != nil { - return nil, err - } - - for _, cs := range css.Items { - for _, cc := range cs.Conditions { - item := &componentStatus{ - ClusterName: k8s.GetClusterName(), - ClusterUID: k8s.GetClusterUID(), - Name: cs.ObjectMeta.Name, - ComponentCondition: cc, - } - results = append(results, k8s.ToMap(item)) - } - } - - if css.Continue == "" { - break - } - options.Continue = css.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/core/component_status_test.go b/infrastructure/kubequery/internal/k8s/core/component_status_test.go deleted file mode 100644 index 41c5d29a83..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/component_status_test.go +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestComponentStatusesGenerate(t *testing.T) { - css, err := ComponentStatusesGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "cluster_uid": "d7fd8e77-93de-4742-9037-5db9a01e966a", - "name": "controller-manager", - "message": "ok", - "status": "True", - "type": "Healthy", - }, - { - "cluster_uid": "d7fd8e77-93de-4742-9037-5db9a01e966a", - "name": "etcd-0", - "message": "{\"health\":\"true\"}", - "status": "True", - "type": "Healthy", - }, - { - "cluster_uid": "d7fd8e77-93de-4742-9037-5db9a01e966a", - "name": "scheduler", - "message": "ok", - "status": "True", - "type": "Healthy", - }, - }, css) -} diff --git a/infrastructure/kubequery/internal/k8s/core/config_map.go b/infrastructure/kubequery/internal/k8s/core/config_map.go deleted file mode 100644 index 2256eb7d7e..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/config_map.go +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type configmap struct { - k8s.CommonNamespacedFields - Immutable *bool -} - -// ConfigMapColumns returns kubernetes config map fields as Osquery table columns. -func ConfigMapColumns() []table.ColumnDefinition { - return k8s.GetSchema(&configmap{}) -} - -// ConfigMapsGenerate generates the kubernetes config maps as Osquery table data. -func ConfigMapsGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - configmaps, err := k8s.GetClient().CoreV1().ConfigMaps(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, c := range configmaps.Items { - item := &configmap{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(c.ObjectMeta), - Immutable: c.Immutable, - } - results = append(results, k8s.ToMap(item)) - } - - if configmaps.Continue == "" { - break - } - options.Continue = configmaps.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/core/config_map_test.go b/infrastructure/kubequery/internal/k8s/core/config_map_test.go deleted file mode 100644 index 1140ad96c0..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/config_map_test.go +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestConfigMapsGenerate(t *testing.T) { - cms, err := ConfigMapsGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "cluster_uid": "d7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611191331", - "name": "jaeger-operator-lock", - "namespace": "default", - "uid": "eec6944c-5c13-4e30-8326-1a82e1962e4d", - }, - }, cms) -} diff --git a/infrastructure/kubequery/internal/k8s/core/endpoint_subset.go b/infrastructure/kubequery/internal/k8s/core/endpoint_subset.go deleted file mode 100644 index 6c9c7ebc8b..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/endpoint_subset.go +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type endpointSubset struct { - k8s.CommonNamespacedFields - v1.EndpointSubset -} - -// EndpointSubsetColumns returns kubernetes endpoint subset fields as Osquery table columns. -func EndpointSubsetColumns() []table.ColumnDefinition { - return k8s.GetSchema(&endpointSubset{}) -} - -// EndpointSubsetsGenerate generates the kubernetes endpoint subsets as Osquery table data. -func EndpointSubsetsGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - endpoints, err := k8s.GetClient().CoreV1().Endpoints(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, e := range endpoints.Items { - for _, s := range e.Subsets { - item := &endpointSubset{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(e.ObjectMeta), - EndpointSubset: s, - } - results = append(results, k8s.ToMap(item)) - } - } - - if endpoints.Continue == "" { - break - } - options.Continue = endpoints.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/core/endpoint_subset_test.go b/infrastructure/kubequery/internal/k8s/core/endpoint_subset_test.go deleted file mode 100644 index 22a63106a0..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/endpoint_subset_test.go +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestEndpointSubsetsGenerate(t *testing.T) { - ess, err := EndpointSubsetsGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "addresses": "[{\"ip\":\"10.1.26.50\",\"nodeName\":\"seshu\",\"targetRef\":{\"kind\":\"Pod\",\"namespace\":\"default\",\"name\":\"jaeger-operator-5db4f9d996-pm7ld\",\"uid\":\"2271363b-ffc9-4f00-984c-e0a125ee2d7a\",\"resourceVersion\":\"451808\"}}]", - "annotations": "{\"endpoints.kubernetes.io/last-change-trigger-time\":\"2021-01-20T20:08:52-05:00\"}", - "cluster_uid": "d7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611191332", - "labels": "{\"name\":\"jaeger-operator\"}", - "name": "jaeger-operator", - "namespace": "default", - "ports": "[{\"name\":\"metrics\",\"port\":8383,\"protocol\":\"TCP\"}]", - "uid": "013741da-d7a5-4a2d-8f4b-792ac6a40dd3", - }, - }, ess) -} diff --git a/infrastructure/kubequery/internal/k8s/core/init_test.go b/infrastructure/kubequery/internal/k8s/core/init_test.go deleted file mode 100644 index 964cb98688..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/init_test.go +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "encoding/json" - "io/ioutil" - "path/filepath" - - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/fake" -) - -func loadTestResource(name string, v interface{}) { - path := filepath.Join("testdata", name) - data, err := ioutil.ReadFile(path) - if err != nil { - panic(err) - } - - err = json.Unmarshal(data, v) - if err != nil { - panic(err) - } -} - -func init() { - lr := &v1.LimitRange{ - ObjectMeta: metav1.ObjectMeta{ - Name: "lr1", - Namespace: "n123", - UID: types.UID("1234"), - Labels: map[string]string{"a": "b"}, - }, - Spec: v1.LimitRangeSpec{ - Limits: []v1.LimitRangeItem{ - { - Type: v1.LimitTypeContainer, - Max: v1.ResourceList{v1.ResourceCPU: resource.MustParse("0")}, - Min: v1.ResourceList{v1.ResourceCPU: resource.MustParse("4")}, - Default: v1.ResourceList{v1.ResourceCPU: resource.MustParse("3")}, - DefaultRequest: v1.ResourceList{v1.ResourceCPU: resource.MustParse("2")}, - MaxLimitRequestRatio: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1")}, - }, - }, - }, - } - - csl := &v1.ComponentStatusList{} - loadTestResource("component_status_test.json", csl) - cm := &v1.ConfigMap{} - loadTestResource("config_map_test.json", cm) - ep := &v1.Endpoints{} - loadTestResource("endpoint_subset_test.json", ep) - ns := &v1.NamespaceList{} - loadTestResource("namespaces_test.json", ns) - node := &v1.Node{} - loadTestResource("node_test.json", node) - pod := &v1.Pod{} - loadTestResource("pod_test.json", pod) - secret := &v1.Secret{} - loadTestResource("secret_test.json", secret) - sa := &v1.ServiceAccount{} - loadTestResource("service_account_test.json", sa) - services := &v1.Service{} - loadTestResource("services_test.json", services) - - k8s.SetClient(fake.NewSimpleClientset(lr, csl, cm, ep, ns, node, pod, secret, sa, services), - types.UID("d7fd8e77-93de-4742-9037-5db9a01e966a"), "") -} diff --git a/infrastructure/kubequery/internal/k8s/core/limit_range.go b/infrastructure/kubequery/internal/k8s/core/limit_range.go deleted file mode 100644 index c88a642727..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/limit_range.go +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type limitRange struct { - k8s.CommonNamespacedFields - v1.LimitRangeItem -} - -// LimitRangeColumns returns kubernetes limit range fields as Osquery table columns. -func LimitRangeColumns() []table.ColumnDefinition { - return k8s.GetSchema(&limitRange{}) -} - -// LimitRangesGenerate generates the kubernetes limit ranges as Osquery table data. -func LimitRangesGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - ranges, err := k8s.GetClient().CoreV1().LimitRanges(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, r := range ranges.Items { - for _, i := range r.Spec.Limits { - item := &limitRange{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(r.ObjectMeta), - LimitRangeItem: i, - } - results = append(results, k8s.ToMap(item)) - } - } - - if ranges.Continue == "" { - break - } - options.Continue = ranges.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/core/limit_range_test.go b/infrastructure/kubequery/internal/k8s/core/limit_range_test.go deleted file mode 100644 index 0cbb7ca921..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/limit_range_test.go +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestLimitRangesGenerate(t *testing.T) { - js, err := LimitRangesGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "cluster_uid": "d7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "0", - "default": "{\"cpu\":\"3\"}", - "default_request": "{\"cpu\":\"2\"}", - "labels": "{\"a\":\"b\"}", - "max": "{\"cpu\":\"0\"}", - "max_limit_request_ratio": "{\"cpu\":\"1\"}", - "min": "{\"cpu\":\"4\"}", - "name": "lr1", - "namespace": "n123", - "type": "Container", - "uid": "1234", - }, - }, js) -} diff --git a/infrastructure/kubequery/internal/k8s/core/namespace.go b/infrastructure/kubequery/internal/k8s/core/namespace.go deleted file mode 100644 index 136a085329..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/namespace.go +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type namespace struct { - k8s.CommonFields - v1.NamespaceStatus -} - -// NamespaceColumns returns kubernetes namespace fields as Osquery table columns. -func NamespaceColumns() []table.ColumnDefinition { - return k8s.GetSchema(&namespace{}) -} - -// NamespacesGenerate generates the kubernetes namespaces as Osquery table data. -func NamespacesGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - namespaces, err := k8s.GetClient().CoreV1().Namespaces().List(ctx, options) - if err != nil { - return nil, err - } - - for _, n := range namespaces.Items { - item := &namespace{ - CommonFields: k8s.GetCommonFields(n.ObjectMeta), - NamespaceStatus: n.Status, - } - results = append(results, k8s.ToMap(item)) - } - - if namespaces.Continue == "" { - break - } - options.Continue = namespaces.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/core/namespace_test.go b/infrastructure/kubequery/internal/k8s/core/namespace_test.go deleted file mode 100644 index 20a83dba19..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/namespace_test.go +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestNamespacesGenerate(t *testing.T) { - nss, err := NamespacesGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "cluster_uid": "d7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1610476216", - "name": "default", - "phase": "Active", - "uid": "7b50dc9c-6149-4cac-a0d0-52bf0fa5356d", - }, - { - "annotations": "{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"v1\\\",\\\"kind\\\":\\\"Namespace\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"name\\\":\\\"ingress\\\"}}\\n\"}", - "cluster_uid": "d7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611191047", - "name": "ingress", - "phase": "Active", - "uid": "7653c4b9-3df2-493e-ae28-5e3a777f7e76", - }, - { - "annotations": "{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"v1\\\",\\\"kind\\\":\\\"Namespace\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"labels\\\":{\\\"istio-injection\\\":\\\"disabled\\\"},\\\"name\\\":\\\"istio-system\\\"}}\\n\"}", - "cluster_uid": "d7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611191143", - "labels": "{\"istio-injection\":\"disabled\"}", - "name": "istio-system", - "phase": "Active", - "uid": "7f931f07-f8d0-4198-bf16-e459914e1866", - }, - { - "cluster_uid": "d7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1610476215", - "name": "kube-node-lease", - "phase": "Active", - "uid": "a8f303fd-0074-475f-935a-122cf8b6d1ad", - }, - { - "cluster_uid": "d7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1610476215", - "name": "kube-public", - "phase": "Active", - "uid": "6c719dfa-3de8-477b-a650-8bf9e2f12ee0", - }, - { - "cluster_uid": "d7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1610476215", - "name": "kube-system", - "phase": "Active", - "uid": "ebca5546-b939-4765-bf3d-869ac644ea0f", - }, - { - "annotations": "{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"v1\\\",\\\"kind\\\":\\\"Namespace\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"name\\\":\\\"monitoring\\\"}}\\n\"}", - "cluster_uid": "d7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611191449", - "name": "monitoring", - "phase": "Active", - "uid": "afb98a87-39bb-4c8f-b0dd-8ea3683ba745", - }, - }, nss) -} diff --git a/infrastructure/kubequery/internal/k8s/core/node.go b/infrastructure/kubequery/internal/k8s/core/node.go deleted file mode 100644 index 2d8f4eb042..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/node.go +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type node struct { - k8s.CommonFields - v1.NodeSpec - v1.NodeStatus -} - -// NodeColumns returns kubernetes node fields as Osquery table columns. -func NodeColumns() []table.ColumnDefinition { - return k8s.GetSchema(&node{}) -} - -// NodesGenerate generates the kubernetes nodes as Osquery table data. -func NodesGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - nodes, err := k8s.GetClient().CoreV1().Nodes().List(ctx, options) - if err != nil { - return nil, err - } - - for _, n := range nodes.Items { - item := &node{ - CommonFields: k8s.GetCommonFields(n.ObjectMeta), - NodeSpec: n.Spec, - NodeStatus: n.Status, - } - results = append(results, k8s.ToMap(item)) - } - - if nodes.Continue == "" { - break - } - options.Continue = nodes.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/core/node_test.go b/infrastructure/kubequery/internal/k8s/core/node_test.go deleted file mode 100644 index 8945d718f3..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/node_test.go +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestNodesGenerate(t *testing.T) { - ns, err := NodesGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "addresses": "[{\"type\":\"InternalIP\",\"address\":\"192.168.0.28\"},{\"type\":\"Hostname\",\"address\":\"seshu\"}]", - "allocatable": "{\"cpu\":\"12\",\"ephemeral-storage\":\"958151776Ki\",\"hugepages-1Gi\":\"0\",\"hugepages-2Mi\":\"0\",\"memory\":\"32411744Ki\",\"pods\":\"110\"}", - "annotations": "{\"node.alpha.kubernetes.io/ttl\":\"0\",\"projectcalico.org/IPv4Address\":\"192.168.192.1/20\",\"projectcalico.org/IPv4VXLANTunnelAddr\":\"10.1.26.0\",\"volumes.kubernetes.io/controller-managed-attach-detach\":\"true\"}", - "capacity": "{\"cpu\":\"12\",\"ephemeral-storage\":\"959200352Ki\",\"hugepages-1Gi\":\"0\",\"hugepages-2Mi\":\"0\",\"memory\":\"32514144Ki\",\"pods\":\"110\"}", - "cluster_uid": "d7fd8e77-93de-4742-9037-5db9a01e966a", - "conditions": "[{\"type\":\"NetworkUnavailable\",\"status\":\"False\",\"lastHeartbeatTime\":\"2021-01-20T16:31:53Z\",\"lastTransitionTime\":\"2021-01-20T16:31:53Z\",\"reason\":\"CalicoIsUp\",\"message\":\"Calico is running on this node\"},{\"type\":\"MemoryPressure\",\"status\":\"False\",\"lastHeartbeatTime\":\"2021-01-21T19:24:08Z\",\"lastTransitionTime\":\"2021-01-12T18:30:24Z\",\"reason\":\"KubeletHasSufficientMemory\",\"message\":\"kubelet has sufficient memory available\"},{\"type\":\"DiskPressure\",\"status\":\"False\",\"lastHeartbeatTime\":\"2021-01-21T19:24:08Z\",\"lastTransitionTime\":\"2021-01-12T18:30:24Z\",\"reason\":\"KubeletHasNoDiskPressure\",\"message\":\"kubelet has no disk pressure\"},{\"type\":\"PIDPressure\",\"status\":\"False\",\"lastHeartbeatTime\":\"2021-01-21T19:24:08Z\",\"lastTransitionTime\":\"2021-01-12T18:30:24Z\",\"reason\":\"KubeletHasSufficientPID\",\"message\":\"kubelet has sufficient PID available\"},{\"type\":\"Ready\",\"status\":\"True\",\"lastHeartbeatTime\":\"2021-01-21T19:24:08Z\",\"lastTransitionTime\":\"2021-01-21T01:12:31Z\",\"reason\":\"KubeletReady\",\"message\":\"kubelet is posting ready status. AppArmor enabled\"}]", - "creation_timestamp": "1610476224", - "daemon_endpoints": "{\"kubeletEndpoint\":{\"Port\":10250}}", - "images": "[{\"names\":[\"docker.io/library/kubequery:latest\"],\"sizeBytes\":202523444},{\"names\":null,\"sizeBytes\":174592418},{\"names\":[\"k8s.gcr.io/ingress-nginx/controller@sha256:fc4979d8b8443a831c9789b5155cded454cb7de737a8b727bc2ba0106d2eae8b\",\"k8s.gcr.io/ingress-nginx/controller:v0.35.0\"],\"sizeBytes\":111763794},{\"names\":[\"docker.io/istio/proxyv2@sha256:3ad9ee2b43b299e5e6d97aaea5ed47dbf3da9293733607d9b52f358313e852ae\",\"docker.io/istio/proxyv2:1.5.1\"],\"sizeBytes\":106728139},{\"names\":[\"docker.io/jaegertracing/jaeger-operator@sha256:5a3198179f7972028a29dd7fbf71ac7a21e0dbf46c85e8cc2c37e3b6a5ee26a4\",\"docker.io/jaegertracing/jaeger-operator:1.14.0\"],\"sizeBytes\":99946252},{\"names\":[\"docker.io/calico/node@sha256:cb9dea7b86471c71925ae318f7c60af72d9ddf1dab0fe2029832a671b83bba6a\",\"docker.io/calico/node:v3.13.2\"],\"sizeBytes\":88917441},{\"names\":[\"docker.io/istio/mixer@sha256:92940f04e9aa20a41e330eb8a00a0b8ee7a3f4029dcdadfca4a5d009774474b2\",\"docker.io/istio/mixer:1.5.1\"],\"sizeBytes\":86988340},{\"names\":[\"docker.io/istio/pilot@sha256:818aecc1c73c53af9091ac1d4f500d9d7cec6d135d372d03cffab1addaff4ec0\",\"docker.io/istio/pilot:1.5.1\"],\"sizeBytes\":85950908},{\"names\":[\"docker.io/uptycs/kubequery@sha256:96b6c15753941f58e97fc6f80ee7ec06ce63d48a14b53ee0cc1dd10dc3585e7d\",\"docker.io/uptycs/kubequery:latest\"],\"sizeBytes\":82661645},{\"names\":[\"docker.io/istio/galley@sha256:d69acf890e5c82cb0c000fc15c540777ee566ae225762d85f157f69c9665338c\",\"docker.io/istio/galley:1.5.1\"],\"sizeBytes\":82020368},{\"names\":[\"docker.io/istio/sidecar_injector@sha256:cf334211f192378e7fcb66baeeb43412e483e34d739e93711d0a61568dd00462\",\"docker.io/istio/sidecar_injector:1.5.1\"],\"sizeBytes\":77988679},{\"names\":[\"docker.io/calico/cni@sha256:bbf7e3ac3f80d0a356a6c27b095bd313d1106f8ed84f85850816ed79295843c1\",\"docker.io/calico/cni:v3.13.2\"],\"sizeBytes\":76710099},{\"names\":[\"docker.io/istio/kubectl@sha256:83ea57063cf3344a2462c5bbaa5b125810f2e8ef7283d2ba3bfd9393e624b80f\",\"docker.io/istio/kubectl:1.5.1\"],\"sizeBytes\":76608582},{\"names\":[\"docker.io/grafana/grafana@sha256:bd55ea2bad17f5016431734b42fdfc202ebdc7d08b6c4ad35ebb03d06efdff69\",\"docker.io/grafana/grafana:6.4.3\"],\"sizeBytes\":76169588},{\"names\":[\"quay.io/kiali/kiali:v1.9\"],\"sizeBytes\":75529164},{\"names\":[\"docker.io/istio/citadel@sha256:92b985411af9844b75c5fc9c39c33fc27ef549c31b5221358f334062aadb86ec\",\"docker.io/istio/citadel:1.5.1\"],\"sizeBytes\":72604439},{\"names\":[\"docker.io/kubernetesui/dashboard@sha256:06868692fb9a7f2ede1a06de1b7b32afabc40ec739c1181d83b5ed3eb147ec6e\",\"docker.io/kubernetesui/dashboard:v2.0.0\"],\"sizeBytes\":66209190},{\"names\":[\"docker.io/grafana/grafana@sha256:89304bc2335f4976618548d7b93d165ed67369d3a051d2f627fc4e0aa3d0aff1\",\"docker.io/grafana/grafana:7.1.0\"],\"sizeBytes\":59911815},{\"names\":[\"quay.io/prometheus/prometheus@sha256:d4ba4dd1a9ebb90916d0bfed3c204adcb118ed24546bf8dd2e6b30fc0fd2009e\",\"quay.io/prometheus/prometheus:v2.20.0\"],\"sizeBytes\":59435495},{\"names\":[\"docker.io/prom/prometheus@sha256:cd93b8711bb92eb9c437d74217311519e0a93bc55779aa664325dc83cd13cb32\",\"docker.io/prom/prometheus:v2.12.0\"],\"sizeBytes\":54819393},{\"names\":[\"docker.io/calico/pod2daemon-flexvol@sha256:0022da5a9a89512f8a117f12d2088b3f1f8f22c094ee15aae24d58085f2c186a\",\"docker.io/calico/pod2daemon-flexvol:v3.13.2\"],\"sizeBytes\":37530211},{\"names\":[\"quay.io/prometheus/alertmanager@sha256:24a5204b418e8fa0214cfb628486749003b039c279c56b5bddb5b10cd100d926\",\"quay.io/prometheus/alertmanager:v0.21.0\"],\"sizeBytes\":27097956},{\"names\":[\"docker.io/jaegertracing/all-in-one@sha256:738442983b772a5d413c8a2c44a5563956adaff224e5b38f52a959124dafc119\",\"docker.io/jaegertracing/all-in-one:1.16\"],\"sizeBytes\":23571671},{\"names\":[\"docker.io/directxman12/k8s-prometheus-adapter@sha256:44558d3ae98467e44fee72ebc3948ce59630996013a51d49cf925682a7b87c18\",\"docker.io/directxman12/k8s-prometheus-adapter:v0.7.0\"],\"sizeBytes\":23407634},{\"names\":[\"docker.io/jaegertracing/all-in-one@sha256:021aefafecbb5559078206996f1f4e8fc5907debab047f4fcc5c837689a66cfa\",\"docker.io/jaegertracing/all-in-one:1.14.0\"],\"sizeBytes\":23208939},{\"names\":[\"docker.io/calico/kube-controllers@sha256:a635173cbe9deb33deba9baadffd933f61c63fbdadc0e3fa60ff1a14198c1da8\",\"docker.io/calico/kube-controllers:v3.13.2\"],\"sizeBytes\":23132265},{\"names\":[\"quay.io/brancz/kube-rbac-proxy@sha256:05e15e1164fd7ac85f5702b3f87ef548f4e00de3a79e6c4a6a34c92035497a9a\",\"quay.io/brancz/kube-rbac-proxy:v0.8.0\"],\"sizeBytes\":19991394},{\"names\":[\"docker.io/kubernetesui/metrics-scraper@sha256:555981a24f184420f3be0c79d4efb6c948a85cfce84034f85a563f4151a81cbf\",\"docker.io/kubernetesui/metrics-scraper:v1.0.4\"],\"sizeBytes\":16020077},{\"names\":[\"docker.io/coredns/coredns@sha256:41bee6992c2ed0f4628fcef75751048927bcd6b1cee89c79f6acb63ca5474d5a\",\"docker.io/coredns/coredns:1.6.6\"],\"sizeBytes\":12932169},{\"names\":[\"quay.io/coreos/prometheus-operator@sha256:a54e806fb27d2fb0251da4f3b2a3bb5320759af63a54a755788304775f2384a7\",\"quay.io/coreos/prometheus-operator:v0.40.0\"],\"sizeBytes\":12496211},{\"names\":[\"quay.io/prometheus/node-exporter@sha256:a2f29256e53cc3e0b64d7a472512600b2e9410347d53cdc85b49f659c17e02ee\",\"quay.io/prometheus/node-exporter:v0.18.1\"],\"sizeBytes\":11122661},{\"names\":[\"gcr.io/k8s-staging-kube-state-metrics/kube-state-metrics@sha256:9718f2e7999e75f4993e312fccada801c0eb98eaba73db072f0f806d67fcc238\",\"gcr.io/k8s-staging-kube-state-metrics/kube-state-metrics:v1.9.7\"],\"sizeBytes\":10782953},{\"names\":[\"k8s.gcr.io/metrics-server-amd64@sha256:c9c4e95068b51d6b33a9dccc61875df07dc650abbf4ac1a19d58b4628f89288b\",\"k8s.gcr.io/metrics-server-amd64:v0.3.6\"],\"sizeBytes\":10542830},{\"names\":[\"docker.io/cdkbot/hostpath-provisioner-amd64@sha256:339f78eabc68ffb1656d584e41f121cb4d2b667565428c8dde836caf5b8a0228\",\"docker.io/cdkbot/hostpath-provisioner-amd64:1.0.0\"],\"sizeBytes\":9745308},{\"names\":[\"quay.io/coreos/prometheus-config-reloader@sha256:c679a143b24b7731ad1577a9865aa3805426cbf1b25e30807b951dff68466ffd\",\"quay.io/coreos/prometheus-config-reloader:v0.40.0\"],\"sizeBytes\":4254190},{\"names\":[\"docker.io/jimmidyson/configmap-reload@sha256:d107c7a235c266273b1c3502a391fec374430e5625539403d0de797fa9c556a2\",\"docker.io/jimmidyson/configmap-reload:v0.3.0\"],\"sizeBytes\":4063371},{\"names\":[\"k8s.gcr.io/pause@sha256:f78411e19d84a252e53bff71a4407a5686c46983a2c2eeed83929b888179acea\",\"k8s.gcr.io/pause:3.1\"],\"sizeBytes\":317164}]", - "labels": "{\"beta.kubernetes.io/arch\":\"amd64\",\"beta.kubernetes.io/os\":\"linux\",\"kubernetes.io/arch\":\"amd64\",\"kubernetes.io/hostname\":\"seshu\",\"kubernetes.io/os\":\"linux\",\"microk8s.io/cluster\":\"true\"}", - "name": "seshu", - "node_info": "{\"machineID\":\"c73ef4a4ef2a4ec19a75719b63db3bb7\",\"systemUUID\":\"4c4c4544-0044-3510-8058-c6c04f5a5932\",\"bootID\":\"0b51cb6f-120b-4557-b74a-e53a5f4f00d5\",\"kernelVersion\":\"5.4.0-60-generic\",\"osImage\":\"Ubuntu 20.04.1 LTS\",\"containerRuntimeVersion\":\"containerd://1.3.7\",\"kubeletVersion\":\"v1.20.1-34+e7db93d188d0d1\",\"kubeProxyVersion\":\"v1.20.1-34+e7db93d188d0d1\",\"operatingSystem\":\"linux\",\"architecture\":\"amd64\"}", - "uid": "d0d45111-421d-4d4f-89c9-3e75ca2dc06c", - "unschedulable": "0", - }, - }, ns) -} diff --git a/infrastructure/kubequery/internal/k8s/core/persistent_volume.go b/infrastructure/kubequery/internal/k8s/core/persistent_volume.go deleted file mode 100644 index 1c00743c67..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/persistent_volume.go +++ /dev/null @@ -1,330 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type persistentVolume struct { - k8s.CommonFields - - Capacity v1.ResourceList - AccessModes []v1.PersistentVolumeAccessMode - ClaimRef *v1.ObjectReference - PersistentVolumeReclaimPolicy v1.PersistentVolumeReclaimPolicy - StorageClassName string - MountOptions []string - VolumeMode *v1.PersistentVolumeMode - NodeAffinity *v1.VolumeNodeAffinity - - StatusPhase v1.PersistentVolumePhase - StatusMessage string - StatusReason string - - VolumeType string - FSType *string - ReadOnly *bool - SecretName string - HostPathPath string - HostPathType *v1.HostPathType - GCEPersistentDiskPDName string - GCEPersistentDiskPartition int32 - AWSElasticBlockStoreVolumeID string - AWSElasticBlockStorePartition int32 - NFSServer string - NFSPath string - ISCSITargetPortal string - ISCSIIqn string - ISCSILun int32 - ISCSIInterface string - ISCSIPortals []string - ISCSIDiscoveryCHAPAuth bool - ISCSISessionCHAPAuth bool - ISCSIInitiatorName *string - LocalPath string - GlusterfsEndpointsName string - GlusterfsPath string - RBDCephMonitors []string - RBDImage string - RBDPool string - RBDRadosUser string - RBDKeyring string - FlexVolumeDriver string - FlexVolumeOptions map[string]string - CinderVolumeID string - CephFSMonitors []string - CephFSPath string - CephFSUser string - CephFSSecretFile string - FlockerDatasetName string - FlockerDatasetUUID string - FCTargetWWNs []string - FCLun *int32 - FcWWIDs []string - AzureFileShareName string - VsphereVolumeVolumePath string - VsphereVolumeStoragePolicyName string - VsphereVolumeStoragePolicyID string - QuobyteRegistry string - QuobyteVolume string - QuobyteUser string - QuobyteGroup string - QuobyteTenant string - AzureDiskDiskName string - AzureDiskDataDiskURI string - AzureDiskCachingMode *v1.AzureDataDiskCachingMode - AzureDiskKind *v1.AzureDataDiskKind - PhotonPersistentDiskPdID string - PortworxVolumeID string - ScaleIOGateway string - ScaleIOSystem string - ScaleIOSSLEnabled bool - ScaleIOProtectionDomain string - ScaleIOStoragePool string - ScaleIOStorageMode string - ScaleIOVolumeName string - StorageOSVolumeName string - StorageOSVolumeNamespace string - CSIDriver string - CSIVolumeAttributes map[string]string -} - -// PersistentVolumeColumns returns kubernetes persistent volume fields as Osquery table columns. -func PersistentVolumeColumns() []table.ColumnDefinition { - return k8s.GetSchema(&persistentVolume{}) -} - -// PersistentVolumesGenerate generates the kubernetes persistent volumes as Osquery table data. -func PersistentVolumesGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - pvs, err := k8s.GetClient().CoreV1().PersistentVolumes().List(ctx, options) - if err != nil { - return nil, err - } - - for _, pv := range pvs.Items { - item := &persistentVolume{ - CommonFields: k8s.GetCommonFields(pv.ObjectMeta), - Capacity: pv.Spec.Capacity, - AccessModes: pv.Spec.AccessModes, - ClaimRef: pv.Spec.ClaimRef, - PersistentVolumeReclaimPolicy: pv.Spec.PersistentVolumeReclaimPolicy, - StorageClassName: pv.Spec.StorageClassName, - MountOptions: pv.Spec.MountOptions, - VolumeMode: pv.Spec.VolumeMode, - NodeAffinity: pv.Spec.NodeAffinity, - StatusPhase: pv.Status.Phase, - StatusMessage: pv.Status.Message, - StatusReason: pv.Status.Reason, - } - if pv.Spec.AWSElasticBlockStore != nil { - item.VolumeType = "aws_elastic_block_store" - item.AWSElasticBlockStoreVolumeID = pv.Spec.AWSElasticBlockStore.VolumeID - item.AWSElasticBlockStorePartition = pv.Spec.AWSElasticBlockStore.Partition - item.FSType = &pv.Spec.AWSElasticBlockStore.FSType - item.ReadOnly = &pv.Spec.AWSElasticBlockStore.ReadOnly - } - if pv.Spec.AzureDisk != nil { - item.VolumeType = "azure_disk" - item.AzureDiskCachingMode = pv.Spec.AzureDisk.CachingMode - item.AzureDiskDataDiskURI = pv.Spec.AzureDisk.DataDiskURI - item.AzureDiskDiskName = pv.Spec.AzureDisk.DiskName - item.AzureDiskKind = pv.Spec.AzureDisk.Kind - item.FSType = pv.Spec.AzureDisk.FSType - item.ReadOnly = pv.Spec.AzureDisk.ReadOnly - } - if pv.Spec.AzureFile != nil { - item.VolumeType = "azure_file" - item.AzureFileShareName = pv.Spec.AzureFile.ShareName - item.SecretName = pv.Spec.AzureFile.SecretName - item.ReadOnly = &pv.Spec.AzureFile.ReadOnly - } - if pv.Spec.CSI != nil { - item.VolumeType = "csi" - item.CSIDriver = pv.Spec.CSI.Driver - item.CSIVolumeAttributes = pv.Spec.CSI.VolumeAttributes - item.FSType = &pv.Spec.CSI.FSType - item.ReadOnly = &pv.Spec.CSI.ReadOnly - if pv.Spec.CSI.NodePublishSecretRef != nil { - item.SecretName = pv.Spec.CSI.NodePublishSecretRef.Name - } - } - if pv.Spec.CephFS != nil { - item.VolumeType = "ceph_fs" - item.CephFSMonitors = pv.Spec.CephFS.Monitors - item.CephFSPath = pv.Spec.CephFS.Path - item.CephFSSecretFile = pv.Spec.CephFS.SecretFile - item.CephFSUser = pv.Spec.CephFS.User - item.ReadOnly = &pv.Spec.CephFS.ReadOnly - if pv.Spec.CephFS.SecretRef != nil { - item.SecretName = pv.Spec.CephFS.SecretRef.Name - } - } - if pv.Spec.Cinder != nil { - item.VolumeType = "cinder" - item.CinderVolumeID = pv.Spec.Cinder.VolumeID - item.FSType = &pv.Spec.Cinder.FSType - item.ReadOnly = &pv.Spec.Cinder.ReadOnly - if pv.Spec.Cinder.SecretRef != nil { - item.SecretName = pv.Spec.Cinder.SecretRef.Name - } - } - if pv.Spec.FC != nil { - item.VolumeType = "fc" - item.FCLun = pv.Spec.FC.Lun - item.FCTargetWWNs = pv.Spec.FC.TargetWWNs - item.FcWWIDs = pv.Spec.FC.WWIDs - item.FSType = &pv.Spec.FC.FSType - item.ReadOnly = &pv.Spec.FC.ReadOnly - } - if pv.Spec.FlexVolume != nil { - item.VolumeType = "flex_volume" - item.FlexVolumeDriver = pv.Spec.FlexVolume.Driver - item.FlexVolumeOptions = pv.Spec.FlexVolume.Options - item.FSType = &pv.Spec.FlexVolume.FSType - item.ReadOnly = &pv.Spec.FlexVolume.ReadOnly - if pv.Spec.FlexVolume.SecretRef != nil { - item.SecretName = pv.Spec.FlexVolume.SecretRef.Name - } - } - if pv.Spec.Flocker != nil { - item.VolumeType = "flocker" - item.FlockerDatasetName = pv.Spec.Flocker.DatasetName - item.FlockerDatasetUUID = pv.Spec.Flocker.DatasetUUID - } - if pv.Spec.GCEPersistentDisk != nil { - item.VolumeType = "gce_persistent_disk" - item.GCEPersistentDiskPDName = pv.Spec.GCEPersistentDisk.PDName - item.GCEPersistentDiskPartition = pv.Spec.GCEPersistentDisk.Partition - item.FSType = &pv.Spec.GCEPersistentDisk.FSType - item.ReadOnly = &pv.Spec.GCEPersistentDisk.ReadOnly - } - if pv.Spec.Glusterfs != nil { - item.VolumeType = "gluster_fs" - item.GlusterfsPath = pv.Spec.Glusterfs.Path - item.GlusterfsEndpointsName = pv.Spec.Glusterfs.EndpointsName - item.ReadOnly = &pv.Spec.Glusterfs.ReadOnly - } - if pv.Spec.HostPath != nil { - item.VolumeType = "host_path" - item.HostPathPath = pv.Spec.HostPath.Path - item.HostPathType = pv.Spec.HostPath.Type - } - if pv.Spec.ISCSI != nil { - item.VolumeType = "iscsci" - item.ISCSITargetPortal = pv.Spec.ISCSI.TargetPortal - item.ISCSIIqn = pv.Spec.ISCSI.IQN - item.ISCSILun = pv.Spec.ISCSI.Lun - item.ISCSIInterface = pv.Spec.ISCSI.ISCSIInterface - item.ISCSIPortals = pv.Spec.ISCSI.Portals - item.ISCSIDiscoveryCHAPAuth = pv.Spec.ISCSI.DiscoveryCHAPAuth - item.ISCSISessionCHAPAuth = pv.Spec.ISCSI.SessionCHAPAuth - item.ISCSIInitiatorName = pv.Spec.ISCSI.InitiatorName - item.FSType = &pv.Spec.ISCSI.FSType - item.ReadOnly = &pv.Spec.ISCSI.ReadOnly - if pv.Spec.ISCSI.SecretRef != nil { - item.SecretName = pv.Spec.ISCSI.SecretRef.Name - } - } - if pv.Spec.Local != nil { - item.LocalPath = pv.Spec.Local.Path - item.FSType = pv.Spec.Local.FSType - } - if pv.Spec.NFS != nil { - item.VolumeType = "nfs" - item.NFSPath = pv.Spec.NFS.Path - item.NFSServer = pv.Spec.NFS.Server - item.ReadOnly = &pv.Spec.NFS.ReadOnly - } - if pv.Spec.PhotonPersistentDisk != nil { - item.VolumeType = "photon_persistent_disk" - item.PhotonPersistentDiskPdID = pv.Spec.PhotonPersistentDisk.PdID - item.FSType = &pv.Spec.PhotonPersistentDisk.FSType - } - if pv.Spec.PortworxVolume != nil { - item.VolumeType = "portworx_volume" - item.PortworxVolumeID = pv.Spec.PortworxVolume.VolumeID - item.FSType = &pv.Spec.PortworxVolume.FSType - item.ReadOnly = &pv.Spec.PortworxVolume.ReadOnly - } - if pv.Spec.Quobyte != nil { - item.VolumeType = "quobyte" - item.QuobyteGroup = pv.Spec.Quobyte.Group - item.QuobyteRegistry = pv.Spec.Quobyte.Registry - item.QuobyteTenant = pv.Spec.Quobyte.Tenant - item.QuobyteUser = pv.Spec.Quobyte.User - item.QuobyteVolume = pv.Spec.Quobyte.Volume - item.ReadOnly = &pv.Spec.Quobyte.ReadOnly - } - if pv.Spec.RBD != nil { - item.VolumeType = "rbd" - item.RBDCephMonitors = pv.Spec.RBD.CephMonitors - item.RBDImage = pv.Spec.RBD.RBDImage - item.RBDPool = pv.Spec.RBD.RBDPool - item.RBDRadosUser = pv.Spec.RBD.RadosUser - item.RBDKeyring = pv.Spec.RBD.Keyring - item.FSType = &pv.Spec.RBD.FSType - item.ReadOnly = &pv.Spec.RBD.ReadOnly - if pv.Spec.RBD.SecretRef != nil { - item.SecretName = pv.Spec.RBD.SecretRef.Name - } - } - if pv.Spec.ScaleIO != nil { - item.VolumeType = "scaleio" - item.ScaleIOGateway = pv.Spec.ScaleIO.Gateway - item.ScaleIOSystem = pv.Spec.ScaleIO.System - item.ScaleIOSSLEnabled = pv.Spec.ScaleIO.SSLEnabled - item.ScaleIOProtectionDomain = pv.Spec.ScaleIO.ProtectionDomain - item.ScaleIOStoragePool = pv.Spec.ScaleIO.StoragePool - item.ScaleIOStorageMode = pv.Spec.ScaleIO.StorageMode - item.ScaleIOVolumeName = pv.Spec.ScaleIO.VolumeName - item.FSType = &pv.Spec.ScaleIO.FSType - item.ReadOnly = &pv.Spec.ScaleIO.ReadOnly - if pv.Spec.ScaleIO.SecretRef != nil { - item.SecretName = pv.Spec.ScaleIO.SecretRef.Name - } - } - if pv.Spec.StorageOS != nil { - item.VolumeType = "storage_os" - item.StorageOSVolumeName = pv.Spec.StorageOS.VolumeName - item.StorageOSVolumeNamespace = pv.Spec.StorageOS.VolumeNamespace - item.FSType = &pv.Spec.StorageOS.FSType - item.ReadOnly = &pv.Spec.StorageOS.ReadOnly - if pv.Spec.StorageOS.SecretRef != nil { - item.SecretName = pv.Spec.StorageOS.SecretRef.Name - } - } - if pv.Spec.VsphereVolume != nil { - item.VolumeType = "vsphere_volume" - item.VsphereVolumeStoragePolicyID = pv.Spec.VsphereVolume.StoragePolicyID - item.VsphereVolumeStoragePolicyName = pv.Spec.VsphereVolume.StoragePolicyName - item.VsphereVolumeVolumePath = pv.Spec.VsphereVolume.VolumePath - item.FSType = &pv.Spec.VsphereVolume.FSType - } - results = append(results, k8s.ToMap(item)) - } - - if pvs.Continue == "" { - break - } - options.Continue = pvs.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/core/persistent_volume_claim.go b/infrastructure/kubequery/internal/k8s/core/persistent_volume_claim.go deleted file mode 100644 index 621b6e2dc8..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/persistent_volume_claim.go +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type persistentVolumeClaim struct { - k8s.CommonFields - v1.PersistentVolumeClaimSpec - Phase v1.PersistentVolumeClaimPhase - Capacity v1.ResourceList - Conditions []v1.PersistentVolumeClaimCondition -} - -// PersistentVolumeClaimColumns returns kubernetes persistent volume claim fields as Osquery table columns. -func PersistentVolumeClaimColumns() []table.ColumnDefinition { - return k8s.GetSchema(&persistentVolumeClaim{}) -} - -// PersistentVolumeClaimsGenerate generates the kubernetes persistent volume claims as Osquery table data. -func PersistentVolumeClaimsGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - pvcs, err := k8s.GetClient().CoreV1().PersistentVolumeClaims(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, pvc := range pvcs.Items { - item := &persistentVolumeClaim{ - CommonFields: k8s.GetCommonFields(pvc.ObjectMeta), - PersistentVolumeClaimSpec: pvc.Spec, - Phase: pvc.Status.Phase, - Capacity: pvc.Status.Capacity, - Conditions: pvc.Status.Conditions, - } - results = append(results, k8s.ToMap(item)) - } - - if pvcs.Continue == "" { - break - } - options.Continue = pvcs.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/core/pod.go b/infrastructure/kubequery/internal/k8s/core/pod.go deleted file mode 100644 index d4c70f2ab9..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/pod.go +++ /dev/null @@ -1,233 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - "strings" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type pod struct { - k8s.CommonNamespacedFields - k8s.CommonPodFields - v1.PodStatus -} - -// PodColumns returns kubernetes pod fields as Osquery table columns. -func PodColumns() []table.ColumnDefinition { - return k8s.GetSchema(&pod{}) -} - -// PodsGenerate generates the kubernetes pods as Osquery table data. -func PodsGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - pods, err := k8s.GetClient().CoreV1().Pods(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, p := range pods.Items { - item := &pod{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(p.ObjectMeta), - CommonPodFields: k8s.GetCommonPodFields(p.Spec), - PodStatus: p.Status, - } - results = append(results, k8s.ToMap(item)) - } - - if pods.Continue == "" { - break - } - options.Continue = pods.Continue - } - - return results, nil -} - -type podContainer struct { - k8s.CommonNamespacedFields - k8s.CommonContainerFields - PodName string - ContainerType string - State v1.ContainerState - LastTerminationState v1.ContainerState - Ready bool - RestartCount int32 - ImageRepo string - ImageID string - ContainerID string - Started *bool -} - -// PodContainerColumns returns kubernetes pod container fields as Osquery table columns. -func PodContainerColumns() []table.ColumnDefinition { - return k8s.GetSchema(&podContainer{}) -} - -func getImageRepo(id string) string { - // docker.io/jaegertracing/jaeger-operator@sha256:5a3198179f7972028a29dd7fbf71ac7a21e0dbf46c85e8cc2c37e3b6a5ee26a4 - index := strings.LastIndex(id, "@") - if index < 0 || index == len(id)-1 { - return "" - } - return id[0:index] -} - -func cleanID(id string) string { - // containerd://4a8e3f149f24fb5d4429f4a38e86097e1aec3b6b174bb382a44c6706ad4406e1 - // docker.io/jaegertracing/jaeger-operator@sha256:5a3198179f7972028a29dd7fbf71ac7a21e0dbf46c85e8cc2c37e3b6a5ee26a4 - index := -1 - for _, s := range []string{"/", ":", "@"} { - i := strings.LastIndex(id, s) - if i > -1 && i > index { - index = i - } - } - - if index < 0 || index == len(id)-1 { - return id - } - return id[index+1:] -} - -func updatePodContainerStatus(pc *podContainer, cs *v1.ContainerStatus) { - if cs != nil { - pc.State = cs.State - pc.LastTerminationState = cs.LastTerminationState - pc.Ready = cs.Ready - pc.RestartCount = cs.RestartCount - pc.ImageRepo = getImageRepo(cs.ImageID) - pc.ImageID = cleanID(cs.ImageID) - pc.ContainerID = cleanID(cs.ContainerID) - pc.Started = cs.Started - } -} - -func createPodContainer(p v1.Pod, c v1.Container, cs *v1.ContainerStatus, containerType string) *podContainer { - item := &podContainer{ - CommonNamespacedFields: k8s.GetParentCommonNamespacedFields(p.ObjectMeta, c.Name), - CommonContainerFields: k8s.GetCommonContainerFields(c), - PodName: p.Name, - ContainerType: containerType, - } - item.Name = c.Name - updatePodContainerStatus(item, cs) - return item -} - -func createPodEphemeralContainer(p v1.Pod, c v1.EphemeralContainer, cs *v1.ContainerStatus) *podContainer { - item := &podContainer{ - CommonNamespacedFields: k8s.GetParentCommonNamespacedFields(p.ObjectMeta, c.Name), - CommonContainerFields: k8s.GetCommonEphemeralContainerFields(c), - PodName: p.Name, - ContainerType: "ephemeral", - } - item.Name = c.Name - updatePodContainerStatus(item, cs) - return item -} - -// PodContainersGenerate generates the kubernetes pod containers as Osquery table data. -func PodContainersGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - pods, err := k8s.GetClient().CoreV1().Pods(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, p := range pods.Items { - for i, c := range p.Spec.InitContainers { - var cs *v1.ContainerStatus = nil - if len(p.Status.InitContainerStatuses) > i { - cs = &p.Status.InitContainerStatuses[i] - } - item := createPodContainer(p, c, cs, "init") - results = append(results, k8s.ToMap(item)) - } - for i, c := range p.Spec.Containers { - var cs *v1.ContainerStatus = nil - if len(p.Status.ContainerStatuses) > i { - cs = &p.Status.ContainerStatuses[i] - } - item := createPodContainer(p, c, cs, "container") - results = append(results, k8s.ToMap(item)) - } - for i, c := range p.Spec.EphemeralContainers { - var cs *v1.ContainerStatus = nil - if len(p.Status.EphemeralContainerStatuses) > i { - cs = &p.Status.EphemeralContainerStatuses[i] - } - item := createPodEphemeralContainer(p, c, cs) - results = append(results, k8s.ToMap(item)) - } - } - - if pods.Continue == "" { - break - } - options.Continue = pods.Continue - } - - return results, nil -} - -type podVolume struct { - k8s.CommonNamespacedFields - k8s.CommonVolumeFields - PodName string -} - -// PodVolumeColumns returns kubernetes pod volume fields as Osquery table columns. -func PodVolumeColumns() []table.ColumnDefinition { - return k8s.GetSchema(&podVolume{}) -} - -// PodVolumesGenerate generates the kubernetes pod volumes as Osquery table data. -func PodVolumesGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - pods, err := k8s.GetClient().CoreV1().Pods(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, p := range pods.Items { - for _, v := range p.Spec.Volumes { - item := &podVolume{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(p.ObjectMeta), - CommonVolumeFields: k8s.GetCommonVolumeFields(v), - PodName: p.Name, - } - item.Name = v.Name - results = append(results, k8s.ToMap(item)) - } - } - - if pods.Continue == "" { - break - } - options.Continue = pods.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/core/pod_template.go b/infrastructure/kubequery/internal/k8s/core/pod_template.go deleted file mode 100644 index 960b2d0f62..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/pod_template.go +++ /dev/null @@ -1,169 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type podTemplate struct { - k8s.CommonNamespacedFields - k8s.CommonPodFields -} - -// PodTemplateColumns returns kubernetes pod template fields as Osquery table columns. -func PodTemplateColumns() []table.ColumnDefinition { - return k8s.GetSchema(&podTemplate{}) -} - -// PodTemplatesGenerate generates the kubernetes pod templates as Osquery table data. -func PodTemplatesGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - pts, err := k8s.GetClient().CoreV1().PodTemplates(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, pt := range pts.Items { - item := &podTemplate{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(pt.ObjectMeta), - CommonPodFields: k8s.GetCommonPodFields(pt.Template.Spec), - } - results = append(results, k8s.ToMap(item)) - } - - if pts.Continue == "" { - break - } - options.Continue = pts.Continue - } - - return results, nil -} - -type podTemplateContainer struct { - k8s.CommonNamespacedFields - k8s.CommonContainerFields - PodTemplateName string - ContainerType string -} - -// PodTemplateContainerColumns returns kubernetes pod template container fields as Osquery table columns. -func PodTemplateContainerColumns() []table.ColumnDefinition { - return k8s.GetSchema(&podTemplateContainer{}) -} - -func createPodTemplateContainer(pt v1.PodTemplate, c v1.Container, containerType string) *podTemplateContainer { - item := &podTemplateContainer{ - CommonNamespacedFields: k8s.GetParentCommonNamespacedFields(pt.ObjectMeta, c.Name), - CommonContainerFields: k8s.GetCommonContainerFields(c), - PodTemplateName: pt.Name, - ContainerType: containerType, - } - item.Name = c.Name - return item -} - -func createPodTemplateEphemeralContainer(pt v1.PodTemplate, c v1.EphemeralContainer) *podTemplateContainer { - item := &podTemplateContainer{ - CommonNamespacedFields: k8s.GetParentCommonNamespacedFields(pt.ObjectMeta, c.Name), - CommonContainerFields: k8s.GetCommonEphemeralContainerFields(c), - PodTemplateName: pt.Name, - ContainerType: "ephemeral", - } - item.Name = c.Name - return item -} - -// PodTemplateContainersGenerate generates the kubernetes pod template containers as Osquery table data. -func PodTemplateContainersGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - pts, err := k8s.GetClient().CoreV1().PodTemplates(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, pt := range pts.Items { - for _, c := range pt.Template.Spec.InitContainers { - item := createPodTemplateContainer(pt, c, "init") - results = append(results, k8s.ToMap(item)) - } - for _, c := range pt.Template.Spec.Containers { - item := createPodTemplateContainer(pt, c, "container") - results = append(results, k8s.ToMap(item)) - } - for _, c := range pt.Template.Spec.EphemeralContainers { - item := createPodTemplateEphemeralContainer(pt, c) - results = append(results, k8s.ToMap(item)) - } - } - - if pts.Continue == "" { - break - } - options.Continue = pts.Continue - } - - return results, nil -} - -type podTemplateVolume struct { - k8s.CommonNamespacedFields - k8s.CommonVolumeFields - PodTemplateName string -} - -// PodTemplateVolumeColumns returns kubernetes pod template volume fields as Osquery table columns. -func PodTemplateVolumeColumns() []table.ColumnDefinition { - return k8s.GetSchema(&podTemplateVolume{}) -} - -// PodTemplateVolumesGenerate generates the kubernetes pod template volumes as Osquery table data. -func PodTemplateVolumesGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - pts, err := k8s.GetClient().CoreV1().PodTemplates(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, pt := range pts.Items { - for _, v := range pt.Template.Spec.Volumes { - item := &podTemplateVolume{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(pt.ObjectMeta), - CommonVolumeFields: k8s.GetCommonVolumeFields(v), - PodTemplateName: pt.Name, - } - item.Name = v.Name - results = append(results, k8s.ToMap(item)) - } - } - - if pts.Continue == "" { - break - } - options.Continue = pts.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/core/pod_test.go b/infrastructure/kubequery/internal/k8s/core/pod_test.go deleted file mode 100644 index 75580e7e7f..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/pod_test.go +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestPodsGenerate(t *testing.T) { - ps, err := PodsGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "annotations": "{\"cni.projectcalico.org/podIP\":\"10.1.26.50/32\",\"cni.projectcalico.org/podIPs\":\"10.1.26.50/32\"}", - "cluster_uid": "d7fd8e77-93de-4742-9037-5db9a01e966a", - "conditions": "[{\"type\":\"Initialized\",\"status\":\"True\",\"lastProbeTime\":null,\"lastTransitionTime\":\"2021-01-21T01:08:25Z\"},{\"type\":\"Ready\",\"status\":\"True\",\"lastProbeTime\":null,\"lastTransitionTime\":\"2021-01-21T01:08:52Z\"},{\"type\":\"ContainersReady\",\"status\":\"True\",\"lastProbeTime\":null,\"lastTransitionTime\":\"2021-01-21T01:08:52Z\"},{\"type\":\"PodScheduled\",\"status\":\"True\",\"lastProbeTime\":null,\"lastTransitionTime\":\"2021-01-21T01:08:25Z\"}]", - "container_statuses": "[{\"name\":\"jaeger-operator\",\"state\":{\"running\":{\"startedAt\":\"2021-01-21T01:08:51Z\"}},\"lastState\":{\"terminated\":{\"exitCode\":1,\"reason\":\"Error\",\"startedAt\":\"2021-01-21T01:08:36Z\",\"finishedAt\":\"2021-01-21T01:08:36Z\",\"containerID\":\"containerd://d4c9607e13f2bd2eec99f5261693557963a1380cfe6aceda23b9e3d3d195962f\"}},\"ready\":true,\"restartCount\":2,\"image\":\"docker.io/jaegertracing/jaeger-operator:1.14.0\",\"imageID\":\"docker.io/jaegertracing/jaeger-operator@sha256:5a3198179f7972028a29dd7fbf71ac7a21e0dbf46c85e8cc2c37e3b6a5ee26a4\",\"containerID\":\"containerd://4a8e3f149f24fb5d4429f4a38e86097e1aec3b6b174bb382a44c6706ad4406e1\",\"started\":true}]", - "creation_timestamp": "1611191305", - "dns_policy": "ClusterFirst", - "enable_service_links": "1", - "host_ip": "192.168.0.28", - "host_ipc": "0", - "host_network": "0", - "host_pid": "0", - "labels": "{\"name\":\"jaeger-operator\",\"pod-template-hash\":\"5db4f9d996\"}", - "name": "jaeger-operator-5db4f9d996-pm7ld", - "namespace": "default", - "node_name": "seshu", - "phase": "Running", - "pod_ip": "10.1.26.50", - "pod_ips": "[{\"ip\":\"10.1.26.50\"}]", - "preemption_policy": "PreemptLowerPriority", - "priority": "0", - "qos_class": "BestEffort", - "restart_policy": "Always", - "scheduler_name": "default-scheduler", - "service_account_name": "jaeger-operator", - "start_time": "1611191305", - "termination_grace_period_seconds": "30", - "tolerations": "[{\"key\":\"node.kubernetes.io/not-ready\",\"operator\":\"Exists\",\"effect\":\"NoExecute\",\"tolerationSeconds\":300},{\"key\":\"node.kubernetes.io/unreachable\",\"operator\":\"Exists\",\"effect\":\"NoExecute\",\"tolerationSeconds\":300}]", - "uid": "2271363b-ffc9-4f00-984c-e0a125ee2d7a", - }, - }, ps) -} - -func TestPodContainersGenerate(t *testing.T) { - pcs, err := PodContainersGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "annotations": "{\"cni.projectcalico.org/podIP\":\"10.1.26.50/32\",\"cni.projectcalico.org/podIPs\":\"10.1.26.50/32\"}", - "args": "[\"start\"]", - "cluster_uid": "d7fd8e77-93de-4742-9037-5db9a01e966a", - "container_id": "4a8e3f149f24fb5d4429f4a38e86097e1aec3b6b174bb382a44c6706ad4406e1", - "container_type": "container", - "creation_timestamp": "1611191305", - "env": "[{\"name\":\"WATCH_NAMESPACE\"},{\"name\":\"POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"apiVersion\":\"v1\",\"fieldPath\":\"metadata.name\"}}},{\"name\":\"POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"apiVersion\":\"v1\",\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"OPERATOR_NAME\",\"value\":\"jaeger-operator\"}]", - "image": "jaegertracing/jaeger-operator:1.14.0", - "image_repo": "docker.io/jaegertracing/jaeger-operator", - "image_id": "5a3198179f7972028a29dd7fbf71ac7a21e0dbf46c85e8cc2c37e3b6a5ee26a4", - "image_pull_policy": "Always", - "labels": "{\"name\":\"jaeger-operator\",\"pod-template-hash\":\"5db4f9d996\"}", - "last_termination_state": "{\"terminated\":{\"exitCode\":1,\"reason\":\"Error\",\"startedAt\":\"2021-01-21T01:08:36Z\",\"finishedAt\":\"2021-01-21T01:08:36Z\",\"containerID\":\"containerd://d4c9607e13f2bd2eec99f5261693557963a1380cfe6aceda23b9e3d3d195962f\"}}", - "name": "jaeger-operator", - "namespace": "default", - "pod_name": "jaeger-operator-5db4f9d996-pm7ld", - "ports": "[{\"name\":\"metrics\",\"containerPort\":8383,\"protocol\":\"TCP\"}]", - "ready": "1", - "restart_count": "2", - "started": "1", - "state": "{\"running\":{\"startedAt\":\"2021-01-21T01:08:51Z\"}}", - "stdin": "0", - "stdin_once": "0", - "termination_message_path": "/dev/termination-log", - "termination_message_policy": "File", - "tty": "0", - "uid": "2e7d1ce3-8546-5b73-beb8-46c109f37668", - "volume_mounts": "[{\"name\":\"jaeger-operator-token-c94jx\",\"readOnly\":true,\"mountPath\":\"/var/run/secrets/kubernetes.io/serviceaccount\"}]", - }, - }, pcs) -} - -func TestPodVolumesGenerate(t *testing.T) { - pcs, err := PodVolumesGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "annotations": "{\"cni.projectcalico.org/podIP\":\"10.1.26.50/32\",\"cni.projectcalico.org/podIPs\":\"10.1.26.50/32\"}", - "aws_elastic_block_store_partition": "0", - "cluster_uid": "d7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611191305", - "gce_persistent_disk_partition": "0", - "iscsi_discovery_chap_auth": "0", - "iscsi_lun": "0", - "iscsi_session_chap_auth": "0", - "labels": "{\"name\":\"jaeger-operator\",\"pod-template-hash\":\"5db4f9d996\"}", - "name": "jaeger-operator-token-c94jx", - "namespace": "default", - "pod_name": "jaeger-operator-5db4f9d996-pm7ld", - "scale_iossl_enabled": "0", - "secret_default_mode": "420", - "secret_name": "jaeger-operator-token-c94jx", - "uid": "2271363b-ffc9-4f00-984c-e0a125ee2d7a", - "volume_type": "secret", - }, - }, pcs) -} diff --git a/infrastructure/kubequery/internal/k8s/core/resource_quota.go b/infrastructure/kubequery/internal/k8s/core/resource_quota.go deleted file mode 100644 index a3086f4594..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/resource_quota.go +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type resourceQuota struct { - k8s.CommonNamespacedFields - v1.ResourceQuotaSpec - StatusHard v1.ResourceList - StatusUsed v1.ResourceList -} - -// ResourceQuotaColumns returns kubernetes resource quota fields as Osquery table columns. -func ResourceQuotaColumns() []table.ColumnDefinition { - return k8s.GetSchema(&resourceQuota{}) -} - -// ResourceQuotasGenerate generates the kubernetes resource quotas as Osquery table data. -func ResourceQuotasGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - quotas, err := k8s.GetClient().CoreV1().ResourceQuotas(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, q := range quotas.Items { - item := &resourceQuota{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(q.ObjectMeta), - ResourceQuotaSpec: q.Spec, - StatusHard: q.Status.Hard, - StatusUsed: q.Status.Used, - } - results = append(results, k8s.ToMap(item)) - } - - if quotas.Continue == "" { - break - } - options.Continue = quotas.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/core/secret.go b/infrastructure/kubequery/internal/k8s/core/secret.go deleted file mode 100644 index 0789ce75f3..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/secret.go +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type secret struct { - k8s.CommonNamespacedFields - Immutable *bool - Type v1.SecretType -} - -// SecretColumns returns kubernetes secret fields as Osquery table columns. -func SecretColumns() []table.ColumnDefinition { - return k8s.GetSchema(&secret{}) -} - -// SecretsGenerate generates the kubernetes secrets as Osquery table data. -func SecretsGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - secrets, err := k8s.GetClient().CoreV1().Secrets(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, s := range secrets.Items { - item := &secret{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(s.ObjectMeta), - Immutable: s.Immutable, - Type: s.Type, - } - results = append(results, k8s.ToMap(item)) - } - - if secrets.Continue == "" { - break - } - options.Continue = secrets.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/core/secret_test.go b/infrastructure/kubequery/internal/k8s/core/secret_test.go deleted file mode 100644 index 5f187f6cde..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/secret_test.go +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestSecretsGenerate(t *testing.T) { - ss, err := SecretsGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "annotations": "{\"istio.io/service-account.name\":\"jaeger-operator\"}", - "cluster_uid": "d7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611191305", - "name": "istio.jaeger-operator", - "namespace": "default", - "type": "istio.io/key-and-cert", - "uid": "fb60f655-6b24-4f35-8e2d-17d7ca3ba7d4", - }, - }, ss) -} diff --git a/infrastructure/kubequery/internal/k8s/core/service.go b/infrastructure/kubequery/internal/k8s/core/service.go deleted file mode 100644 index 51ef6e6167..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/service.go +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type service struct { - k8s.CommonNamespacedFields - v1.ServiceSpec - v1.ServiceStatus -} - -// ServiceColumns returns kubernetes service fields as Osquery table columns. -func ServiceColumns() []table.ColumnDefinition { - return k8s.GetSchema(&service{}) -} - -// ServicesGenerate generates the kubernetes services as Osquery table data. -func ServicesGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - services, err := k8s.GetClient().CoreV1().Services(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, s := range services.Items { - item := &service{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(s.ObjectMeta), - ServiceSpec: s.Spec, - ServiceStatus: s.Status, - } - results = append(results, k8s.ToMap(item)) - } - - if services.Continue == "" { - break - } - options.Continue = services.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/core/service_account.go b/infrastructure/kubequery/internal/k8s/core/service_account.go deleted file mode 100644 index 42ae336dfd..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/service_account.go +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type serviceAccount struct { - k8s.CommonNamespacedFields - Secrets []v1.ObjectReference - ImagePullSecrets []v1.LocalObjectReference - AutomountServiceAccountToken *bool -} - -// ServiceAccountColumns returns kubernetes service account fields as Osquery table columns. -func ServiceAccountColumns() []table.ColumnDefinition { - return k8s.GetSchema(&serviceAccount{}) -} - -// ServiceAccountsGenerate generates the kubernetes service accounts as Osquery table data. -func ServiceAccountsGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - sas, err := k8s.GetClient().CoreV1().ServiceAccounts(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, sa := range sas.Items { - item := &serviceAccount{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(sa.ObjectMeta), - Secrets: sa.Secrets, - ImagePullSecrets: sa.ImagePullSecrets, - AutomountServiceAccountToken: sa.AutomountServiceAccountToken, - } - results = append(results, k8s.ToMap(item)) - } - - if sas.Continue == "" { - break - } - options.Continue = sas.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/core/service_account_test.go b/infrastructure/kubequery/internal/k8s/core/service_account_test.go deleted file mode 100644 index 3359476834..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/service_account_test.go +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestServiceAccountsGenerate(t *testing.T) { - sas, err := ServiceAccountsGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "annotations": "{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"v1\\\",\\\"kind\\\":\\\"ServiceAccount\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"labels\\\":{\\\"app\\\":\\\"istio-ingressgateway\\\",\\\"chart\\\":\\\"gateways\\\",\\\"heritage\\\":\\\"Tiller\\\",\\\"release\\\":\\\"istio\\\"},\\\"name\\\":\\\"istio-ingressgateway-service-account\\\",\\\"namespace\\\":\\\"istio-system\\\"}}\\n\"}", - "cluster_uid": "d7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611191143", - "labels": "{\"app\":\"istio-ingressgateway\",\"chart\":\"gateways\",\"heritage\":\"Tiller\",\"release\":\"istio\"}", - "name": "istio-ingressgateway-service-account", - "namespace": "istio-system", - "secrets": "[{\"name\":\"istio-ingressgateway-service-account-token-zmk8b\"}]", - "uid": "de09c78a-ea26-42ff-82d5-2f7d3f24a8d1", - }, - }, sas) -} diff --git a/infrastructure/kubequery/internal/k8s/core/service_test.go b/infrastructure/kubequery/internal/k8s/core/service_test.go deleted file mode 100644 index a5eaf06ce2..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/service_test.go +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package core - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestServicesGenerate(t *testing.T) { - ss, err := ServicesGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "cluster_ip": "10.152.183.187", - "cluster_ips": "[\"10.152.183.187\"]", - "cluster_uid": "d7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611191332", - "health_check_node_port": "0", - "labels": "{\"name\":\"jaeger-operator\"}", - "load_balancer": "{}", - "name": "jaeger-operator", - "namespace": "default", - "ports": "[{\"name\":\"metrics\",\"protocol\":\"TCP\",\"port\":8383,\"targetPort\":8383}]", - "publish_not_ready_addresses": "0", - "selector": "{\"name\":\"jaeger-operator\"}", - "session_affinity": "None", - "type": "ClusterIP", - "uid": "d8dfda88-e2c5-479e-bb2d-d0964805a925", - }, - }, ss) -} diff --git a/infrastructure/kubequery/internal/k8s/core/testdata/component_status_test.json b/infrastructure/kubequery/internal/k8s/core/testdata/component_status_test.json deleted file mode 100644 index a79df29c58..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/testdata/component_status_test.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "apiVersion": "v1", - "items": [ - { - "apiVersion": "v1", - "conditions": [ - { - "message": "ok", - "status": "True", - "type": "Healthy" - } - ], - "kind": "ComponentStatus", - "metadata": { - "creationTimestamp": null, - "name": "scheduler", - "selfLink": "/api/v1/componentstatuses/scheduler" - } - }, - { - "apiVersion": "v1", - "conditions": [ - { - "message": "ok", - "status": "True", - "type": "Healthy" - } - ], - "kind": "ComponentStatus", - "metadata": { - "creationTimestamp": null, - "name": "controller-manager", - "selfLink": "/api/v1/componentstatuses/controller-manager" - } - }, - { - "apiVersion": "v1", - "conditions": [ - { - "message": "{\"health\":\"true\"}", - "status": "True", - "type": "Healthy" - } - ], - "kind": "ComponentStatus", - "metadata": { - "creationTimestamp": null, - "name": "etcd-0", - "selfLink": "/api/v1/componentstatuses/etcd-0" - } - } - ], - "kind": "List", - "metadata": { - "resourceVersion": "", - "selfLink": "" - } -} \ No newline at end of file diff --git a/infrastructure/kubequery/internal/k8s/core/testdata/config_map_test.json b/infrastructure/kubequery/internal/k8s/core/testdata/config_map_test.json deleted file mode 100644 index ac3e301124..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/testdata/config_map_test.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "apiVersion": "v1", - "kind": "ConfigMap", - "metadata": { - "creationTimestamp": "2021-01-21T01:08:51Z", - "managedFields": [ - { - "apiVersion": "v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:ownerReferences": { - ".": {}, - "k:{\"uid\":\"2271363b-ffc9-4f00-984c-e0a125ee2d7a\"}": { - ".": {}, - "f:apiVersion": {}, - "f:kind": {}, - "f:name": {}, - "f:uid": {} - } - } - } - }, - "manager": "jaeger-operator", - "operation": "Update", - "time": "2021-01-21T01:08:51Z" - } - ], - "name": "jaeger-operator-lock", - "namespace": "default", - "ownerReferences": [ - { - "apiVersion": "v1", - "kind": "Pod", - "name": "jaeger-operator-5db4f9d996-pm7ld", - "uid": "2271363b-ffc9-4f00-984c-e0a125ee2d7a" - } - ], - "resourceVersion": "451803", - "selfLink": "/api/v1/namespaces/default/configmaps/jaeger-operator-lock", - "uid": "eec6944c-5c13-4e30-8326-1a82e1962e4d" - } -} diff --git a/infrastructure/kubequery/internal/k8s/core/testdata/endpoint_subset_test.json b/infrastructure/kubequery/internal/k8s/core/testdata/endpoint_subset_test.json deleted file mode 100644 index ba32ca90c9..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/testdata/endpoint_subset_test.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "apiVersion": "v1", - "kind": "Endpoints", - "metadata": { - "annotations": { - "endpoints.kubernetes.io/last-change-trigger-time": "2021-01-20T20:08:52-05:00" - }, - "creationTimestamp": "2021-01-21T01:08:52Z", - "labels": { - "name": "jaeger-operator" - }, - "managedFields": [ - { - "apiVersion": "v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:endpoints.kubernetes.io/last-change-trigger-time": {} - }, - "f:labels": { - ".": {}, - "f:name": {} - } - }, - "f:subsets": {} - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-21T01:08:52Z" - } - ], - "name": "jaeger-operator", - "namespace": "default", - "resourceVersion": "451810", - "selfLink": "/api/v1/namespaces/default/endpoints/jaeger-operator", - "uid": "013741da-d7a5-4a2d-8f4b-792ac6a40dd3" - }, - "subsets": [ - { - "addresses": [ - { - "ip": "10.1.26.50", - "nodeName": "seshu", - "targetRef": { - "kind": "Pod", - "name": "jaeger-operator-5db4f9d996-pm7ld", - "namespace": "default", - "resourceVersion": "451808", - "uid": "2271363b-ffc9-4f00-984c-e0a125ee2d7a" - } - } - ], - "ports": [ - { - "name": "metrics", - "port": 8383, - "protocol": "TCP" - } - ] - } - ] -} diff --git a/infrastructure/kubequery/internal/k8s/core/testdata/namespaces_test.json b/infrastructure/kubequery/internal/k8s/core/testdata/namespaces_test.json deleted file mode 100644 index dc85639776..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/testdata/namespaces_test.json +++ /dev/null @@ -1,275 +0,0 @@ -{ - "apiVersion": "v1", - "items": [ - { - "apiVersion": "v1", - "kind": "Namespace", - "metadata": { - "creationTimestamp": "2021-01-12T18:30:15Z", - "managedFields": [ - { - "apiVersion": "v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:status": { - "f:phase": {} - } - }, - "manager": "kube-apiserver", - "operation": "Update", - "time": "2021-01-12T18:30:15Z" - } - ], - "name": "kube-system", - "resourceVersion": "11", - "selfLink": "/api/v1/namespaces/kube-system", - "uid": "ebca5546-b939-4765-bf3d-869ac644ea0f" - }, - "spec": { - "finalizers": [ - "kubernetes" - ] - }, - "status": { - "phase": "Active" - } - }, - { - "apiVersion": "v1", - "kind": "Namespace", - "metadata": { - "creationTimestamp": "2021-01-12T18:30:15Z", - "managedFields": [ - { - "apiVersion": "v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:status": { - "f:phase": {} - } - }, - "manager": "kube-apiserver", - "operation": "Update", - "time": "2021-01-12T18:30:15Z" - } - ], - "name": "kube-public", - "resourceVersion": "32", - "selfLink": "/api/v1/namespaces/kube-public", - "uid": "6c719dfa-3de8-477b-a650-8bf9e2f12ee0" - }, - "spec": { - "finalizers": [ - "kubernetes" - ] - }, - "status": { - "phase": "Active" - } - }, - { - "apiVersion": "v1", - "kind": "Namespace", - "metadata": { - "creationTimestamp": "2021-01-12T18:30:15Z", - "managedFields": [ - { - "apiVersion": "v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:status": { - "f:phase": {} - } - }, - "manager": "kube-apiserver", - "operation": "Update", - "time": "2021-01-12T18:30:15Z" - } - ], - "name": "kube-node-lease", - "resourceVersion": "44", - "selfLink": "/api/v1/namespaces/kube-node-lease", - "uid": "a8f303fd-0074-475f-935a-122cf8b6d1ad" - }, - "spec": { - "finalizers": [ - "kubernetes" - ] - }, - "status": { - "phase": "Active" - } - }, - { - "apiVersion": "v1", - "kind": "Namespace", - "metadata": { - "creationTimestamp": "2021-01-12T18:30:16Z", - "managedFields": [ - { - "apiVersion": "v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:status": { - "f:phase": {} - } - }, - "manager": "kube-apiserver", - "operation": "Update", - "time": "2021-01-12T18:30:16Z" - } - ], - "name": "default", - "resourceVersion": "149", - "selfLink": "/api/v1/namespaces/default", - "uid": "7b50dc9c-6149-4cac-a0d0-52bf0fa5356d" - }, - "spec": { - "finalizers": [ - "kubernetes" - ] - }, - "status": { - "phase": "Active" - } - }, - { - "apiVersion": "v1", - "kind": "Namespace", - "metadata": { - "annotations": { - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Namespace\",\"metadata\":{\"annotations\":{},\"name\":\"ingress\"}}\n" - }, - "creationTimestamp": "2021-01-21T01:04:07Z", - "managedFields": [ - { - "apiVersion": "v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - } - }, - "f:status": { - "f:phase": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-21T01:04:07Z" - } - ], - "name": "ingress", - "resourceVersion": "450124", - "selfLink": "/api/v1/namespaces/ingress", - "uid": "7653c4b9-3df2-493e-ae28-5e3a777f7e76" - }, - "spec": { - "finalizers": [ - "kubernetes" - ] - }, - "status": { - "phase": "Active" - } - }, - { - "apiVersion": "v1", - "kind": "Namespace", - "metadata": { - "annotations": { - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Namespace\",\"metadata\":{\"annotations\":{},\"labels\":{\"istio-injection\":\"disabled\"},\"name\":\"istio-system\"}}\n" - }, - "creationTimestamp": "2021-01-21T01:05:43Z", - "labels": { - "istio-injection": "disabled" - }, - "managedFields": [ - { - "apiVersion": "v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:istio-injection": {} - } - }, - "f:status": { - "f:phase": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-21T01:05:43Z" - } - ], - "name": "istio-system", - "resourceVersion": "450427", - "selfLink": "/api/v1/namespaces/istio-system", - "uid": "7f931f07-f8d0-4198-bf16-e459914e1866" - }, - "spec": { - "finalizers": [ - "kubernetes" - ] - }, - "status": { - "phase": "Active" - } - }, - { - "apiVersion": "v1", - "kind": "Namespace", - "metadata": { - "annotations": { - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Namespace\",\"metadata\":{\"annotations\":{},\"name\":\"monitoring\"}}\n" - }, - "creationTimestamp": "2021-01-21T01:10:49Z", - "managedFields": [ - { - "apiVersion": "v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - } - }, - "f:status": { - "f:phase": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-21T01:10:49Z" - } - ], - "name": "monitoring", - "resourceVersion": "452095", - "selfLink": "/api/v1/namespaces/monitoring", - "uid": "afb98a87-39bb-4c8f-b0dd-8ea3683ba745" - }, - "spec": { - "finalizers": [ - "kubernetes" - ] - }, - "status": { - "phase": "Active" - } - } - ], - "kind": "List", - "metadata": { - "resourceVersion": "", - "selfLink": "" - } -} diff --git a/infrastructure/kubequery/internal/k8s/core/testdata/node_test.json b/infrastructure/kubequery/internal/k8s/core/testdata/node_test.json deleted file mode 100644 index 217d637e7b..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/testdata/node_test.json +++ /dev/null @@ -1,527 +0,0 @@ -{ - "apiVersion": "v1", - "kind": "Node", - "metadata": { - "annotations": { - "node.alpha.kubernetes.io/ttl": "0", - "projectcalico.org/IPv4Address": "192.168.192.1/20", - "projectcalico.org/IPv4VXLANTunnelAddr": "10.1.26.0", - "volumes.kubernetes.io/controller-managed-attach-detach": "true" - }, - "creationTimestamp": "2021-01-12T18:30:24Z", - "labels": { - "beta.kubernetes.io/arch": "amd64", - "beta.kubernetes.io/os": "linux", - "kubernetes.io/arch": "amd64", - "kubernetes.io/hostname": "seshu", - "kubernetes.io/os": "linux", - "microk8s.io/cluster": "true" - }, - "managedFields": [ - { - "apiVersion": "v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - "f:projectcalico.org/IPv4Address": {}, - "f:projectcalico.org/IPv4VXLANTunnelAddr": {} - } - }, - "f:status": { - "f:conditions": { - "k:{\"type\":\"NetworkUnavailable\"}": { - ".": {}, - "f:lastHeartbeatTime": {}, - "f:lastTransitionTime": {}, - "f:message": {}, - "f:reason": {}, - "f:status": {}, - "f:type": {} - } - } - } - }, - "manager": "calico-node", - "operation": "Update", - "time": "2021-01-12T18:30:48Z" - }, - { - "apiVersion": "v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:volumes.kubernetes.io/controller-managed-attach-detach": {} - }, - "f:labels": { - ".": {}, - "f:beta.kubernetes.io/arch": {}, - "f:beta.kubernetes.io/os": {}, - "f:kubernetes.io/arch": {}, - "f:kubernetes.io/hostname": {}, - "f:kubernetes.io/os": {}, - "f:microk8s.io/cluster": {} - } - }, - "f:status": { - "f:addresses": { - ".": {}, - "k:{\"type\":\"Hostname\"}": { - ".": {}, - "f:address": {}, - "f:type": {} - }, - "k:{\"type\":\"InternalIP\"}": { - ".": {}, - "f:address": {}, - "f:type": {} - } - }, - "f:allocatable": { - ".": {}, - "f:cpu": {}, - "f:ephemeral-storage": {}, - "f:hugepages-1Gi": {}, - "f:hugepages-2Mi": {}, - "f:memory": {}, - "f:pods": {} - }, - "f:capacity": { - ".": {}, - "f:cpu": {}, - "f:ephemeral-storage": {}, - "f:hugepages-1Gi": {}, - "f:hugepages-2Mi": {}, - "f:memory": {}, - "f:pods": {} - }, - "f:conditions": { - ".": {}, - "k:{\"type\":\"DiskPressure\"}": { - ".": {}, - "f:lastHeartbeatTime": {}, - "f:lastTransitionTime": {}, - "f:message": {}, - "f:reason": {}, - "f:status": {}, - "f:type": {} - }, - "k:{\"type\":\"MemoryPressure\"}": { - ".": {}, - "f:lastHeartbeatTime": {}, - "f:lastTransitionTime": {}, - "f:message": {}, - "f:reason": {}, - "f:status": {}, - "f:type": {} - }, - "k:{\"type\":\"PIDPressure\"}": { - ".": {}, - "f:lastHeartbeatTime": {}, - "f:lastTransitionTime": {}, - "f:message": {}, - "f:reason": {}, - "f:status": {}, - "f:type": {} - }, - "k:{\"type\":\"Ready\"}": { - ".": {}, - "f:lastHeartbeatTime": {}, - "f:lastTransitionTime": {}, - "f:message": {}, - "f:reason": {}, - "f:status": {}, - "f:type": {} - } - }, - "f:daemonEndpoints": { - "f:kubeletEndpoint": { - "f:Port": {} - } - }, - "f:images": {}, - "f:nodeInfo": { - "f:architecture": {}, - "f:bootID": {}, - "f:containerRuntimeVersion": {}, - "f:kernelVersion": {}, - "f:kubeProxyVersion": {}, - "f:kubeletVersion": {}, - "f:machineID": {}, - "f:operatingSystem": {}, - "f:osImage": {}, - "f:systemUUID": {} - } - } - }, - "manager": "kubelet", - "operation": "Update", - "time": "2021-01-12T18:31:34Z" - }, - { - "apiVersion": "v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - "f:node.alpha.kubernetes.io/ttl": {} - } - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-21T01:10:50Z" - } - ], - "name": "seshu", - "resourceVersion": "499998", - "selfLink": "/api/v1/nodes/seshu", - "uid": "d0d45111-421d-4d4f-89c9-3e75ca2dc06c" - }, - "spec": {}, - "status": { - "addresses": [ - { - "address": "192.168.0.28", - "type": "InternalIP" - }, - { - "address": "seshu", - "type": "Hostname" - } - ], - "allocatable": { - "cpu": "12", - "ephemeral-storage": "958151776Ki", - "hugepages-1Gi": "0", - "hugepages-2Mi": "0", - "memory": "32411744Ki", - "pods": "110" - }, - "capacity": { - "cpu": "12", - "ephemeral-storage": "959200352Ki", - "hugepages-1Gi": "0", - "hugepages-2Mi": "0", - "memory": "32514144Ki", - "pods": "110" - }, - "conditions": [ - { - "lastHeartbeatTime": "2021-01-20T16:31:53Z", - "lastTransitionTime": "2021-01-20T16:31:53Z", - "message": "Calico is running on this node", - "reason": "CalicoIsUp", - "status": "False", - "type": "NetworkUnavailable" - }, - { - "lastHeartbeatTime": "2021-01-21T19:24:08Z", - "lastTransitionTime": "2021-01-12T18:30:24Z", - "message": "kubelet has sufficient memory available", - "reason": "KubeletHasSufficientMemory", - "status": "False", - "type": "MemoryPressure" - }, - { - "lastHeartbeatTime": "2021-01-21T19:24:08Z", - "lastTransitionTime": "2021-01-12T18:30:24Z", - "message": "kubelet has no disk pressure", - "reason": "KubeletHasNoDiskPressure", - "status": "False", - "type": "DiskPressure" - }, - { - "lastHeartbeatTime": "2021-01-21T19:24:08Z", - "lastTransitionTime": "2021-01-12T18:30:24Z", - "message": "kubelet has sufficient PID available", - "reason": "KubeletHasSufficientPID", - "status": "False", - "type": "PIDPressure" - }, - { - "lastHeartbeatTime": "2021-01-21T19:24:08Z", - "lastTransitionTime": "2021-01-21T01:12:31Z", - "message": "kubelet is posting ready status. AppArmor enabled", - "reason": "KubeletReady", - "status": "True", - "type": "Ready" - } - ], - "daemonEndpoints": { - "kubeletEndpoint": { - "Port": 10250 - } - }, - "images": [ - { - "names": [ - "docker.io/library/kubequery:latest" - ], - "sizeBytes": 202523444 - }, - { - "names": null, - "sizeBytes": 174592418 - }, - { - "names": [ - "k8s.gcr.io/ingress-nginx/controller@sha256:fc4979d8b8443a831c9789b5155cded454cb7de737a8b727bc2ba0106d2eae8b", - "k8s.gcr.io/ingress-nginx/controller:v0.35.0" - ], - "sizeBytes": 111763794 - }, - { - "names": [ - "docker.io/istio/proxyv2@sha256:3ad9ee2b43b299e5e6d97aaea5ed47dbf3da9293733607d9b52f358313e852ae", - "docker.io/istio/proxyv2:1.5.1" - ], - "sizeBytes": 106728139 - }, - { - "names": [ - "docker.io/jaegertracing/jaeger-operator@sha256:5a3198179f7972028a29dd7fbf71ac7a21e0dbf46c85e8cc2c37e3b6a5ee26a4", - "docker.io/jaegertracing/jaeger-operator:1.14.0" - ], - "sizeBytes": 99946252 - }, - { - "names": [ - "docker.io/calico/node@sha256:cb9dea7b86471c71925ae318f7c60af72d9ddf1dab0fe2029832a671b83bba6a", - "docker.io/calico/node:v3.13.2" - ], - "sizeBytes": 88917441 - }, - { - "names": [ - "docker.io/istio/mixer@sha256:92940f04e9aa20a41e330eb8a00a0b8ee7a3f4029dcdadfca4a5d009774474b2", - "docker.io/istio/mixer:1.5.1" - ], - "sizeBytes": 86988340 - }, - { - "names": [ - "docker.io/istio/pilot@sha256:818aecc1c73c53af9091ac1d4f500d9d7cec6d135d372d03cffab1addaff4ec0", - "docker.io/istio/pilot:1.5.1" - ], - "sizeBytes": 85950908 - }, - { - "names": [ - "docker.io/uptycs/kubequery@sha256:96b6c15753941f58e97fc6f80ee7ec06ce63d48a14b53ee0cc1dd10dc3585e7d", - "docker.io/uptycs/kubequery:latest" - ], - "sizeBytes": 82661645 - }, - { - "names": [ - "docker.io/istio/galley@sha256:d69acf890e5c82cb0c000fc15c540777ee566ae225762d85f157f69c9665338c", - "docker.io/istio/galley:1.5.1" - ], - "sizeBytes": 82020368 - }, - { - "names": [ - "docker.io/istio/sidecar_injector@sha256:cf334211f192378e7fcb66baeeb43412e483e34d739e93711d0a61568dd00462", - "docker.io/istio/sidecar_injector:1.5.1" - ], - "sizeBytes": 77988679 - }, - { - "names": [ - "docker.io/calico/cni@sha256:bbf7e3ac3f80d0a356a6c27b095bd313d1106f8ed84f85850816ed79295843c1", - "docker.io/calico/cni:v3.13.2" - ], - "sizeBytes": 76710099 - }, - { - "names": [ - "docker.io/istio/kubectl@sha256:83ea57063cf3344a2462c5bbaa5b125810f2e8ef7283d2ba3bfd9393e624b80f", - "docker.io/istio/kubectl:1.5.1" - ], - "sizeBytes": 76608582 - }, - { - "names": [ - "docker.io/grafana/grafana@sha256:bd55ea2bad17f5016431734b42fdfc202ebdc7d08b6c4ad35ebb03d06efdff69", - "docker.io/grafana/grafana:6.4.3" - ], - "sizeBytes": 76169588 - }, - { - "names": [ - "quay.io/kiali/kiali:v1.9" - ], - "sizeBytes": 75529164 - }, - { - "names": [ - "docker.io/istio/citadel@sha256:92b985411af9844b75c5fc9c39c33fc27ef549c31b5221358f334062aadb86ec", - "docker.io/istio/citadel:1.5.1" - ], - "sizeBytes": 72604439 - }, - { - "names": [ - "docker.io/kubernetesui/dashboard@sha256:06868692fb9a7f2ede1a06de1b7b32afabc40ec739c1181d83b5ed3eb147ec6e", - "docker.io/kubernetesui/dashboard:v2.0.0" - ], - "sizeBytes": 66209190 - }, - { - "names": [ - "docker.io/grafana/grafana@sha256:89304bc2335f4976618548d7b93d165ed67369d3a051d2f627fc4e0aa3d0aff1", - "docker.io/grafana/grafana:7.1.0" - ], - "sizeBytes": 59911815 - }, - { - "names": [ - "quay.io/prometheus/prometheus@sha256:d4ba4dd1a9ebb90916d0bfed3c204adcb118ed24546bf8dd2e6b30fc0fd2009e", - "quay.io/prometheus/prometheus:v2.20.0" - ], - "sizeBytes": 59435495 - }, - { - "names": [ - "docker.io/prom/prometheus@sha256:cd93b8711bb92eb9c437d74217311519e0a93bc55779aa664325dc83cd13cb32", - "docker.io/prom/prometheus:v2.12.0" - ], - "sizeBytes": 54819393 - }, - { - "names": [ - "docker.io/calico/pod2daemon-flexvol@sha256:0022da5a9a89512f8a117f12d2088b3f1f8f22c094ee15aae24d58085f2c186a", - "docker.io/calico/pod2daemon-flexvol:v3.13.2" - ], - "sizeBytes": 37530211 - }, - { - "names": [ - "quay.io/prometheus/alertmanager@sha256:24a5204b418e8fa0214cfb628486749003b039c279c56b5bddb5b10cd100d926", - "quay.io/prometheus/alertmanager:v0.21.0" - ], - "sizeBytes": 27097956 - }, - { - "names": [ - "docker.io/jaegertracing/all-in-one@sha256:738442983b772a5d413c8a2c44a5563956adaff224e5b38f52a959124dafc119", - "docker.io/jaegertracing/all-in-one:1.16" - ], - "sizeBytes": 23571671 - }, - { - "names": [ - "docker.io/directxman12/k8s-prometheus-adapter@sha256:44558d3ae98467e44fee72ebc3948ce59630996013a51d49cf925682a7b87c18", - "docker.io/directxman12/k8s-prometheus-adapter:v0.7.0" - ], - "sizeBytes": 23407634 - }, - { - "names": [ - "docker.io/jaegertracing/all-in-one@sha256:021aefafecbb5559078206996f1f4e8fc5907debab047f4fcc5c837689a66cfa", - "docker.io/jaegertracing/all-in-one:1.14.0" - ], - "sizeBytes": 23208939 - }, - { - "names": [ - "docker.io/calico/kube-controllers@sha256:a635173cbe9deb33deba9baadffd933f61c63fbdadc0e3fa60ff1a14198c1da8", - "docker.io/calico/kube-controllers:v3.13.2" - ], - "sizeBytes": 23132265 - }, - { - "names": [ - "quay.io/brancz/kube-rbac-proxy@sha256:05e15e1164fd7ac85f5702b3f87ef548f4e00de3a79e6c4a6a34c92035497a9a", - "quay.io/brancz/kube-rbac-proxy:v0.8.0" - ], - "sizeBytes": 19991394 - }, - { - "names": [ - "docker.io/kubernetesui/metrics-scraper@sha256:555981a24f184420f3be0c79d4efb6c948a85cfce84034f85a563f4151a81cbf", - "docker.io/kubernetesui/metrics-scraper:v1.0.4" - ], - "sizeBytes": 16020077 - }, - { - "names": [ - "docker.io/coredns/coredns@sha256:41bee6992c2ed0f4628fcef75751048927bcd6b1cee89c79f6acb63ca5474d5a", - "docker.io/coredns/coredns:1.6.6" - ], - "sizeBytes": 12932169 - }, - { - "names": [ - "quay.io/coreos/prometheus-operator@sha256:a54e806fb27d2fb0251da4f3b2a3bb5320759af63a54a755788304775f2384a7", - "quay.io/coreos/prometheus-operator:v0.40.0" - ], - "sizeBytes": 12496211 - }, - { - "names": [ - "quay.io/prometheus/node-exporter@sha256:a2f29256e53cc3e0b64d7a472512600b2e9410347d53cdc85b49f659c17e02ee", - "quay.io/prometheus/node-exporter:v0.18.1" - ], - "sizeBytes": 11122661 - }, - { - "names": [ - "gcr.io/k8s-staging-kube-state-metrics/kube-state-metrics@sha256:9718f2e7999e75f4993e312fccada801c0eb98eaba73db072f0f806d67fcc238", - "gcr.io/k8s-staging-kube-state-metrics/kube-state-metrics:v1.9.7" - ], - "sizeBytes": 10782953 - }, - { - "names": [ - "k8s.gcr.io/metrics-server-amd64@sha256:c9c4e95068b51d6b33a9dccc61875df07dc650abbf4ac1a19d58b4628f89288b", - "k8s.gcr.io/metrics-server-amd64:v0.3.6" - ], - "sizeBytes": 10542830 - }, - { - "names": [ - "docker.io/cdkbot/hostpath-provisioner-amd64@sha256:339f78eabc68ffb1656d584e41f121cb4d2b667565428c8dde836caf5b8a0228", - "docker.io/cdkbot/hostpath-provisioner-amd64:1.0.0" - ], - "sizeBytes": 9745308 - }, - { - "names": [ - "quay.io/coreos/prometheus-config-reloader@sha256:c679a143b24b7731ad1577a9865aa3805426cbf1b25e30807b951dff68466ffd", - "quay.io/coreos/prometheus-config-reloader:v0.40.0" - ], - "sizeBytes": 4254190 - }, - { - "names": [ - "docker.io/jimmidyson/configmap-reload@sha256:d107c7a235c266273b1c3502a391fec374430e5625539403d0de797fa9c556a2", - "docker.io/jimmidyson/configmap-reload:v0.3.0" - ], - "sizeBytes": 4063371 - }, - { - "names": [ - "k8s.gcr.io/pause@sha256:f78411e19d84a252e53bff71a4407a5686c46983a2c2eeed83929b888179acea", - "k8s.gcr.io/pause:3.1" - ], - "sizeBytes": 317164 - } - ], - "nodeInfo": { - "architecture": "amd64", - "bootID": "0b51cb6f-120b-4557-b74a-e53a5f4f00d5", - "containerRuntimeVersion": "containerd://1.3.7", - "kernelVersion": "5.4.0-60-generic", - "kubeProxyVersion": "v1.20.1-34+e7db93d188d0d1", - "kubeletVersion": "v1.20.1-34+e7db93d188d0d1", - "machineID": "c73ef4a4ef2a4ec19a75719b63db3bb7", - "operatingSystem": "linux", - "osImage": "Ubuntu 20.04.1 LTS", - "systemUUID": "4c4c4544-0044-3510-8058-c6c04f5a5932" - } - } -} diff --git a/infrastructure/kubequery/internal/k8s/core/testdata/pod_test.json b/infrastructure/kubequery/internal/k8s/core/testdata/pod_test.json deleted file mode 100644 index 3053fb6a66..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/testdata/pod_test.json +++ /dev/null @@ -1,344 +0,0 @@ -{ - "apiVersion": "v1", - "kind": "Pod", - "metadata": { - "annotations": { - "cni.projectcalico.org/podIP": "10.1.26.50/32", - "cni.projectcalico.org/podIPs": "10.1.26.50/32" - }, - "creationTimestamp": "2021-01-21T01:08:25Z", - "generateName": "jaeger-operator-5db4f9d996-", - "labels": { - "name": "jaeger-operator", - "pod-template-hash": "5db4f9d996" - }, - "managedFields": [ - { - "apiVersion": "v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:generateName": {}, - "f:labels": { - ".": {}, - "f:name": {}, - "f:pod-template-hash": {} - }, - "f:ownerReferences": { - ".": {}, - "k:{\"uid\":\"2efeb411-ff99-434b-a5a2-4e06c2b0afaa\"}": { - ".": {}, - "f:apiVersion": {}, - "f:blockOwnerDeletion": {}, - "f:controller": {}, - "f:kind": {}, - "f:name": {}, - "f:uid": {} - } - } - }, - "f:spec": { - "f:containers": { - "k:{\"name\":\"jaeger-operator\"}": { - ".": {}, - "f:args": {}, - "f:env": { - ".": {}, - "k:{\"name\":\"OPERATOR_NAME\"}": { - ".": {}, - "f:name": {}, - "f:value": {} - }, - "k:{\"name\":\"POD_NAME\"}": { - ".": {}, - "f:name": {}, - "f:valueFrom": { - ".": {}, - "f:fieldRef": { - ".": {}, - "f:apiVersion": {}, - "f:fieldPath": {} - } - } - }, - "k:{\"name\":\"POD_NAMESPACE\"}": { - ".": {}, - "f:name": {}, - "f:valueFrom": { - ".": {}, - "f:fieldRef": { - ".": {}, - "f:apiVersion": {}, - "f:fieldPath": {} - } - } - }, - "k:{\"name\":\"WATCH_NAMESPACE\"}": { - ".": {}, - "f:name": {} - } - }, - "f:image": {}, - "f:imagePullPolicy": {}, - "f:name": {}, - "f:ports": { - ".": {}, - "k:{\"containerPort\":8383,\"protocol\":\"TCP\"}": { - ".": {}, - "f:containerPort": {}, - "f:name": {}, - "f:protocol": {} - } - }, - "f:resources": {}, - "f:terminationMessagePath": {}, - "f:terminationMessagePolicy": {} - } - }, - "f:dnsPolicy": {}, - "f:enableServiceLinks": {}, - "f:restartPolicy": {}, - "f:schedulerName": {}, - "f:securityContext": {}, - "f:serviceAccount": {}, - "f:serviceAccountName": {}, - "f:terminationGracePeriodSeconds": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-21T01:08:25Z" - }, - { - "apiVersion": "v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:cni.projectcalico.org/podIP": {}, - "f:cni.projectcalico.org/podIPs": {} - } - } - }, - "manager": "calico", - "operation": "Update", - "time": "2021-01-21T01:08:26Z" - }, - { - "apiVersion": "v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:status": { - "f:conditions": { - "k:{\"type\":\"ContainersReady\"}": { - ".": {}, - "f:lastProbeTime": {}, - "f:lastTransitionTime": {}, - "f:status": {}, - "f:type": {} - }, - "k:{\"type\":\"Initialized\"}": { - ".": {}, - "f:lastProbeTime": {}, - "f:lastTransitionTime": {}, - "f:status": {}, - "f:type": {} - }, - "k:{\"type\":\"Ready\"}": { - ".": {}, - "f:lastProbeTime": {}, - "f:lastTransitionTime": {}, - "f:status": {}, - "f:type": {} - } - }, - "f:containerStatuses": {}, - "f:hostIP": {}, - "f:phase": {}, - "f:podIP": {}, - "f:podIPs": { - ".": {}, - "k:{\"ip\":\"10.1.26.50\"}": { - ".": {}, - "f:ip": {} - } - }, - "f:startTime": {} - } - }, - "manager": "kubelet", - "operation": "Update", - "time": "2021-01-21T01:08:52Z" - } - ], - "name": "jaeger-operator-5db4f9d996-pm7ld", - "namespace": "default", - "ownerReferences": [ - { - "apiVersion": "apps/v1", - "blockOwnerDeletion": true, - "controller": true, - "kind": "ReplicaSet", - "name": "jaeger-operator-5db4f9d996", - "uid": "2efeb411-ff99-434b-a5a2-4e06c2b0afaa" - } - ], - "resourceVersion": "451808", - "selfLink": "/api/v1/namespaces/default/pods/jaeger-operator-5db4f9d996-pm7ld", - "uid": "2271363b-ffc9-4f00-984c-e0a125ee2d7a" - }, - "spec": { - "containers": [ - { - "args": [ - "start" - ], - "env": [ - { - "name": "WATCH_NAMESPACE" - }, - { - "name": "POD_NAME", - "valueFrom": { - "fieldRef": { - "apiVersion": "v1", - "fieldPath": "metadata.name" - } - } - }, - { - "name": "POD_NAMESPACE", - "valueFrom": { - "fieldRef": { - "apiVersion": "v1", - "fieldPath": "metadata.namespace" - } - } - }, - { - "name": "OPERATOR_NAME", - "value": "jaeger-operator" - } - ], - "image": "jaegertracing/jaeger-operator:1.14.0", - "imagePullPolicy": "Always", - "name": "jaeger-operator", - "ports": [ - { - "containerPort": 8383, - "name": "metrics", - "protocol": "TCP" - } - ], - "resources": {}, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", - "name": "jaeger-operator-token-c94jx", - "readOnly": true - } - ] - } - ], - "dnsPolicy": "ClusterFirst", - "enableServiceLinks": true, - "nodeName": "seshu", - "preemptionPolicy": "PreemptLowerPriority", - "priority": 0, - "restartPolicy": "Always", - "schedulerName": "default-scheduler", - "securityContext": {}, - "serviceAccount": "jaeger-operator", - "serviceAccountName": "jaeger-operator", - "terminationGracePeriodSeconds": 30, - "tolerations": [ - { - "effect": "NoExecute", - "key": "node.kubernetes.io/not-ready", - "operator": "Exists", - "tolerationSeconds": 300 - }, - { - "effect": "NoExecute", - "key": "node.kubernetes.io/unreachable", - "operator": "Exists", - "tolerationSeconds": 300 - } - ], - "volumes": [ - { - "name": "jaeger-operator-token-c94jx", - "secret": { - "defaultMode": 420, - "secretName": "jaeger-operator-token-c94jx" - } - } - ] - }, - "status": { - "conditions": [ - { - "lastProbeTime": null, - "lastTransitionTime": "2021-01-21T01:08:25Z", - "status": "True", - "type": "Initialized" - }, - { - "lastProbeTime": null, - "lastTransitionTime": "2021-01-21T01:08:52Z", - "status": "True", - "type": "Ready" - }, - { - "lastProbeTime": null, - "lastTransitionTime": "2021-01-21T01:08:52Z", - "status": "True", - "type": "ContainersReady" - }, - { - "lastProbeTime": null, - "lastTransitionTime": "2021-01-21T01:08:25Z", - "status": "True", - "type": "PodScheduled" - } - ], - "containerStatuses": [ - { - "containerID": "containerd://4a8e3f149f24fb5d4429f4a38e86097e1aec3b6b174bb382a44c6706ad4406e1", - "image": "docker.io/jaegertracing/jaeger-operator:1.14.0", - "imageID": "docker.io/jaegertracing/jaeger-operator@sha256:5a3198179f7972028a29dd7fbf71ac7a21e0dbf46c85e8cc2c37e3b6a5ee26a4", - "lastState": { - "terminated": { - "containerID": "containerd://d4c9607e13f2bd2eec99f5261693557963a1380cfe6aceda23b9e3d3d195962f", - "exitCode": 1, - "finishedAt": "2021-01-21T01:08:36Z", - "reason": "Error", - "startedAt": "2021-01-21T01:08:36Z" - } - }, - "name": "jaeger-operator", - "ready": true, - "restartCount": 2, - "started": true, - "state": { - "running": { - "startedAt": "2021-01-21T01:08:51Z" - } - } - } - ], - "hostIP": "192.168.0.28", - "phase": "Running", - "podIP": "10.1.26.50", - "podIPs": [ - { - "ip": "10.1.26.50" - } - ], - "qosClass": "BestEffort", - "startTime": "2021-01-21T01:08:25Z" - } -} diff --git a/infrastructure/kubequery/internal/k8s/core/testdata/secret_test.json b/infrastructure/kubequery/internal/k8s/core/testdata/secret_test.json deleted file mode 100644 index 98cd9c37f9..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/testdata/secret_test.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "apiVersion": "v1", - "data": { - "cert-chain.pem": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURKekNDQWcrZ0F3SUJBZ0lRSzZFK3FQVGpGRjM5TVRaU2FYSU1HekFOQmdrcWhraUc5dzBCQVFzRkFEQVkKTVJZd0ZBWURWUVFLRXcxamJIVnpkR1Z5TG14dlkyRnNNQjRYRFRJeE1ERXlNVEF4TURneU5Wb1hEVEl4TURReQpNVEF4TURneU5Wb3dBRENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFPWkZqazZWCkgxY1RoM1ZEdGowZy9rMWticnJadzQ3NHN1SkduSlJFVDArK1FBa1BDdE1sRzJOYVNENTByQWNFdTBBSlZzaXAKTmM0NTZkRlVzdk1IYVErQWhZYURQTDdHa3c2eDRyVVBRQkQwa3lYQUhjenUrY3FhZklXdGcrTGZibDhYYVZHTQpQMk5HVEJ1aEhtQVV0V0VORVB4NW1jaUlVOWpDZXQ0Szh0ZDFZQ2FtS1hSak1ldFBrR3ZrRDBGdGQ0WTVNSVNyCnNyNjdVdE9rWmgvTEpEWnFJVlBqa0JWdEJnQXdZNGdFRTV5SUdna3lNaVlreE9xL1JMOGxQejN2WVA5ci85aEMKQUtWVTJTSGxrNGFMa3VmVzJWaTdzSTJpRnZIYzgxeVJ6VE5UYkVFWkY2MUxFQm1MNzVIamdTbVN5R0poZTBQcwpzYWJQTzk2RDJOTkFNbjhDQXdFQUFhT0JoRENCZ1RBT0JnTlZIUThCQWY4RUJBTUNCYUF3SFFZRFZSMGxCQll3CkZBWUlLd1lCQlFVSEF3RUdDQ3NHQVFVRkJ3TUNNQXdHQTFVZEV3RUIvd1FDTUFBd1FnWURWUjBSQVFIL0JEZ3cKTm9ZMGMzQnBabVpsT2k4dlkyeDFjM1JsY2k1c2IyTmhiQzl1Y3k5a1pXWmhkV3gwTDNOaEwycGhaV2RsY2kxdgpjR1Z5WVhSdmNqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFDYXFzY09QNXJnMm96ZmlpckdEWmx2dUFWOFZJCmR6ZUxEaTlMMHpLVjBxLy9TQW5SUEl5bWk0ampDYkRkbmYvYnpWWEdnZVNUNTNTeUR4Z3ZXVkQ2akZzK0JwTlcKcXFFWWE0NWdLUEFreUVpaVJJbW0vRFBQSzk4T2FKbGROK2xpVDllU1o1SUFocCtvVml5WnVwZURTR3cvTXdIdwphQ3dPbjYwSDdrUEU3cTY3NlZLU2tXd1BtdWpQU0xqeE5UWXhQVklPblZsVmo4REZwNUM4LzZGM3VUb1J6Qm1aCmM2Znhvblo1UWdreDBWRHl4R3lMU0w2UXRudFBXdkpha0RsZFphek4zUlM2ZnFqZ0dFa01FUWQwdjhGcEh3RTQKZmRJbTNMYksvWWtWSXkwZ0l1TGYzVklKNkxBcjdCWW11ODE3dGxjRExud3FIZUVFL1Z3cGxvWHpWZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K", - "root-cert.pem": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMzVENDQWNXZ0F3SUJBZ0lRUWlNTDcvcFoyV0hMWVZRMGFIelpMREFOQmdrcWhraUc5dzBCQVFzRkFEQVkKTVJZd0ZBWURWUVFLRXcxamJIVnpkR1Z5TG14dlkyRnNNQjRYRFRJeE1ERXlNVEF4TURZek1Gb1hEVE14TURFeApPVEF4TURZek1Gb3dHREVXTUJRR0ExVUVDaE1OWTJ4MWMzUmxjaTVzYjJOaGJEQ0NBU0l3RFFZSktvWklodmNOCkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFLZGJpM0JYZ0FkcElYcDVCaThYdWMxVWhZei91SmtXYkxBODRxRTAKdmtHQ3R6N3cycFltUEJ2dmdvUzZzdyt2MGlHcFdsZllGdDQ0U1p1V1M4Um5SR2QzeXhtK09oWmFab1c0eFI2bwpRdE9wRTFUMnc3QUVFeTVKY2MvUkdjTHBiWXpPUFdCamtXU01YSmZoeEJSeTZZL3J3dUJMVjBrRjNlZnF5cUVUCklUcUxaWVYxYS9BYzhGcGVkcnlieVFBSHRzcWdnTUNMN250VlgrMUZwdUg5QVhmRGxpV0tTNnJ1T2tPUEMrY1IKY3l2ZkUyRmlINmMyZkVqeU5WMTU3cEc1UHliQTFsaGdMUVVXSUsxZ1FBTGg1Um9IVE1BUDFYakt3S2FWeDBRVApSeDhpaWlhZnVvdHEyZ3Q2NDRtL2ZmRndRUmVKVTcwLytrV2h6NXBsNGQrVVQzOENBd0VBQWFNak1DRXdEZ1lEClZSMFBBUUgvQkFRREFnSUVNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUIKQUtCcDdxS1MrNENhb29naCtiVmVmOWoyaFRLOFVPRDNWSzFuQXFLRllZcCtvU3l4dzI2bGNmcWpBVXlBYmp2RQpUSEtQaHhaL3NtbVd1RGU0SWI0YTAvNi9RaWRqemJSMTVCOWlwWTFESExqUGU4anJMMm1uSjd0YzBSYnM0ZFVrCjVIV0VXbzRDZXM0SFUwajVFVGdycHdUY2J5VXYyVTRXNjA1WTRMRnJCNVJWOS9wSkRyKzhtb1FhdWhEZEpTTHIKaW1yZ0xyVmRxZHRTOTl6dDdVRTRsZ3ovckpKV1h6eSs4S1dZRjh5RXUxSjVnNUhMR25ubXJZbjRGUUFEZW1iSQp3bnBvc1FObm5XUnZqSXZlRDE3dDNnOHFSbFhDa1krZGM4VGJKT2lEZ1JUOC9yUlo3REpiMjlsQlp0NkpkOE10CjRCZzNSaDllTVBhWHFIZUdlckhHMzgwPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==" - }, - "kind": "Secret", - "metadata": { - "annotations": { - "istio.io/service-account.name": "jaeger-operator" - }, - "creationTimestamp": "2021-01-21T01:08:25Z", - "managedFields": [ - { - "apiVersion": "v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:data": { - ".": {}, - "f:cert-chain.pem": {}, - "f:key.pem": {}, - "f:root-cert.pem": {} - }, - "f:metadata": { - "f:annotations": { - ".": {}, - "f:istio.io/service-account.name": {} - } - }, - "f:type": {} - }, - "manager": "istio_ca", - "operation": "Update", - "time": "2021-01-21T01:08:25Z" - } - ], - "name": "istio.jaeger-operator", - "namespace": "default", - "resourceVersion": "451570", - "selfLink": "/api/v1/namespaces/default/secrets/istio.jaeger-operator", - "uid": "fb60f655-6b24-4f35-8e2d-17d7ca3ba7d4" - }, - "type": "istio.io/key-and-cert" -} diff --git a/infrastructure/kubequery/internal/k8s/core/testdata/service_account_test.json b/infrastructure/kubequery/internal/k8s/core/testdata/service_account_test.json deleted file mode 100644 index 274a092183..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/testdata/service_account_test.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "apiVersion": "v1", - "kind": "ServiceAccount", - "metadata": { - "annotations": { - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"ServiceAccount\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"istio-ingressgateway\",\"chart\":\"gateways\",\"heritage\":\"Tiller\",\"release\":\"istio\"},\"name\":\"istio-ingressgateway-service-account\",\"namespace\":\"istio-system\"}}\n" - }, - "creationTimestamp": "2021-01-21T01:05:43Z", - "labels": { - "app": "istio-ingressgateway", - "chart": "gateways", - "heritage": "Tiller", - "release": "istio" - }, - "managedFields": [ - { - "apiVersion": "v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:secrets": { - ".": {}, - "k:{\"name\":\"istio-ingressgateway-service-account-token-zmk8b\"}": { - ".": {}, - "f:name": {} - } - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-21T01:05:43Z" - }, - { - "apiVersion": "v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:app": {}, - "f:chart": {}, - "f:heritage": {}, - "f:release": {} - } - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-21T01:05:43Z" - } - ], - "name": "istio-ingressgateway-service-account", - "namespace": "istio-system", - "resourceVersion": "450491", - "selfLink": "/api/v1/namespaces/istio-system/serviceaccounts/istio-ingressgateway-service-account", - "uid": "de09c78a-ea26-42ff-82d5-2f7d3f24a8d1" - }, - "secrets": [ - { - "name": "istio-ingressgateway-service-account-token-zmk8b" - } - ] -} diff --git a/infrastructure/kubequery/internal/k8s/core/testdata/services_test.json b/infrastructure/kubequery/internal/k8s/core/testdata/services_test.json deleted file mode 100644 index 4c52d6bfef..0000000000 --- a/infrastructure/kubequery/internal/k8s/core/testdata/services_test.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "apiVersion": "v1", - "kind": "Service", - "metadata": { - "creationTimestamp": "2021-01-21T01:08:52Z", - "labels": { - "name": "jaeger-operator" - }, - "managedFields": [ - { - "apiVersion": "v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:labels": { - ".": {}, - "f:name": {} - }, - "f:ownerReferences": { - ".": {}, - "k:{\"uid\":\"baa856ea-2d04-4bbb-b9be-aa5b89c58087\"}": { - ".": {}, - "f:apiVersion": {}, - "f:blockOwnerDeletion": {}, - "f:controller": {}, - "f:kind": {}, - "f:name": {}, - "f:uid": {} - } - } - }, - "f:spec": { - "f:ports": { - ".": {}, - "k:{\"port\":8383,\"protocol\":\"TCP\"}": { - ".": {}, - "f:name": {}, - "f:port": {}, - "f:protocol": {}, - "f:targetPort": {} - } - }, - "f:selector": { - ".": {}, - "f:name": {} - }, - "f:sessionAffinity": {}, - "f:type": {} - } - }, - "manager": "jaeger-operator", - "operation": "Update", - "time": "2021-01-21T01:08:52Z" - } - ], - "name": "jaeger-operator", - "namespace": "default", - "ownerReferences": [ - { - "apiVersion": "apps/v1", - "blockOwnerDeletion": true, - "controller": true, - "kind": "Deployment", - "name": "jaeger-operator", - "uid": "baa856ea-2d04-4bbb-b9be-aa5b89c58087" - } - ], - "resourceVersion": "451805", - "selfLink": "/api/v1/namespaces/default/services/jaeger-operator", - "uid": "d8dfda88-e2c5-479e-bb2d-d0964805a925" - }, - "spec": { - "clusterIP": "10.152.183.187", - "clusterIPs": [ - "10.152.183.187" - ], - "ports": [ - { - "name": "metrics", - "port": 8383, - "protocol": "TCP", - "targetPort": 8383 - } - ], - "selector": { - "name": "jaeger-operator" - }, - "sessionAffinity": "None", - "type": "ClusterIP" - }, - "status": { - "loadBalancer": {} - } -} diff --git a/infrastructure/kubequery/internal/k8s/discovery/api_resource.go b/infrastructure/kubequery/internal/k8s/discovery/api_resource.go deleted file mode 100644 index 55405aca33..0000000000 --- a/infrastructure/kubequery/internal/k8s/discovery/api_resource.go +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package discovery - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" -) - -type apiResource struct { - ClusterName string - ClusterUID types.UID - metav1.APIResource - GroupVersion string -} - -// APIResourceColumns returns kubernetes API resource fields as Osquery table columns. -func APIResourceColumns() []table.ColumnDefinition { - return k8s.GetSchema(&apiResource{}) -} - -// APIResourcesGenerate generates the kubernetes API resources as Osquery table data. -func APIResourcesGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - results := make([]map[string]string, 0) - - sr, err := k8s.GetClient().Discovery().ServerResources() - if err != nil { - return nil, err - } - - for _, rl := range sr { - for _, r := range rl.APIResources { - item := &apiResource{ - ClusterName: k8s.GetClusterName(), - ClusterUID: k8s.GetClusterUID(), - GroupVersion: rl.GroupVersion, - APIResource: r, - } - results = append(results, k8s.ToMap(item)) - } - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/discovery/api_resource_test.go b/infrastructure/kubequery/internal/k8s/discovery/api_resource_test.go deleted file mode 100644 index 4f8383b83d..0000000000 --- a/infrastructure/kubequery/internal/k8s/discovery/api_resource_test.go +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package discovery - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/fake" -) - -func TestAPIResourcesGenerate(t *testing.T) { - clientset := fake.NewSimpleClientset() - k8s.SetClient(clientset, types.UID("hello"), "cluster-name") - - ars, err := APIResourcesGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{}, ars) -} diff --git a/infrastructure/kubequery/internal/k8s/discovery/info.go b/infrastructure/kubequery/internal/k8s/discovery/info.go deleted file mode 100644 index 11627eeb0d..0000000000 --- a/infrastructure/kubequery/internal/k8s/discovery/info.go +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package discovery - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/version" -) - -type info struct { - ClusterUID types.UID - ClusterName string - version.Info -} - -// InfoColumns returns kubernetes info fields as Osquery table columns. -func InfoColumns() []table.ColumnDefinition { - return k8s.GetSchema(&info{}) -} - -// InfoGenerate generates the kubernetes info as Osquery table data. -func InfoGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - results := make([]map[string]string, 0) - - sv, err := k8s.GetClient().Discovery().ServerVersion() - if err != nil { - return nil, err - } - - item := &info{ - ClusterUID: k8s.GetClusterUID(), - ClusterName: k8s.GetClusterName(), - Info: *sv, - } - results = append(results, k8s.ToMap(item)) - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/discovery/info_test.go b/infrastructure/kubequery/internal/k8s/discovery/info_test.go deleted file mode 100644 index 9c24c796d6..0000000000 --- a/infrastructure/kubequery/internal/k8s/discovery/info_test.go +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package discovery - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/version" - fakediscovery "k8s.io/client-go/discovery/fake" - "k8s.io/client-go/kubernetes/fake" -) - -func TestInfoGenerate(t *testing.T) { - clientset := fake.NewSimpleClientset() - clientset.Discovery().(*fakediscovery.FakeDiscovery).FakedServerVersion = &version.Info{ - Major: "1", - Minor: "46", - GitVersion: "master", - GitCommit: "123", - BuildDate: "1970-01-01T00:00:00Z", - GoVersion: "go1.15", - Compiler: "gc", - Platform: "linux/amd64", - } - k8s.SetClient(clientset, types.UID("hello"), "cluster-name") - - ars, err := InfoGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "build_date": "1970-01-01T00:00:00Z", - "cluster_uid": "hello", - "cluster_name": "cluster-name", - "compiler": "gc", - "git_commit": "123", - "git_version": "master", - "go_version": "go1.15", - "major": "1", - "minor": "46", - "platform": "linux/amd64", - }, - }, ars) -} diff --git a/infrastructure/kubequery/internal/k8s/event/watcher.go b/infrastructure/kubequery/internal/k8s/event/watcher.go deleted file mode 100644 index 85b9ea1788..0000000000 --- a/infrastructure/kubequery/internal/k8s/event/watcher.go +++ /dev/null @@ -1,159 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package event - -import ( - "context" - "sync" - "time" - - osquery "github.com/Uptycs/basequery-go" - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/events/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/informers" - "k8s.io/client-go/tools/cache" -) - -const tableName = "kubernetes_events" - -// Watcher holds the kubernetes informer. Can be started to receive events from k8s. -type Watcher struct { - lock sync.Mutex - client *osquery.ExtensionManagerClient - stopper chan struct{} - informer cache.SharedInformer -} - -type event struct { - Time metav1.Time - EventType string - ClusterUID types.UID - ClusterName string - Name string - Namespace string - CreationTimestamp metav1.Time - Labels map[string]string - Annotations map[string]string - ReportingController string - ReportingInstance string - Action string - Reason string - Note string - Type string - RegardingKind string - RegardingNamespace string - RegardingName string - RegardingUID types.UID - RelatedKind string - RelatedNamespace string - RelatedName string - RelatedUID types.UID -} - -// Columns returns kubernetes event fields as Osquery table columns. -func Columns() []table.ColumnDefinition { - return k8s.GetSchema(&event{}) -} - -// Generate generates the kubernetes events as Osquery table data. -// For event'ed table Generate method should never be called. So this always returns nil. -func Generate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - return nil, nil -} - -func streamEvent(client *osquery.ExtensionManagerClient, eventType string, e *v1.Event) { - event := &event{ - EventType: eventType, - ClusterName: k8s.GetClusterName(), - ClusterUID: k8s.GetClusterUID(), - Name: e.Name, - Namespace: e.Namespace, - CreationTimestamp: e.CreationTimestamp, - Reason: e.Reason, - Note: e.Note, - Type: e.Type, - Labels: e.Labels, - Annotations: e.Annotations, - ReportingController: e.ReportingController, - ReportingInstance: e.ReportingInstance, - Action: e.Action, - RegardingUID: e.Regarding.UID, - RegardingKind: e.Regarding.Kind, - RegardingName: e.Regarding.Name, - RegardingNamespace: e.Regarding.Namespace, - } - if e.EventTime.IsZero() { - event.Time = metav1.Now() - } else { - event.Time = metav1.Time(e.EventTime) - } - if e.Related != nil { - event.RelatedUID = e.Related.UID - event.RelatedKind = e.Related.Kind - event.RelatedName = e.Related.Name - event.RelatedNamespace = e.Related.Namespace - } - - events := make([]map[string]string, 1) - events[0] = k8s.ToMap(event) - - // TODO: Returned status, error is ignored - client.StreamEvents(tableName, events) -} - -// CreateEventWatcher when started will get events from kubernetes that will be streamed to Osquery. -func CreateEventWatcher(socket string, timeout time.Duration) (*Watcher, error) { - client, err := osquery.NewClient(socket, timeout) - if err != nil { - return nil, err - } - - factory := informers.NewSharedInformerFactory(k8s.GetClient(), 0) - watcher := &Watcher{ - client: client, - stopper: make(chan struct{}), - informer: factory.Events().V1().Events().Informer(), - } - - watcher.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - watcher.lock.Lock() - streamEvent(client, "add", obj.(*v1.Event)) - watcher.lock.Unlock() - }, - UpdateFunc: func(old interface{}, new interface{}) { - watcher.lock.Lock() - streamEvent(client, "update", new.(*v1.Event)) - watcher.lock.Unlock() - }, - DeleteFunc: func(obj interface{}) { - watcher.lock.Lock() - streamEvent(client, "delete", obj.(*v1.Event)) - watcher.lock.Unlock() - }, - }) - - return watcher, nil -} - -// Start will start the watcher to stream kubernetes events as they come in. -func (e *Watcher) Start() { - go e.informer.Run(e.stopper) -} - -// Stop terminates the watcher. -func (e *Watcher) Stop() { - e.client.Close() - e.stopper <- struct{}{} - close(e.stopper) -} diff --git a/infrastructure/kubequery/internal/k8s/networking/ingress.go b/infrastructure/kubequery/internal/k8s/networking/ingress.go deleted file mode 100644 index 0da7ac8918..0000000000 --- a/infrastructure/kubequery/internal/k8s/networking/ingress.go +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package networking - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/networking/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type ingress struct { - k8s.CommonNamespacedFields - v1.IngressSpec - v1.IngressStatus -} - -// IngressColumns returns kubernetes ingress fields as Osquery table columns. -func IngressColumns() []table.ColumnDefinition { - return k8s.GetSchema(&ingress{}) -} - -// IngressesGenerate generates the kubernetes ingresses as Osquery table data. -func IngressesGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - ingresses, err := k8s.GetClient().NetworkingV1().Ingresses(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, i := range ingresses.Items { - item := &ingress{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(i.ObjectMeta), - IngressSpec: i.Spec, - IngressStatus: i.Status, - } - results = append(results, k8s.ToMap(item)) - } - - if ingresses.Continue == "" { - break - } - options.Continue = ingresses.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/networking/ingress_class.go b/infrastructure/kubequery/internal/k8s/networking/ingress_class.go deleted file mode 100644 index 89090a51e1..0000000000 --- a/infrastructure/kubequery/internal/k8s/networking/ingress_class.go +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package networking - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/networking/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type ingressClass struct { - k8s.CommonFields - v1.IngressClassSpec -} - -// IngressClassColumns returns kubernetes ingress class fields as Osquery table columns. -func IngressClassColumns() []table.ColumnDefinition { - return k8s.GetSchema(&ingressClass{}) -} - -// IngressClassesGenerate generates the kubernetes ingress classes as Osquery table data. -func IngressClassesGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - ics, err := k8s.GetClient().NetworkingV1().IngressClasses().List(ctx, options) - if err != nil { - return nil, err - } - - for _, ic := range ics.Items { - item := &ingressClass{ - CommonFields: k8s.GetCommonFields(ic.ObjectMeta), - IngressClassSpec: ic.Spec, - } - results = append(results, k8s.ToMap(item)) - } - - if ics.Continue == "" { - break - } - options.Continue = ics.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/networking/ingress_class_test.go b/infrastructure/kubequery/internal/k8s/networking/ingress_class_test.go deleted file mode 100644 index eb405d6643..0000000000 --- a/infrastructure/kubequery/internal/k8s/networking/ingress_class_test.go +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package networking - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestIngressClassesGenerate(t *testing.T) { - igcs, err := IngressClassesGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "annotations": "{\"ingressclass.kubernetes.io/is-default-class\":\"true\",\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"networking.k8s.io/v1\\\",\\\"kind\\\":\\\"IngressClass\\\",\\\"metadata\\\":{\\\"annotations\\\":{\\\"ingressclass.kubernetes.io/is-default-class\\\":\\\"true\\\"},\\\"name\\\":\\\"public\\\"},\\\"spec\\\":{\\\"controller\\\":\\\"k8s.io/ingress-nginx\\\"}}\\n\"}", - "cluster_uid": "c7fd8e77-93de-4742-9037-5db9a01e966a", - "controller": "k8s.io/ingress-nginx", - "creation_timestamp": "1611191047", - "name": "public", - "uid": "dab8c076-3158-4a4a-8ee4-5632990ce074", - }, - }, igcs) -} diff --git a/infrastructure/kubequery/internal/k8s/networking/ingress_test.go b/infrastructure/kubequery/internal/k8s/networking/ingress_test.go deleted file mode 100644 index 00139e7e40..0000000000 --- a/infrastructure/kubequery/internal/k8s/networking/ingress_test.go +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package networking - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestIngressesGenerate(t *testing.T) { - igs, err := IngressesGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "cluster_uid": "c7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611191344", - "default_backend": "{\"service\":{\"name\":\"simplest-query\",\"port\":{\"number\":16686}}}", - "labels": "{\"app\":\"jaeger\",\"app.kubernetes.io/component\":\"query-ingress\",\"app.kubernetes.io/instance\":\"simplest\",\"app.kubernetes.io/managed-by\":\"jaeger-operator\",\"app.kubernetes.io/name\":\"simplest-query\",\"app.kubernetes.io/part-of\":\"jaeger\"}", - "load_balancer": "{}", - "name": "simplest-query", - "namespace": "default", - "uid": "0cdc9181-0cb1-43bd-97b4-e31c864a13e2", - }, - }, igs) -} diff --git a/infrastructure/kubequery/internal/k8s/networking/init_test.go b/infrastructure/kubequery/internal/k8s/networking/init_test.go deleted file mode 100644 index 42b0f88553..0000000000 --- a/infrastructure/kubequery/internal/k8s/networking/init_test.go +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package networking - -import ( - "encoding/json" - "io/ioutil" - "path/filepath" - - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/networking/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/fake" -) - -func loadTestResource(name string, v interface{}) { - path := filepath.Join("testdata", name) - data, err := ioutil.ReadFile(path) - if err != nil { - panic(err) - } - - err = json.Unmarshal(data, v) - if err != nil { - panic(err) - } -} - -func init() { - ig := &v1.Ingress{} - loadTestResource("ingress_test.json", ig) - igc := &v1.IngressClass{} - loadTestResource("ingress_class_test.json", igc) - npl := &v1.NetworkPolicyList{} - loadTestResource("network_policy_test.json", npl) - - k8s.SetClient(fake.NewSimpleClientset(ig, igc, npl), types.UID("c7fd8e77-93de-4742-9037-5db9a01e966a"), "") -} diff --git a/infrastructure/kubequery/internal/k8s/networking/network_policy.go b/infrastructure/kubequery/internal/k8s/networking/network_policy.go deleted file mode 100644 index b31203049a..0000000000 --- a/infrastructure/kubequery/internal/k8s/networking/network_policy.go +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package networking - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/networking/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type networkPolicy struct { - k8s.CommonNamespacedFields - PodSelector metav1.LabelSelector - PolicyTypes []v1.PolicyType - Type string - Ports []v1.NetworkPolicyPort - FromTo []v1.NetworkPolicyPeer -} - -// NetworkPolicyColumns returns kubernetes network policy fields as Osquery table columns. -func NetworkPolicyColumns() []table.ColumnDefinition { - return k8s.GetSchema(&networkPolicy{}) -} - -// NetworkPoliciesGenerate generates the kubernetes network policies as Osquery table data. -func NetworkPoliciesGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - nps, err := k8s.GetClient().NetworkingV1().NetworkPolicies(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, np := range nps.Items { - for _, i := range np.Spec.Ingress { - item := &networkPolicy{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(np.ObjectMeta), - PodSelector: np.Spec.PodSelector, - PolicyTypes: np.Spec.PolicyTypes, - Type: "ingress", - Ports: i.Ports, - FromTo: i.From, - } - results = append(results, k8s.ToMap(item)) - } - for _, e := range np.Spec.Egress { - item := &networkPolicy{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(np.ObjectMeta), - PodSelector: np.Spec.PodSelector, - PolicyTypes: np.Spec.PolicyTypes, - Type: "egress", - Ports: e.Ports, - FromTo: e.To, - } - results = append(results, k8s.ToMap(item)) - } - } - - if nps.Continue == "" { - break - } - options.Continue = nps.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/networking/network_policy_test.go b/infrastructure/kubequery/internal/k8s/networking/network_policy_test.go deleted file mode 100644 index 5467b3ca5a..0000000000 --- a/infrastructure/kubequery/internal/k8s/networking/network_policy_test.go +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package networking - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestNetworkPoliciesGenerate(t *testing.T) { - nps, err := NetworkPoliciesGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "annotations": "{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"networking.k8s.io/v1\\\",\\\"kind\\\":\\\"NetworkPolicy\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"name\\\":\\\"test-network-policy\\\",\\\"namespace\\\":\\\"default\\\"},\\\"spec\\\":{\\\"egress\\\":[{\\\"ports\\\":[{\\\"port\\\":5978,\\\"protocol\\\":\\\"TCP\\\"}],\\\"to\\\":[{\\\"ipBlock\\\":{\\\"cidr\\\":\\\"10.0.0.0/24\\\"}}]}],\\\"ingress\\\":[{\\\"from\\\":[{\\\"ipBlock\\\":{\\\"cidr\\\":\\\"172.17.0.0/16\\\",\\\"except\\\":[\\\"172.17.1.0/24\\\"]}},{\\\"namespaceSelector\\\":{\\\"matchLabels\\\":{\\\"project\\\":\\\"myproject\\\"}}},{\\\"podSelector\\\":{\\\"matchLabels\\\":{\\\"role\\\":\\\"frontend\\\"}}}],\\\"ports\\\":[{\\\"port\\\":6379,\\\"protocol\\\":\\\"TCP\\\"}]}],\\\"podSelector\\\":{\\\"matchLabels\\\":{\\\"role\\\":\\\"db\\\"}},\\\"policyTypes\\\":[\\\"Ingress\\\",\\\"Egress\\\"]}}\\n\"}", - "cluster_uid": "c7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611328106", - "from_to": "[{\"ipBlock\":{\"cidr\":\"172.17.0.0/16\",\"except\":[\"172.17.1.0/24\"]}},{\"namespaceSelector\":{\"matchLabels\":{\"project\":\"myproject\"}}},{\"podSelector\":{\"matchLabels\":{\"role\":\"frontend\"}}}]", - "name": "test-network-policy", - "namespace": "default", - "pod_selector": "{\"matchLabels\":{\"role\":\"db\"}}", - "policy_types": "[\"Ingress\",\"Egress\"]", - "ports": "[{\"protocol\":\"TCP\",\"port\":6379}]", - "type": "ingress", - "uid": "ef70a000-9460-4098-9100-1d2b4bf608e1", - }, - { - "annotations": "{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"networking.k8s.io/v1\\\",\\\"kind\\\":\\\"NetworkPolicy\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"name\\\":\\\"test-network-policy\\\",\\\"namespace\\\":\\\"default\\\"},\\\"spec\\\":{\\\"egress\\\":[{\\\"ports\\\":[{\\\"port\\\":5978,\\\"protocol\\\":\\\"TCP\\\"}],\\\"to\\\":[{\\\"ipBlock\\\":{\\\"cidr\\\":\\\"10.0.0.0/24\\\"}}]}],\\\"ingress\\\":[{\\\"from\\\":[{\\\"ipBlock\\\":{\\\"cidr\\\":\\\"172.17.0.0/16\\\",\\\"except\\\":[\\\"172.17.1.0/24\\\"]}},{\\\"namespaceSelector\\\":{\\\"matchLabels\\\":{\\\"project\\\":\\\"myproject\\\"}}},{\\\"podSelector\\\":{\\\"matchLabels\\\":{\\\"role\\\":\\\"frontend\\\"}}}],\\\"ports\\\":[{\\\"port\\\":6379,\\\"protocol\\\":\\\"TCP\\\"}]}],\\\"podSelector\\\":{\\\"matchLabels\\\":{\\\"role\\\":\\\"db\\\"}},\\\"policyTypes\\\":[\\\"Ingress\\\",\\\"Egress\\\"]}}\\n\"}", - "cluster_uid": "c7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611328106", - "from_to": "[{\"ipBlock\":{\"cidr\":\"10.0.0.0/24\"}}]", - "name": "test-network-policy", - "namespace": "default", - "pod_selector": "{\"matchLabels\":{\"role\":\"db\"}}", - "policy_types": "[\"Ingress\",\"Egress\"]", - "ports": "[{\"protocol\":\"TCP\",\"port\":5978}]", - "type": "egress", - "uid": "ef70a000-9460-4098-9100-1d2b4bf608e1", - }, - }, nps) -} diff --git a/infrastructure/kubequery/internal/k8s/networking/testdata/ingress_class_test.json b/infrastructure/kubequery/internal/k8s/networking/testdata/ingress_class_test.json deleted file mode 100644 index 1b4c8d62e5..0000000000 --- a/infrastructure/kubequery/internal/k8s/networking/testdata/ingress_class_test.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "apiVersion": "networking.k8s.io/v1", - "kind": "IngressClass", - "metadata": { - "annotations": { - "ingressclass.kubernetes.io/is-default-class": "true", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"networking.k8s.io/v1\",\"kind\":\"IngressClass\",\"metadata\":{\"annotations\":{\"ingressclass.kubernetes.io/is-default-class\":\"true\"},\"name\":\"public\"},\"spec\":{\"controller\":\"k8s.io/ingress-nginx\"}}\n" - }, - "creationTimestamp": "2021-01-21T01:04:07Z", - "generation": 1, - "managedFields": [ - { - "apiVersion": "networking.k8s.io/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:ingressclass.kubernetes.io/is-default-class": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - } - }, - "f:spec": { - "f:controller": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-21T01:04:07Z" - } - ], - "name": "public", - "resourceVersion": "450123", - "selfLink": "/apis/networking.k8s.io/v1/ingressclasses/public", - "uid": "dab8c076-3158-4a4a-8ee4-5632990ce074" - }, - "spec": { - "controller": "k8s.io/ingress-nginx" - } -} diff --git a/infrastructure/kubequery/internal/k8s/networking/testdata/ingress_test.json b/infrastructure/kubequery/internal/k8s/networking/testdata/ingress_test.json deleted file mode 100644 index 518dc0edf7..0000000000 --- a/infrastructure/kubequery/internal/k8s/networking/testdata/ingress_test.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "apiVersion": "networking.k8s.io/v1", - "kind": "Ingress", - "metadata": { - "creationTimestamp": "2021-01-21T01:09:04Z", - "generation": 1, - "labels": { - "app": "jaeger", - "app.kubernetes.io/component": "query-ingress", - "app.kubernetes.io/instance": "simplest", - "app.kubernetes.io/managed-by": "jaeger-operator", - "app.kubernetes.io/name": "simplest-query", - "app.kubernetes.io/part-of": "jaeger" - }, - "managedFields": [ - { - "apiVersion": "extensions/v1beta1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:labels": { - ".": {}, - "f:app": {}, - "f:app.kubernetes.io/component": {}, - "f:app.kubernetes.io/instance": {}, - "f:app.kubernetes.io/managed-by": {}, - "f:app.kubernetes.io/name": {}, - "f:app.kubernetes.io/part-of": {} - }, - "f:ownerReferences": { - ".": {}, - "k:{\"uid\":\"95d303e8-d347-4f8f-b008-a4da3e44b847\"}": { - ".": {}, - "f:apiVersion": {}, - "f:controller": {}, - "f:kind": {}, - "f:name": {}, - "f:uid": {} - } - } - }, - "f:spec": { - "f:backend": { - ".": {}, - "f:serviceName": {}, - "f:servicePort": {} - } - } - }, - "manager": "jaeger-operator", - "operation": "Update", - "time": "2021-01-21T01:09:04Z" - } - ], - "name": "simplest-query", - "namespace": "default", - "ownerReferences": [ - { - "apiVersion": "jaegertracing.io/v1", - "controller": true, - "kind": "Jaeger", - "name": "simplest", - "uid": "95d303e8-d347-4f8f-b008-a4da3e44b847" - } - ], - "resourceVersion": "451926", - "selfLink": "/apis/networking.k8s.io/v1/namespaces/default/ingresses/simplest-query", - "uid": "0cdc9181-0cb1-43bd-97b4-e31c864a13e2" - }, - "spec": { - "defaultBackend": { - "service": { - "name": "simplest-query", - "port": { - "number": 16686 - } - } - } - }, - "status": { - "loadBalancer": {} - } -} diff --git a/infrastructure/kubequery/internal/k8s/networking/testdata/network_policy_test.json b/infrastructure/kubequery/internal/k8s/networking/testdata/network_policy_test.json deleted file mode 100644 index 76b84d861b..0000000000 --- a/infrastructure/kubequery/internal/k8s/networking/testdata/network_policy_test.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "apiVersion": "v1", - "items": [ - { - "apiVersion": "networking.k8s.io/v1", - "kind": "NetworkPolicy", - "metadata": { - "annotations": { - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"networking.k8s.io/v1\",\"kind\":\"NetworkPolicy\",\"metadata\":{\"annotations\":{},\"name\":\"test-network-policy\",\"namespace\":\"default\"},\"spec\":{\"egress\":[{\"ports\":[{\"port\":5978,\"protocol\":\"TCP\"}],\"to\":[{\"ipBlock\":{\"cidr\":\"10.0.0.0/24\"}}]}],\"ingress\":[{\"from\":[{\"ipBlock\":{\"cidr\":\"172.17.0.0/16\",\"except\":[\"172.17.1.0/24\"]}},{\"namespaceSelector\":{\"matchLabels\":{\"project\":\"myproject\"}}},{\"podSelector\":{\"matchLabels\":{\"role\":\"frontend\"}}}],\"ports\":[{\"port\":6379,\"protocol\":\"TCP\"}]}],\"podSelector\":{\"matchLabels\":{\"role\":\"db\"}},\"policyTypes\":[\"Ingress\",\"Egress\"]}}\n" - }, - "creationTimestamp": "2021-01-22T15:08:26Z", - "generation": 1, - "managedFields": [ - { - "apiVersion": "networking.k8s.io/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - } - }, - "f:spec": { - "f:egress": {}, - "f:ingress": {}, - "f:podSelector": {}, - "f:policyTypes": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-22T15:08:26Z" - } - ], - "name": "test-network-policy", - "namespace": "default", - "resourceVersion": "536004", - "selfLink": "/apis/networking.k8s.io/v1/namespaces/default/networkpolicies/test-network-policy", - "uid": "ef70a000-9460-4098-9100-1d2b4bf608e1" - }, - "spec": { - "egress": [ - { - "ports": [ - { - "port": 5978, - "protocol": "TCP" - } - ], - "to": [ - { - "ipBlock": { - "cidr": "10.0.0.0/24" - } - } - ] - } - ], - "ingress": [ - { - "from": [ - { - "ipBlock": { - "cidr": "172.17.0.0/16", - "except": [ - "172.17.1.0/24" - ] - } - }, - { - "namespaceSelector": { - "matchLabels": { - "project": "myproject" - } - } - }, - { - "podSelector": { - "matchLabels": { - "role": "frontend" - } - } - } - ], - "ports": [ - { - "port": 6379, - "protocol": "TCP" - } - ] - } - ], - "podSelector": { - "matchLabels": { - "role": "db" - } - }, - "policyTypes": [ - "Ingress", - "Egress" - ] - } - } - ], - "kind": "List", - "metadata": { - "resourceVersion": "", - "selfLink": "" - } -} diff --git a/infrastructure/kubequery/internal/k8s/policy/init_test.go b/infrastructure/kubequery/internal/k8s/policy/init_test.go deleted file mode 100644 index ba9b3b5f43..0000000000 --- a/infrastructure/kubequery/internal/k8s/policy/init_test.go +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package policy - -import ( - "encoding/json" - "io/ioutil" - "path/filepath" - - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/policy/v1" - v1beta1 "k8s.io/api/policy/v1beta1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/fake" -) - -func loadTestResource(name string, v interface{}) { - path := filepath.Join("testdata", name) - data, err := ioutil.ReadFile(path) - if err != nil { - panic(err) - } - - err = json.Unmarshal(data, v) - if err != nil { - panic(err) - } -} - -func init() { - pdb := &v1.PodDisruptionBudget{} - loadTestResource("pod_disruption_budget_test.json", pdb) - psp := &v1beta1.PodSecurityPolicy{} - loadTestResource("pod_security_policy_test.json", psp) - - k8s.SetClient(fake.NewSimpleClientset(pdb, psp), types.UID("b7fd8e77-93de-4742-9037-5db9a01e966a"), "") -} diff --git a/infrastructure/kubequery/internal/k8s/policy/pod_disruption_budget.go b/infrastructure/kubequery/internal/k8s/policy/pod_disruption_budget.go deleted file mode 100644 index 2af59d6d4a..0000000000 --- a/infrastructure/kubequery/internal/k8s/policy/pod_disruption_budget.go +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package policy - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/policy/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type podDisruptionBudget struct { - k8s.CommonNamespacedFields - v1.PodDisruptionBudgetSpec - v1.PodDisruptionBudgetStatus -} - -// PodDisruptionBudgetColumns returns kubernetes pod disruption budget fields as Osquery table columns. -func PodDisruptionBudgetColumns() []table.ColumnDefinition { - return k8s.GetSchema(&podDisruptionBudget{}) -} - -// PodDisruptionBudgetsGenerate generates the kubernetes pod disruption budgets as Osquery table data. -func PodDisruptionBudgetsGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - pdbs, err := k8s.GetClient().PolicyV1().PodDisruptionBudgets(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, pdb := range pdbs.Items { - item := &podDisruptionBudget{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(pdb.ObjectMeta), - PodDisruptionBudgetSpec: pdb.Spec, - PodDisruptionBudgetStatus: pdb.Status, - } - results = append(results, k8s.ToMap(item)) - } - - if pdbs.Continue == "" { - break - } - options.Continue = pdbs.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/policy/pod_disruption_budget_test.go b/infrastructure/kubequery/internal/k8s/policy/pod_disruption_budget_test.go deleted file mode 100644 index bbf0aff161..0000000000 --- a/infrastructure/kubequery/internal/k8s/policy/pod_disruption_budget_test.go +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package policy - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestPodDisruptionBudgetsGenerate(t *testing.T) { - pdbs, err := PodDisruptionBudgetsGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "annotations": "{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"policy/v1\\\",\\\"kind\\\":\\\"PodDisruptionBudget\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"labels\\\":{\\\"app\\\":\\\"policy\\\",\\\"chart\\\":\\\"mixer\\\",\\\"heritage\\\":\\\"Tiller\\\",\\\"istio\\\":\\\"mixer\\\",\\\"istio-mixer-type\\\":\\\"policy\\\",\\\"release\\\":\\\"istio\\\",\\\"version\\\":\\\"1.5.1\\\"},\\\"name\\\":\\\"istio-policy\\\",\\\"namespace\\\":\\\"istio-system\\\"},\\\"spec\\\":{\\\"minAvailable\\\":1,\\\"selector\\\":{\\\"matchLabels\\\":{\\\"app\\\":\\\"policy\\\",\\\"istio\\\":\\\"mixer\\\",\\\"istio-mixer-type\\\":\\\"policy\\\",\\\"release\\\":\\\"istio\\\"}}}}\\n\"}", - "cluster_uid": "b7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611191143", - "current_healthy": "1", - "desired_healthy": "1", - "disruptions_allowed": "0", - "expected_pods": "1", - "labels": "{\"app\":\"policy\",\"chart\":\"mixer\",\"heritage\":\"Tiller\",\"istio\":\"mixer\",\"istio-mixer-type\":\"policy\",\"release\":\"istio\",\"version\":\"1.5.1\"}", - "min_available": "1", - "name": "istio-policy", - "namespace": "istio-system", - "observed_generation": "1", - "selector": "{\"matchLabels\":{\"app\":\"policy\",\"istio\":\"mixer\",\"istio-mixer-type\":\"policy\",\"release\":\"istio\"}}", - "uid": "77dc4487-d95d-40a9-8fdb-f3bbe334c4e3", - }, - }, pdbs) -} diff --git a/infrastructure/kubequery/internal/k8s/policy/pod_security_policy.go b/infrastructure/kubequery/internal/k8s/policy/pod_security_policy.go deleted file mode 100644 index 36a3478f0e..0000000000 --- a/infrastructure/kubequery/internal/k8s/policy/pod_security_policy.go +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package policy - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1beta1 "k8s.io/api/policy/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type podSecurityPolicy struct { - k8s.CommonFields - v1beta1.PodSecurityPolicySpec -} - -// PodSecurityPolicyColumns returns kubernetes pod security policy fields as Osquery table columns. -func PodSecurityPolicyColumns() []table.ColumnDefinition { - return k8s.GetSchema(&podSecurityPolicy{}) -} - -// PodSecurityPoliciesGenerate generates the kubernetes pod security policies as Osquery table data. -func PodSecurityPoliciesGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - psps, err := k8s.GetClient().PolicyV1beta1().PodSecurityPolicies().List(ctx, options) - if err != nil { - return nil, err - } - - for _, psp := range psps.Items { - item := &podSecurityPolicy{ - CommonFields: k8s.GetCommonFields(psp.ObjectMeta), - PodSecurityPolicySpec: psp.Spec, - } - results = append(results, k8s.ToMap(item)) - } - - if psps.Continue == "" { - break - } - options.Continue = psps.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/policy/pod_security_policy_test.go b/infrastructure/kubequery/internal/k8s/policy/pod_security_policy_test.go deleted file mode 100644 index e48f1a575e..0000000000 --- a/infrastructure/kubequery/internal/k8s/policy/pod_security_policy_test.go +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package policy - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestPodSecurityPoliciesGenerate(t *testing.T) { - psps, err := PodSecurityPoliciesGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "allow_privilege_escalation": "0", - "annotations": "{\"apparmor.security.beta.kubernetes.io/allowedProfileNames\":\"runtime/default\",\"apparmor.security.beta.kubernetes.io/defaultProfileName\":\"runtime/default\",\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"policy/v1beta1\\\",\\\"kind\\\":\\\"PodSecurityPolicy\\\",\\\"metadata\\\":{\\\"annotations\\\":{\\\"apparmor.security.beta.kubernetes.io/allowedProfileNames\\\":\\\"runtime/default\\\",\\\"apparmor.security.beta.kubernetes.io/defaultProfileName\\\":\\\"runtime/default\\\",\\\"seccomp.security.alpha.kubernetes.io/allowedProfileNames\\\":\\\"docker/default,runtime/default\\\",\\\"seccomp.security.alpha.kubernetes.io/defaultProfileName\\\":\\\"runtime/default\\\"},\\\"name\\\":\\\"restricted\\\"},\\\"spec\\\":{\\\"allowPrivilegeEscalation\\\":false,\\\"fsGroup\\\":{\\\"ranges\\\":[{\\\"max\\\":65535,\\\"min\\\":1}],\\\"rule\\\":\\\"MustRunAs\\\"},\\\"hostIPC\\\":false,\\\"hostNetwork\\\":false,\\\"hostPID\\\":false,\\\"privileged\\\":false,\\\"readOnlyRootFilesystem\\\":false,\\\"requiredDropCapabilities\\\":[\\\"ALL\\\"],\\\"runAsUser\\\":{\\\"rule\\\":\\\"MustRunAsNonRoot\\\"},\\\"seLinux\\\":{\\\"rule\\\":\\\"RunAsAny\\\"},\\\"supplementalGroups\\\":{\\\"ranges\\\":[{\\\"max\\\":65535,\\\"min\\\":1}],\\\"rule\\\":\\\"MustRunAs\\\"},\\\"volumes\\\":[\\\"configMap\\\",\\\"emptyDir\\\",\\\"projected\\\",\\\"secret\\\",\\\"downwardAPI\\\",\\\"persistentVolumeClaim\\\"]}}\\n\",\"seccomp.security.alpha.kubernetes.io/allowedProfileNames\":\"docker/default,runtime/default\",\"seccomp.security.alpha.kubernetes.io/defaultProfileName\":\"runtime/default\"}", - "cluster_uid": "b7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611164232", - "fs_group": "{\"rule\":\"MustRunAs\",\"ranges\":[{\"min\":1,\"max\":65535}]}", - "host_ipc": "0", - "host_network": "0", - "host_pid": "0", - "name": "restricted", - "privileged": "0", - "read_only_root_filesystem": "0", - "required_drop_capabilities": "[\"ALL\"]", - "run_as_user": "{\"rule\":\"MustRunAsNonRoot\"}", - "se_linux": "{\"rule\":\"RunAsAny\"}", - "supplemental_groups": "{\"rule\":\"MustRunAs\",\"ranges\":[{\"min\":1,\"max\":65535}]}", - "uid": "de6eb036-24db-4490-8811-590a2c2e1529", - "volumes": "[\"configMap\",\"emptyDir\",\"projected\",\"secret\",\"downwardAPI\",\"persistentVolumeClaim\"]", - }, - }, psps) -} diff --git a/infrastructure/kubequery/internal/k8s/policy/testdata/pod_disruption_budget_test.json b/infrastructure/kubequery/internal/k8s/policy/testdata/pod_disruption_budget_test.json deleted file mode 100644 index ddfb9c077e..0000000000 --- a/infrastructure/kubequery/internal/k8s/policy/testdata/pod_disruption_budget_test.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "apiVersion": "policy/v1", - "kind": "PodDisruptionBudget", - "metadata": { - "annotations": { - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"policy/v1\",\"kind\":\"PodDisruptionBudget\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"policy\",\"chart\":\"mixer\",\"heritage\":\"Tiller\",\"istio\":\"mixer\",\"istio-mixer-type\":\"policy\",\"release\":\"istio\",\"version\":\"1.5.1\"},\"name\":\"istio-policy\",\"namespace\":\"istio-system\"},\"spec\":{\"minAvailable\":1,\"selector\":{\"matchLabels\":{\"app\":\"policy\",\"istio\":\"mixer\",\"istio-mixer-type\":\"policy\",\"release\":\"istio\"}}}}\n" - }, - "creationTimestamp": "2021-01-21T01:05:43Z", - "generation": 1, - "labels": { - "app": "policy", - "chart": "mixer", - "heritage": "Tiller", - "istio": "mixer", - "istio-mixer-type": "policy", - "release": "istio", - "version": "1.5.1" - }, - "managedFields": [ - { - "apiVersion": "policy/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:app": {}, - "f:chart": {}, - "f:heritage": {}, - "f:istio": {}, - "f:istio-mixer-type": {}, - "f:release": {}, - "f:version": {} - } - }, - "f:spec": { - "f:minAvailable": {}, - "f:selector": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-21T01:05:43Z" - }, - { - "apiVersion": "policy/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:status": { - "f:currentHealthy": {}, - "f:desiredHealthy": {}, - "f:expectedPods": {}, - "f:observedGeneration": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-21T01:06:23Z" - } - ], - "name": "istio-policy", - "namespace": "istio-system", - "resourceVersion": "451148", - "selfLink": "/apis/policy/v1/namespaces/istio-system/poddisruptionbudgets/istio-policy", - "uid": "77dc4487-d95d-40a9-8fdb-f3bbe334c4e3" - }, - "spec": { - "minAvailable": 1, - "selector": { - "matchLabels": { - "app": "policy", - "istio": "mixer", - "istio-mixer-type": "policy", - "release": "istio" - } - } - }, - "status": { - "currentHealthy": 1, - "desiredHealthy": 1, - "disruptionsAllowed": 0, - "expectedPods": 1, - "observedGeneration": 1 - } -} diff --git a/infrastructure/kubequery/internal/k8s/policy/testdata/pod_security_policy_test.json b/infrastructure/kubequery/internal/k8s/policy/testdata/pod_security_policy_test.json deleted file mode 100644 index 1fb6795585..0000000000 --- a/infrastructure/kubequery/internal/k8s/policy/testdata/pod_security_policy_test.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "apiVersion": "policy/v1beta1", - "kind": "PodSecurityPolicy", - "metadata": { - "annotations": { - "apparmor.security.beta.kubernetes.io/allowedProfileNames": "runtime/default", - "apparmor.security.beta.kubernetes.io/defaultProfileName": "runtime/default", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"policy/v1beta1\",\"kind\":\"PodSecurityPolicy\",\"metadata\":{\"annotations\":{\"apparmor.security.beta.kubernetes.io/allowedProfileNames\":\"runtime/default\",\"apparmor.security.beta.kubernetes.io/defaultProfileName\":\"runtime/default\",\"seccomp.security.alpha.kubernetes.io/allowedProfileNames\":\"docker/default,runtime/default\",\"seccomp.security.alpha.kubernetes.io/defaultProfileName\":\"runtime/default\"},\"name\":\"restricted\"},\"spec\":{\"allowPrivilegeEscalation\":false,\"fsGroup\":{\"ranges\":[{\"max\":65535,\"min\":1}],\"rule\":\"MustRunAs\"},\"hostIPC\":false,\"hostNetwork\":false,\"hostPID\":false,\"privileged\":false,\"readOnlyRootFilesystem\":false,\"requiredDropCapabilities\":[\"ALL\"],\"runAsUser\":{\"rule\":\"MustRunAsNonRoot\"},\"seLinux\":{\"rule\":\"RunAsAny\"},\"supplementalGroups\":{\"ranges\":[{\"max\":65535,\"min\":1}],\"rule\":\"MustRunAs\"},\"volumes\":[\"configMap\",\"emptyDir\",\"projected\",\"secret\",\"downwardAPI\",\"persistentVolumeClaim\"]}}\n", - "seccomp.security.alpha.kubernetes.io/allowedProfileNames": "docker/default,runtime/default", - "seccomp.security.alpha.kubernetes.io/defaultProfileName": "runtime/default" - }, - "creationTimestamp": "2021-01-20T17:37:12Z", - "managedFields": [ - { - "apiVersion": "policy/v1beta1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:apparmor.security.beta.kubernetes.io/allowedProfileNames": {}, - "f:apparmor.security.beta.kubernetes.io/defaultProfileName": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {}, - "f:seccomp.security.alpha.kubernetes.io/allowedProfileNames": {}, - "f:seccomp.security.alpha.kubernetes.io/defaultProfileName": {} - } - }, - "f:spec": { - "f:allowPrivilegeEscalation": {}, - "f:fsGroup": { - "f:ranges": {}, - "f:rule": {} - }, - "f:requiredDropCapabilities": {}, - "f:runAsUser": { - "f:rule": {} - }, - "f:seLinux": { - "f:rule": {} - }, - "f:supplementalGroups": { - "f:ranges": {}, - "f:rule": {} - }, - "f:volumes": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-20T17:37:12Z" - } - ], - "name": "restricted", - "resourceVersion": "421294", - "selfLink": "/apis/policy/v1beta1/podsecuritypolicies/restricted", - "uid": "de6eb036-24db-4490-8811-590a2c2e1529" - }, - "spec": { - "allowPrivilegeEscalation": false, - "fsGroup": { - "ranges": [ - { - "max": 65535, - "min": 1 - } - ], - "rule": "MustRunAs" - }, - "requiredDropCapabilities": [ - "ALL" - ], - "runAsUser": { - "rule": "MustRunAsNonRoot" - }, - "seLinux": { - "rule": "RunAsAny" - }, - "supplementalGroups": { - "ranges": [ - { - "max": 65535, - "min": 1 - } - ], - "rule": "MustRunAs" - }, - "volumes": [ - "configMap", - "emptyDir", - "projected", - "secret", - "downwardAPI", - "persistentVolumeClaim" - ] - } -} diff --git a/infrastructure/kubequery/internal/k8s/rbac/cluster_role_binding_subject.go b/infrastructure/kubequery/internal/k8s/rbac/cluster_role_binding_subject.go deleted file mode 100644 index bffc63ca54..0000000000 --- a/infrastructure/kubequery/internal/k8s/rbac/cluster_role_binding_subject.go +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package rbac - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type clusterRoleBindingSubject struct { - k8s.CommonFields - RoleAPIGroup string - RoleName string - RoleKind string - SubjectName string - SubjectKind string - SubjectNamespace string -} - -// ClusterRoleBindingSubjectColumns returns kubernetes cluster role binding subject fields as Osquery table columns. -func ClusterRoleBindingSubjectColumns() []table.ColumnDefinition { - return k8s.GetSchema(&clusterRoleBindingSubject{}) -} - -// ClusterRoleBindingSubjectsGenerate generates the kubernetes cluster role binding subjects as Osquery table data. -func ClusterRoleBindingSubjectsGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - crbs, err := k8s.GetClient().RbacV1().ClusterRoleBindings().List(ctx, options) - if err != nil { - return nil, err - } - - for _, crb := range crbs.Items { - for _, s := range crb.Subjects { - item := &clusterRoleBindingSubject{ - CommonFields: k8s.GetCommonFields(crb.ObjectMeta), - RoleAPIGroup: crb.RoleRef.APIGroup, - RoleName: crb.RoleRef.Name, - RoleKind: crb.RoleRef.Kind, - SubjectName: s.Name, - SubjectKind: s.Kind, - SubjectNamespace: s.Namespace, - } - results = append(results, k8s.ToMap(item)) - } - } - - if crbs.Continue == "" { - break - } - options.Continue = crbs.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/rbac/cluster_role_binding_subject_test.go b/infrastructure/kubequery/internal/k8s/rbac/cluster_role_binding_subject_test.go deleted file mode 100644 index d710b7553d..0000000000 --- a/infrastructure/kubequery/internal/k8s/rbac/cluster_role_binding_subject_test.go +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package rbac - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestClusterRoleBindingSubjectsGenerate(t *testing.T) { - crbss, err := ClusterRoleBindingSubjectsGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "annotations": "{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"rbac.authorization.k8s.io/v1\\\",\\\"kind\\\":\\\"ClusterRoleBinding\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"name\\\":\\\"kubernetes-dashboard\\\"},\\\"roleRef\\\":{\\\"apiGroup\\\":\\\"rbac.authorization.k8s.io\\\",\\\"kind\\\":\\\"ClusterRole\\\",\\\"name\\\":\\\"kubernetes-dashboard\\\"},\\\"subjects\\\":[{\\\"kind\\\":\\\"ServiceAccount\\\",\\\"name\\\":\\\"kubernetes-dashboard\\\",\\\"namespace\\\":\\\"kube-system\\\"}]}\\n\"}", - "cluster_uid": "a7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611190911", - "name": "kubernetes-dashboard", - "role_api_group": "rbac.authorization.k8s.io", - "role_kind": "ClusterRole", - "role_name": "kubernetes-dashboard", - "subject_kind": "ServiceAccount", - "subject_name": "kubernetes-dashboard", - "subject_namespace": "kube-system", - "uid": "7e3bf161-3a4e-495d-98a8-f71248d0ba36", - }, - { - "annotations": "{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"rbac.authorization.k8s.io/v1\\\",\\\"kind\\\":\\\"ClusterRoleBinding\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"name\\\":\\\"nginx-ingress-microk8s\\\"},\\\"roleRef\\\":{\\\"apiGroup\\\":\\\"rbac.authorization.k8s.io\\\",\\\"kind\\\":\\\"ClusterRole\\\",\\\"name\\\":\\\"nginx-ingress-microk8s-clusterrole\\\"},\\\"subjects\\\":[{\\\"kind\\\":\\\"ServiceAccount\\\",\\\"name\\\":\\\"nginx-ingress-microk8s-serviceaccount\\\",\\\"namespace\\\":\\\"ingress\\\"}]}\\n\"}", - "cluster_uid": "a7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611191047", - "name": "nginx-ingress-microk8s", - "role_api_group": "rbac.authorization.k8s.io", - "role_kind": "ClusterRole", - "role_name": "nginx-ingress-microk8s-clusterrole", - "subject_kind": "ServiceAccount", - "subject_name": "nginx-ingress-microk8s-serviceaccount", - "subject_namespace": "ingress", - "uid": "aa9c6e0e-3dd4-4da3-936a-a6edea62c7b7", - }, - }, crbss) -} diff --git a/infrastructure/kubequery/internal/k8s/rbac/cluster_role_policy_rule.go b/infrastructure/kubequery/internal/k8s/rbac/cluster_role_policy_rule.go deleted file mode 100644 index c784bf64ef..0000000000 --- a/infrastructure/kubequery/internal/k8s/rbac/cluster_role_policy_rule.go +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package rbac - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type clusterRolePolicyRule struct { - k8s.CommonFields - v1.PolicyRule - AggregationRule *v1.AggregationRule -} - -// ClusterRolePolicyRuleColumns returns kubernetes cluster role policy rule fields as Osquery table columns. -func ClusterRolePolicyRuleColumns() []table.ColumnDefinition { - return k8s.GetSchema(&clusterRolePolicyRule{}) -} - -// ClusterRolePolicyRulesGenerate generates the kubernetes cluster role policy rules as Osquery table data. -func ClusterRolePolicyRulesGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - crs, err := k8s.GetClient().RbacV1().ClusterRoles().List(ctx, options) - if err != nil { - return nil, err - } - - for _, cr := range crs.Items { - for _, r := range cr.Rules { - item := &clusterRolePolicyRule{ - CommonFields: k8s.GetCommonFields(cr.ObjectMeta), - PolicyRule: r, - AggregationRule: cr.AggregationRule, - } - results = append(results, k8s.ToMap(item)) - } - } - - if crs.Continue == "" { - break - } - options.Continue = crs.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/rbac/cluster_role_policy_rule_test.go b/infrastructure/kubequery/internal/k8s/rbac/cluster_role_policy_rule_test.go deleted file mode 100644 index 17e0014776..0000000000 --- a/infrastructure/kubequery/internal/k8s/rbac/cluster_role_policy_rule_test.go +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package rbac - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestClusterRolePolicyRulesGenerate(t *testing.T) { - crprs, err := ClusterRolePolicyRulesGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "annotations": "{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"rbac.authorization.k8s.io/v1\\\",\\\"kind\\\":\\\"ClusterRole\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"labels\\\":{\\\"app\\\":\\\"kiali\\\",\\\"chart\\\":\\\"kiali\\\",\\\"heritage\\\":\\\"Tiller\\\",\\\"release\\\":\\\"istio\\\"},\\\"name\\\":\\\"kiali-viewer\\\"},\\\"rules\\\":[{\\\"apiGroups\\\":[\\\"\\\"],\\\"resources\\\":[\\\"configmaps\\\",\\\"endpoints\\\",\\\"namespaces\\\",\\\"nodes\\\",\\\"pods\\\",\\\"pods/log\\\",\\\"replicationcontrollers\\\",\\\"services\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"extensions\\\",\\\"apps\\\"],\\\"resources\\\":[\\\"deployments\\\",\\\"replicasets\\\",\\\"statefulsets\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"autoscaling\\\"],\\\"resources\\\":[\\\"horizontalpodautoscalers\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"batch\\\"],\\\"resources\\\":[\\\"cronjobs\\\",\\\"jobs\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"config.istio.io\\\",\\\"networking.istio.io\\\",\\\"authentication.istio.io\\\",\\\"rbac.istio.io\\\",\\\"security.istio.io\\\"],\\\"resources\\\":[\\\"*\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"monitoring.kiali.io\\\"],\\\"resources\\\":[\\\"monitoringdashboards\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\"]}]}\\n\"}", - "api_groups": "[\"\"]", - "cluster_uid": "a7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611191143", - "labels": "{\"app\":\"kiali\",\"chart\":\"kiali\",\"heritage\":\"Tiller\",\"release\":\"istio\"}", - "name": "kiali-viewer", - "resources": "[\"configmaps\",\"endpoints\",\"namespaces\",\"nodes\",\"pods\",\"pods/log\",\"replicationcontrollers\",\"services\"]", - "uid": "b5d5ca79-f4e3-4478-b954-fe62a146f279", - "verbs": "[\"get\",\"list\",\"watch\"]", - }, - { - "annotations": "{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"rbac.authorization.k8s.io/v1\\\",\\\"kind\\\":\\\"ClusterRole\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"labels\\\":{\\\"app\\\":\\\"kiali\\\",\\\"chart\\\":\\\"kiali\\\",\\\"heritage\\\":\\\"Tiller\\\",\\\"release\\\":\\\"istio\\\"},\\\"name\\\":\\\"kiali-viewer\\\"},\\\"rules\\\":[{\\\"apiGroups\\\":[\\\"\\\"],\\\"resources\\\":[\\\"configmaps\\\",\\\"endpoints\\\",\\\"namespaces\\\",\\\"nodes\\\",\\\"pods\\\",\\\"pods/log\\\",\\\"replicationcontrollers\\\",\\\"services\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"extensions\\\",\\\"apps\\\"],\\\"resources\\\":[\\\"deployments\\\",\\\"replicasets\\\",\\\"statefulsets\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"autoscaling\\\"],\\\"resources\\\":[\\\"horizontalpodautoscalers\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"batch\\\"],\\\"resources\\\":[\\\"cronjobs\\\",\\\"jobs\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"config.istio.io\\\",\\\"networking.istio.io\\\",\\\"authentication.istio.io\\\",\\\"rbac.istio.io\\\",\\\"security.istio.io\\\"],\\\"resources\\\":[\\\"*\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"monitoring.kiali.io\\\"],\\\"resources\\\":[\\\"monitoringdashboards\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\"]}]}\\n\"}", - "api_groups": "[\"extensions\",\"apps\"]", - "cluster_uid": "a7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611191143", - "labels": "{\"app\":\"kiali\",\"chart\":\"kiali\",\"heritage\":\"Tiller\",\"release\":\"istio\"}", - "name": "kiali-viewer", - "resources": "[\"deployments\",\"replicasets\",\"statefulsets\"]", - "uid": "b5d5ca79-f4e3-4478-b954-fe62a146f279", - "verbs": "[\"get\",\"list\",\"watch\"]", - }, - { - "annotations": "{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"rbac.authorization.k8s.io/v1\\\",\\\"kind\\\":\\\"ClusterRole\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"labels\\\":{\\\"app\\\":\\\"kiali\\\",\\\"chart\\\":\\\"kiali\\\",\\\"heritage\\\":\\\"Tiller\\\",\\\"release\\\":\\\"istio\\\"},\\\"name\\\":\\\"kiali-viewer\\\"},\\\"rules\\\":[{\\\"apiGroups\\\":[\\\"\\\"],\\\"resources\\\":[\\\"configmaps\\\",\\\"endpoints\\\",\\\"namespaces\\\",\\\"nodes\\\",\\\"pods\\\",\\\"pods/log\\\",\\\"replicationcontrollers\\\",\\\"services\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"extensions\\\",\\\"apps\\\"],\\\"resources\\\":[\\\"deployments\\\",\\\"replicasets\\\",\\\"statefulsets\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"autoscaling\\\"],\\\"resources\\\":[\\\"horizontalpodautoscalers\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"batch\\\"],\\\"resources\\\":[\\\"cronjobs\\\",\\\"jobs\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"config.istio.io\\\",\\\"networking.istio.io\\\",\\\"authentication.istio.io\\\",\\\"rbac.istio.io\\\",\\\"security.istio.io\\\"],\\\"resources\\\":[\\\"*\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"monitoring.kiali.io\\\"],\\\"resources\\\":[\\\"monitoringdashboards\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\"]}]}\\n\"}", - "api_groups": "[\"autoscaling\"]", - "cluster_uid": "a7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611191143", - "labels": "{\"app\":\"kiali\",\"chart\":\"kiali\",\"heritage\":\"Tiller\",\"release\":\"istio\"}", - "name": "kiali-viewer", - "resources": "[\"horizontalpodautoscalers\"]", - "uid": "b5d5ca79-f4e3-4478-b954-fe62a146f279", - "verbs": "[\"get\",\"list\",\"watch\"]", - }, - { - "annotations": "{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"rbac.authorization.k8s.io/v1\\\",\\\"kind\\\":\\\"ClusterRole\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"labels\\\":{\\\"app\\\":\\\"kiali\\\",\\\"chart\\\":\\\"kiali\\\",\\\"heritage\\\":\\\"Tiller\\\",\\\"release\\\":\\\"istio\\\"},\\\"name\\\":\\\"kiali-viewer\\\"},\\\"rules\\\":[{\\\"apiGroups\\\":[\\\"\\\"],\\\"resources\\\":[\\\"configmaps\\\",\\\"endpoints\\\",\\\"namespaces\\\",\\\"nodes\\\",\\\"pods\\\",\\\"pods/log\\\",\\\"replicationcontrollers\\\",\\\"services\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"extensions\\\",\\\"apps\\\"],\\\"resources\\\":[\\\"deployments\\\",\\\"replicasets\\\",\\\"statefulsets\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"autoscaling\\\"],\\\"resources\\\":[\\\"horizontalpodautoscalers\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"batch\\\"],\\\"resources\\\":[\\\"cronjobs\\\",\\\"jobs\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"config.istio.io\\\",\\\"networking.istio.io\\\",\\\"authentication.istio.io\\\",\\\"rbac.istio.io\\\",\\\"security.istio.io\\\"],\\\"resources\\\":[\\\"*\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"monitoring.kiali.io\\\"],\\\"resources\\\":[\\\"monitoringdashboards\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\"]}]}\\n\"}", - "api_groups": "[\"batch\"]", - "cluster_uid": "a7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611191143", - "labels": "{\"app\":\"kiali\",\"chart\":\"kiali\",\"heritage\":\"Tiller\",\"release\":\"istio\"}", - "name": "kiali-viewer", - "resources": "[\"cronjobs\",\"jobs\"]", - "uid": "b5d5ca79-f4e3-4478-b954-fe62a146f279", - "verbs": "[\"get\",\"list\",\"watch\"]", - }, - { - "annotations": "{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"rbac.authorization.k8s.io/v1\\\",\\\"kind\\\":\\\"ClusterRole\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"labels\\\":{\\\"app\\\":\\\"kiali\\\",\\\"chart\\\":\\\"kiali\\\",\\\"heritage\\\":\\\"Tiller\\\",\\\"release\\\":\\\"istio\\\"},\\\"name\\\":\\\"kiali-viewer\\\"},\\\"rules\\\":[{\\\"apiGroups\\\":[\\\"\\\"],\\\"resources\\\":[\\\"configmaps\\\",\\\"endpoints\\\",\\\"namespaces\\\",\\\"nodes\\\",\\\"pods\\\",\\\"pods/log\\\",\\\"replicationcontrollers\\\",\\\"services\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"extensions\\\",\\\"apps\\\"],\\\"resources\\\":[\\\"deployments\\\",\\\"replicasets\\\",\\\"statefulsets\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"autoscaling\\\"],\\\"resources\\\":[\\\"horizontalpodautoscalers\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"batch\\\"],\\\"resources\\\":[\\\"cronjobs\\\",\\\"jobs\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"config.istio.io\\\",\\\"networking.istio.io\\\",\\\"authentication.istio.io\\\",\\\"rbac.istio.io\\\",\\\"security.istio.io\\\"],\\\"resources\\\":[\\\"*\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"monitoring.kiali.io\\\"],\\\"resources\\\":[\\\"monitoringdashboards\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\"]}]}\\n\"}", - "api_groups": "[\"config.istio.io\",\"networking.istio.io\",\"authentication.istio.io\",\"rbac.istio.io\",\"security.istio.io\"]", - "cluster_uid": "a7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611191143", - "labels": "{\"app\":\"kiali\",\"chart\":\"kiali\",\"heritage\":\"Tiller\",\"release\":\"istio\"}", - "name": "kiali-viewer", - "resources": "[\"*\"]", - "uid": "b5d5ca79-f4e3-4478-b954-fe62a146f279", - "verbs": "[\"get\",\"list\",\"watch\"]", - }, - { - "annotations": "{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"rbac.authorization.k8s.io/v1\\\",\\\"kind\\\":\\\"ClusterRole\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"labels\\\":{\\\"app\\\":\\\"kiali\\\",\\\"chart\\\":\\\"kiali\\\",\\\"heritage\\\":\\\"Tiller\\\",\\\"release\\\":\\\"istio\\\"},\\\"name\\\":\\\"kiali-viewer\\\"},\\\"rules\\\":[{\\\"apiGroups\\\":[\\\"\\\"],\\\"resources\\\":[\\\"configmaps\\\",\\\"endpoints\\\",\\\"namespaces\\\",\\\"nodes\\\",\\\"pods\\\",\\\"pods/log\\\",\\\"replicationcontrollers\\\",\\\"services\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"extensions\\\",\\\"apps\\\"],\\\"resources\\\":[\\\"deployments\\\",\\\"replicasets\\\",\\\"statefulsets\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"autoscaling\\\"],\\\"resources\\\":[\\\"horizontalpodautoscalers\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"batch\\\"],\\\"resources\\\":[\\\"cronjobs\\\",\\\"jobs\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"config.istio.io\\\",\\\"networking.istio.io\\\",\\\"authentication.istio.io\\\",\\\"rbac.istio.io\\\",\\\"security.istio.io\\\"],\\\"resources\\\":[\\\"*\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]},{\\\"apiGroups\\\":[\\\"monitoring.kiali.io\\\"],\\\"resources\\\":[\\\"monitoringdashboards\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\"]}]}\\n\"}", - "api_groups": "[\"monitoring.kiali.io\"]", - "cluster_uid": "a7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611191143", - "labels": "{\"app\":\"kiali\",\"chart\":\"kiali\",\"heritage\":\"Tiller\",\"release\":\"istio\"}", - "name": "kiali-viewer", - "resources": "[\"monitoringdashboards\"]", - "uid": "b5d5ca79-f4e3-4478-b954-fe62a146f279", - "verbs": "[\"get\",\"list\"]", - }, - { - "annotations": "{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"rbac.authorization.k8s.io/v1\\\",\\\"kind\\\":\\\"ClusterRole\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"labels\\\":{\\\"k8s-app\\\":\\\"kubernetes-dashboard\\\"},\\\"name\\\":\\\"kubernetes-dashboard\\\"},\\\"rules\\\":[{\\\"apiGroups\\\":[\\\"metrics.k8s.io\\\"],\\\"resources\\\":[\\\"pods\\\",\\\"nodes\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"list\\\",\\\"watch\\\"]}]}\\n\"}", - "api_groups": "[\"metrics.k8s.io\"]", - "cluster_uid": "a7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611190911", - "labels": "{\"k8s-app\":\"kubernetes-dashboard\"}", - "name": "kubernetes-dashboard", - "resources": "[\"pods\",\"nodes\"]", - "uid": "5afb084d-e4da-4207-844d-d3a2e002ecda", - "verbs": "[\"get\",\"list\",\"watch\"]", - }, - }, crprs) -} diff --git a/infrastructure/kubequery/internal/k8s/rbac/init_test.go b/infrastructure/kubequery/internal/k8s/rbac/init_test.go deleted file mode 100644 index 570f452892..0000000000 --- a/infrastructure/kubequery/internal/k8s/rbac/init_test.go +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package rbac - -import ( - "encoding/json" - "io/ioutil" - "path/filepath" - - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/fake" -) - -func loadTestResource(name string, v interface{}) { - path := filepath.Join("testdata", name) - data, err := ioutil.ReadFile(path) - if err != nil { - panic(err) - } - - err = json.Unmarshal(data, v) - if err != nil { - panic(err) - } -} - -func init() { - rs := &v1.Role{} - loadTestResource("role_policy_rule_test.json", rs) - rbs := &v1.RoleBinding{} - loadTestResource("role_binding_subject_test.json", rbs) - crs := &v1.ClusterRoleList{} - loadTestResource("cluster_role_policy_rule_test.json", crs) - crbs := &v1.ClusterRoleBindingList{} - loadTestResource("cluster_role_binding_subject_test.json", crbs) - - k8s.SetClient(fake.NewSimpleClientset(rs, rbs, crs, crbs), types.UID("a7fd8e77-93de-4742-9037-5db9a01e966a"), "") -} diff --git a/infrastructure/kubequery/internal/k8s/rbac/role_binding_subject.go b/infrastructure/kubequery/internal/k8s/rbac/role_binding_subject.go deleted file mode 100644 index c08b4225cd..0000000000 --- a/infrastructure/kubequery/internal/k8s/rbac/role_binding_subject.go +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package rbac - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type roleBindingSubject struct { - k8s.CommonNamespacedFields - RoleName string - RoleKind string - SubjectName string - SubjectKind string - SubjectNamespace string -} - -// RoleBindingSubjectColumns returns kubernetes role binding subject fields as Osquery table columns. -func RoleBindingSubjectColumns() []table.ColumnDefinition { - return k8s.GetSchema(&roleBindingSubject{}) -} - -// RoleBindingSubjectsGenerate generates the kubernetes role binding subjects as Osquery table data. -func RoleBindingSubjectsGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - rbs, err := k8s.GetClient().RbacV1().RoleBindings(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, rb := range rbs.Items { - for _, s := range rb.Subjects { - item := &roleBindingSubject{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(rb.ObjectMeta), - RoleName: rb.RoleRef.Name, - RoleKind: rb.RoleRef.Kind, - SubjectName: s.Name, - SubjectKind: s.Kind, - SubjectNamespace: s.Namespace, - } - results = append(results, k8s.ToMap(item)) - } - } - - if rbs.Continue == "" { - break - } - options.Continue = rbs.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/rbac/role_binding_subject_test.go b/infrastructure/kubequery/internal/k8s/rbac/role_binding_subject_test.go deleted file mode 100644 index c09e15e22c..0000000000 --- a/infrastructure/kubequery/internal/k8s/rbac/role_binding_subject_test.go +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package rbac - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestRoleBindingSubjectsGenerate(t *testing.T) { - rbss, err := RoleBindingSubjectsGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "annotations": "{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"rbac.authorization.k8s.io/v1\\\",\\\"kind\\\":\\\"RoleBinding\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"labels\\\":{\\\"k8s-app\\\":\\\"kubernetes-dashboard\\\"},\\\"name\\\":\\\"kubernetes-dashboard\\\",\\\"namespace\\\":\\\"kube-system\\\"},\\\"roleRef\\\":{\\\"apiGroup\\\":\\\"rbac.authorization.k8s.io\\\",\\\"kind\\\":\\\"Role\\\",\\\"name\\\":\\\"kubernetes-dashboard\\\"},\\\"subjects\\\":[{\\\"kind\\\":\\\"ServiceAccount\\\",\\\"name\\\":\\\"kubernetes-dashboard\\\",\\\"namespace\\\":\\\"kube-system\\\"}]}\\n\"}", - "cluster_uid": "a7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611190911", - "labels": "{\"k8s-app\":\"kubernetes-dashboard\"}", - "name": "kubernetes-dashboard", - "namespace": "kube-system", - "role_kind": "Role", - "role_name": "kubernetes-dashboard", - "subject_kind": "ServiceAccount", - "subject_name": "kubernetes-dashboard", - "subject_namespace": "kube-system", - "uid": "216b24d7-0611-4cb9-991b-fad53856241d", - }, - }, rbss) -} diff --git a/infrastructure/kubequery/internal/k8s/rbac/role_policy_rule.go b/infrastructure/kubequery/internal/k8s/rbac/role_policy_rule.go deleted file mode 100644 index ccc23e9ec4..0000000000 --- a/infrastructure/kubequery/internal/k8s/rbac/role_policy_rule.go +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package rbac - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type rolePolicyRule struct { - k8s.CommonNamespacedFields - v1.PolicyRule -} - -// RolePolicyRuleColumns returns kubernetes role policy rule fields as Osquery table columns. -func RolePolicyRuleColumns() []table.ColumnDefinition { - return k8s.GetSchema(&rolePolicyRule{}) -} - -// RolePolicyRulesGenerate generates the kubernetes role policy rules as Osquery table data. -func RolePolicyRulesGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - roles, err := k8s.GetClient().RbacV1().Roles(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, r := range roles.Items { - for _, p := range r.Rules { - item := &rolePolicyRule{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(r.ObjectMeta), - PolicyRule: p, - } - results = append(results, k8s.ToMap(item)) - } - } - - if roles.Continue == "" { - break - } - options.Continue = roles.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/rbac/role_policy_rule_test.go b/infrastructure/kubequery/internal/k8s/rbac/role_policy_rule_test.go deleted file mode 100644 index 443d85a87d..0000000000 --- a/infrastructure/kubequery/internal/k8s/rbac/role_policy_rule_test.go +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package rbac - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestRolePolicyRulesGenerate(t *testing.T) { - rprs, err := RolePolicyRulesGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "annotations": "{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"rbac.authorization.k8s.io/v1\\\",\\\"kind\\\":\\\"Role\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"labels\\\":{\\\"k8s-app\\\":\\\"kubernetes-dashboard\\\"},\\\"name\\\":\\\"kubernetes-dashboard\\\",\\\"namespace\\\":\\\"kube-system\\\"},\\\"rules\\\":[{\\\"apiGroups\\\":[\\\"\\\"],\\\"resourceNames\\\":[\\\"kubernetes-dashboard-key-holder\\\",\\\"kubernetes-dashboard-certs\\\",\\\"kubernetes-dashboard-csrf\\\"],\\\"resources\\\":[\\\"secrets\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"update\\\",\\\"delete\\\"]},{\\\"apiGroups\\\":[\\\"\\\"],\\\"resourceNames\\\":[\\\"kubernetes-dashboard-settings\\\"],\\\"resources\\\":[\\\"configmaps\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"update\\\"]},{\\\"apiGroups\\\":[\\\"\\\"],\\\"resourceNames\\\":[\\\"heapster\\\",\\\"dashboard-metrics-scraper\\\"],\\\"resources\\\":[\\\"services\\\"],\\\"verbs\\\":[\\\"proxy\\\"]},{\\\"apiGroups\\\":[\\\"\\\"],\\\"resourceNames\\\":[\\\"heapster\\\",\\\"http:heapster:\\\",\\\"https:heapster:\\\",\\\"dashboard-metrics-scraper\\\",\\\"http:dashboard-metrics-scraper\\\"],\\\"resources\\\":[\\\"services/proxy\\\"],\\\"verbs\\\":[\\\"get\\\"]}]}\\n\"}", - "api_groups": "[\"\"]", - "cluster_uid": "a7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611190911", - "labels": "{\"k8s-app\":\"kubernetes-dashboard\"}", - "name": "kubernetes-dashboard", - "namespace": "kube-system", - "resource_names": "[\"kubernetes-dashboard-key-holder\",\"kubernetes-dashboard-certs\",\"kubernetes-dashboard-csrf\"]", - "resources": "[\"secrets\"]", - "uid": "74e02baa-2c11-413f-828a-2cbe39011469", - "verbs": "[\"get\",\"update\",\"delete\"]", - }, - { - "annotations": "{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"rbac.authorization.k8s.io/v1\\\",\\\"kind\\\":\\\"Role\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"labels\\\":{\\\"k8s-app\\\":\\\"kubernetes-dashboard\\\"},\\\"name\\\":\\\"kubernetes-dashboard\\\",\\\"namespace\\\":\\\"kube-system\\\"},\\\"rules\\\":[{\\\"apiGroups\\\":[\\\"\\\"],\\\"resourceNames\\\":[\\\"kubernetes-dashboard-key-holder\\\",\\\"kubernetes-dashboard-certs\\\",\\\"kubernetes-dashboard-csrf\\\"],\\\"resources\\\":[\\\"secrets\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"update\\\",\\\"delete\\\"]},{\\\"apiGroups\\\":[\\\"\\\"],\\\"resourceNames\\\":[\\\"kubernetes-dashboard-settings\\\"],\\\"resources\\\":[\\\"configmaps\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"update\\\"]},{\\\"apiGroups\\\":[\\\"\\\"],\\\"resourceNames\\\":[\\\"heapster\\\",\\\"dashboard-metrics-scraper\\\"],\\\"resources\\\":[\\\"services\\\"],\\\"verbs\\\":[\\\"proxy\\\"]},{\\\"apiGroups\\\":[\\\"\\\"],\\\"resourceNames\\\":[\\\"heapster\\\",\\\"http:heapster:\\\",\\\"https:heapster:\\\",\\\"dashboard-metrics-scraper\\\",\\\"http:dashboard-metrics-scraper\\\"],\\\"resources\\\":[\\\"services/proxy\\\"],\\\"verbs\\\":[\\\"get\\\"]}]}\\n\"}", - "api_groups": "[\"\"]", - "cluster_uid": "a7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611190911", - "labels": "{\"k8s-app\":\"kubernetes-dashboard\"}", - "name": "kubernetes-dashboard", - "namespace": "kube-system", - "resource_names": "[\"kubernetes-dashboard-settings\"]", - "resources": "[\"configmaps\"]", - "uid": "74e02baa-2c11-413f-828a-2cbe39011469", - "verbs": "[\"get\",\"update\"]", - }, - { - "annotations": "{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"rbac.authorization.k8s.io/v1\\\",\\\"kind\\\":\\\"Role\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"labels\\\":{\\\"k8s-app\\\":\\\"kubernetes-dashboard\\\"},\\\"name\\\":\\\"kubernetes-dashboard\\\",\\\"namespace\\\":\\\"kube-system\\\"},\\\"rules\\\":[{\\\"apiGroups\\\":[\\\"\\\"],\\\"resourceNames\\\":[\\\"kubernetes-dashboard-key-holder\\\",\\\"kubernetes-dashboard-certs\\\",\\\"kubernetes-dashboard-csrf\\\"],\\\"resources\\\":[\\\"secrets\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"update\\\",\\\"delete\\\"]},{\\\"apiGroups\\\":[\\\"\\\"],\\\"resourceNames\\\":[\\\"kubernetes-dashboard-settings\\\"],\\\"resources\\\":[\\\"configmaps\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"update\\\"]},{\\\"apiGroups\\\":[\\\"\\\"],\\\"resourceNames\\\":[\\\"heapster\\\",\\\"dashboard-metrics-scraper\\\"],\\\"resources\\\":[\\\"services\\\"],\\\"verbs\\\":[\\\"proxy\\\"]},{\\\"apiGroups\\\":[\\\"\\\"],\\\"resourceNames\\\":[\\\"heapster\\\",\\\"http:heapster:\\\",\\\"https:heapster:\\\",\\\"dashboard-metrics-scraper\\\",\\\"http:dashboard-metrics-scraper\\\"],\\\"resources\\\":[\\\"services/proxy\\\"],\\\"verbs\\\":[\\\"get\\\"]}]}\\n\"}", - "api_groups": "[\"\"]", - "cluster_uid": "a7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611190911", - "labels": "{\"k8s-app\":\"kubernetes-dashboard\"}", - "name": "kubernetes-dashboard", - "namespace": "kube-system", - "resource_names": "[\"heapster\",\"dashboard-metrics-scraper\"]", - "resources": "[\"services\"]", - "uid": "74e02baa-2c11-413f-828a-2cbe39011469", - "verbs": "[\"proxy\"]", - }, - { - "annotations": "{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"rbac.authorization.k8s.io/v1\\\",\\\"kind\\\":\\\"Role\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"labels\\\":{\\\"k8s-app\\\":\\\"kubernetes-dashboard\\\"},\\\"name\\\":\\\"kubernetes-dashboard\\\",\\\"namespace\\\":\\\"kube-system\\\"},\\\"rules\\\":[{\\\"apiGroups\\\":[\\\"\\\"],\\\"resourceNames\\\":[\\\"kubernetes-dashboard-key-holder\\\",\\\"kubernetes-dashboard-certs\\\",\\\"kubernetes-dashboard-csrf\\\"],\\\"resources\\\":[\\\"secrets\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"update\\\",\\\"delete\\\"]},{\\\"apiGroups\\\":[\\\"\\\"],\\\"resourceNames\\\":[\\\"kubernetes-dashboard-settings\\\"],\\\"resources\\\":[\\\"configmaps\\\"],\\\"verbs\\\":[\\\"get\\\",\\\"update\\\"]},{\\\"apiGroups\\\":[\\\"\\\"],\\\"resourceNames\\\":[\\\"heapster\\\",\\\"dashboard-metrics-scraper\\\"],\\\"resources\\\":[\\\"services\\\"],\\\"verbs\\\":[\\\"proxy\\\"]},{\\\"apiGroups\\\":[\\\"\\\"],\\\"resourceNames\\\":[\\\"heapster\\\",\\\"http:heapster:\\\",\\\"https:heapster:\\\",\\\"dashboard-metrics-scraper\\\",\\\"http:dashboard-metrics-scraper\\\"],\\\"resources\\\":[\\\"services/proxy\\\"],\\\"verbs\\\":[\\\"get\\\"]}]}\\n\"}", - "api_groups": "[\"\"]", - "cluster_uid": "a7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1611190911", - "labels": "{\"k8s-app\":\"kubernetes-dashboard\"}", - "name": "kubernetes-dashboard", - "namespace": "kube-system", - "resource_names": "[\"heapster\",\"http:heapster:\",\"https:heapster:\",\"dashboard-metrics-scraper\",\"http:dashboard-metrics-scraper\"]", - "resources": "[\"services/proxy\"]", - "uid": "74e02baa-2c11-413f-828a-2cbe39011469", - "verbs": "[\"get\"]", - }, - }, rprs) -} diff --git a/infrastructure/kubequery/internal/k8s/rbac/testdata/cluster_role_binding_subject_test.json b/infrastructure/kubequery/internal/k8s/rbac/testdata/cluster_role_binding_subject_test.json deleted file mode 100644 index 7f4a4e03a1..0000000000 --- a/infrastructure/kubequery/internal/k8s/rbac/testdata/cluster_role_binding_subject_test.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - "apiVersion": "v1", - "items": [ - { - "apiVersion": "rbac.authorization.k8s.io/v1", - "kind": "ClusterRoleBinding", - "metadata": { - "annotations": { - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"rbac.authorization.k8s.io/v1\",\"kind\":\"ClusterRoleBinding\",\"metadata\":{\"annotations\":{},\"name\":\"kubernetes-dashboard\"},\"roleRef\":{\"apiGroup\":\"rbac.authorization.k8s.io\",\"kind\":\"ClusterRole\",\"name\":\"kubernetes-dashboard\"},\"subjects\":[{\"kind\":\"ServiceAccount\",\"name\":\"kubernetes-dashboard\",\"namespace\":\"kube-system\"}]}\n" - }, - "creationTimestamp": "2021-01-21T01:01:51Z", - "managedFields": [ - { - "apiVersion": "rbac.authorization.k8s.io/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - } - }, - "f:roleRef": { - "f:apiGroup": {}, - "f:kind": {}, - "f:name": {} - }, - "f:subjects": {} - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-21T01:01:51Z" - } - ], - "name": "kubernetes-dashboard", - "resourceVersion": "449798", - "selfLink": "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings/kubernetes-dashboard", - "uid": "7e3bf161-3a4e-495d-98a8-f71248d0ba36" - }, - "roleRef": { - "apiGroup": "rbac.authorization.k8s.io", - "kind": "ClusterRole", - "name": "kubernetes-dashboard" - }, - "subjects": [ - { - "kind": "ServiceAccount", - "name": "kubernetes-dashboard", - "namespace": "kube-system" - } - ] - }, - { - "apiVersion": "rbac.authorization.k8s.io/v1", - "kind": "ClusterRoleBinding", - "metadata": { - "annotations": { - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"rbac.authorization.k8s.io/v1\",\"kind\":\"ClusterRoleBinding\",\"metadata\":{\"annotations\":{},\"name\":\"nginx-ingress-microk8s\"},\"roleRef\":{\"apiGroup\":\"rbac.authorization.k8s.io\",\"kind\":\"ClusterRole\",\"name\":\"nginx-ingress-microk8s-clusterrole\"},\"subjects\":[{\"kind\":\"ServiceAccount\",\"name\":\"nginx-ingress-microk8s-serviceaccount\",\"namespace\":\"ingress\"}]}\n" - }, - "creationTimestamp": "2021-01-21T01:04:07Z", - "managedFields": [ - { - "apiVersion": "rbac.authorization.k8s.io/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - } - }, - "f:roleRef": { - "f:apiGroup": {}, - "f:kind": {}, - "f:name": {} - }, - "f:subjects": {} - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-21T01:04:07Z" - } - ], - "name": "nginx-ingress-microk8s", - "resourceVersion": "450134", - "selfLink": "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings/nginx-ingress-microk8s", - "uid": "aa9c6e0e-3dd4-4da3-936a-a6edea62c7b7" - }, - "roleRef": { - "apiGroup": "rbac.authorization.k8s.io", - "kind": "ClusterRole", - "name": "nginx-ingress-microk8s-clusterrole" - }, - "subjects": [ - { - "kind": "ServiceAccount", - "name": "nginx-ingress-microk8s-serviceaccount", - "namespace": "ingress" - } - ] - } - ], - "kind": "List", - "metadata": { - "resourceVersion": "", - "selfLink": "" - } -} diff --git a/infrastructure/kubequery/internal/k8s/rbac/testdata/cluster_role_policy_rule_test.json b/infrastructure/kubequery/internal/k8s/rbac/testdata/cluster_role_policy_rule_test.json deleted file mode 100644 index bda5a78e53..0000000000 --- a/infrastructure/kubequery/internal/k8s/rbac/testdata/cluster_role_policy_rule_test.json +++ /dev/null @@ -1,204 +0,0 @@ -{ - "apiVersion": "v1", - "items": [ - { - "apiVersion": "rbac.authorization.k8s.io/v1", - "kind": "ClusterRole", - "metadata": { - "annotations": { - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"rbac.authorization.k8s.io/v1\",\"kind\":\"ClusterRole\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"kiali\",\"chart\":\"kiali\",\"heritage\":\"Tiller\",\"release\":\"istio\"},\"name\":\"kiali-viewer\"},\"rules\":[{\"apiGroups\":[\"\"],\"resources\":[\"configmaps\",\"endpoints\",\"namespaces\",\"nodes\",\"pods\",\"pods/log\",\"replicationcontrollers\",\"services\"],\"verbs\":[\"get\",\"list\",\"watch\"]},{\"apiGroups\":[\"extensions\",\"apps\"],\"resources\":[\"deployments\",\"replicasets\",\"statefulsets\"],\"verbs\":[\"get\",\"list\",\"watch\"]},{\"apiGroups\":[\"autoscaling\"],\"resources\":[\"horizontalpodautoscalers\"],\"verbs\":[\"get\",\"list\",\"watch\"]},{\"apiGroups\":[\"batch\"],\"resources\":[\"cronjobs\",\"jobs\"],\"verbs\":[\"get\",\"list\",\"watch\"]},{\"apiGroups\":[\"config.istio.io\",\"networking.istio.io\",\"authentication.istio.io\",\"rbac.istio.io\",\"security.istio.io\"],\"resources\":[\"*\"],\"verbs\":[\"get\",\"list\",\"watch\"]},{\"apiGroups\":[\"monitoring.kiali.io\"],\"resources\":[\"monitoringdashboards\"],\"verbs\":[\"get\",\"list\"]}]}\n" - }, - "creationTimestamp": "2021-01-21T01:05:43Z", - "labels": { - "app": "kiali", - "chart": "kiali", - "heritage": "Tiller", - "release": "istio" - }, - "managedFields": [ - { - "apiVersion": "rbac.authorization.k8s.io/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:app": {}, - "f:chart": {}, - "f:heritage": {}, - "f:release": {} - } - }, - "f:rules": {} - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-21T01:05:43Z" - } - ], - "name": "kiali-viewer", - "resourceVersion": "450538", - "selfLink": "/apis/rbac.authorization.k8s.io/v1/clusterroles/kiali-viewer", - "uid": "b5d5ca79-f4e3-4478-b954-fe62a146f279" - }, - "rules": [ - { - "apiGroups": [ - "" - ], - "resources": [ - "configmaps", - "endpoints", - "namespaces", - "nodes", - "pods", - "pods/log", - "replicationcontrollers", - "services" - ], - "verbs": [ - "get", - "list", - "watch" - ] - }, - { - "apiGroups": [ - "extensions", - "apps" - ], - "resources": [ - "deployments", - "replicasets", - "statefulsets" - ], - "verbs": [ - "get", - "list", - "watch" - ] - }, - { - "apiGroups": [ - "autoscaling" - ], - "resources": [ - "horizontalpodautoscalers" - ], - "verbs": [ - "get", - "list", - "watch" - ] - }, - { - "apiGroups": [ - "batch" - ], - "resources": [ - "cronjobs", - "jobs" - ], - "verbs": [ - "get", - "list", - "watch" - ] - }, - { - "apiGroups": [ - "config.istio.io", - "networking.istio.io", - "authentication.istio.io", - "rbac.istio.io", - "security.istio.io" - ], - "resources": [ - "*" - ], - "verbs": [ - "get", - "list", - "watch" - ] - }, - { - "apiGroups": [ - "monitoring.kiali.io" - ], - "resources": [ - "monitoringdashboards" - ], - "verbs": [ - "get", - "list" - ] - } - ] - }, - { - "apiVersion": "rbac.authorization.k8s.io/v1", - "kind": "ClusterRole", - "metadata": { - "annotations": { - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"rbac.authorization.k8s.io/v1\",\"kind\":\"ClusterRole\",\"metadata\":{\"annotations\":{},\"labels\":{\"k8s-app\":\"kubernetes-dashboard\"},\"name\":\"kubernetes-dashboard\"},\"rules\":[{\"apiGroups\":[\"metrics.k8s.io\"],\"resources\":[\"pods\",\"nodes\"],\"verbs\":[\"get\",\"list\",\"watch\"]}]}\n" - }, - "creationTimestamp": "2021-01-21T01:01:51Z", - "labels": { - "k8s-app": "kubernetes-dashboard" - }, - "managedFields": [ - { - "apiVersion": "rbac.authorization.k8s.io/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:k8s-app": {} - } - }, - "f:rules": {} - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-21T01:01:51Z" - } - ], - "name": "kubernetes-dashboard", - "resourceVersion": "449796", - "selfLink": "/apis/rbac.authorization.k8s.io/v1/clusterroles/kubernetes-dashboard", - "uid": "5afb084d-e4da-4207-844d-d3a2e002ecda" - }, - "rules": [ - { - "apiGroups": [ - "metrics.k8s.io" - ], - "resources": [ - "pods", - "nodes" - ], - "verbs": [ - "get", - "list", - "watch" - ] - } - ] - } - ], - "kind": "List", - "metadata": { - "resourceVersion": "", - "selfLink": "" - } -} diff --git a/infrastructure/kubequery/internal/k8s/rbac/testdata/role_binding_subject_test.json b/infrastructure/kubequery/internal/k8s/rbac/testdata/role_binding_subject_test.json deleted file mode 100644 index ed557d4bbc..0000000000 --- a/infrastructure/kubequery/internal/k8s/rbac/testdata/role_binding_subject_test.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "apiVersion": "rbac.authorization.k8s.io/v1", - "kind": "RoleBinding", - "metadata": { - "annotations": { - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"rbac.authorization.k8s.io/v1\",\"kind\":\"RoleBinding\",\"metadata\":{\"annotations\":{},\"labels\":{\"k8s-app\":\"kubernetes-dashboard\"},\"name\":\"kubernetes-dashboard\",\"namespace\":\"kube-system\"},\"roleRef\":{\"apiGroup\":\"rbac.authorization.k8s.io\",\"kind\":\"Role\",\"name\":\"kubernetes-dashboard\"},\"subjects\":[{\"kind\":\"ServiceAccount\",\"name\":\"kubernetes-dashboard\",\"namespace\":\"kube-system\"}]}\n" - }, - "creationTimestamp": "2021-01-21T01:01:51Z", - "labels": { - "k8s-app": "kubernetes-dashboard" - }, - "managedFields": [ - { - "apiVersion": "rbac.authorization.k8s.io/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:k8s-app": {} - } - }, - "f:roleRef": { - "f:apiGroup": {}, - "f:kind": {}, - "f:name": {} - }, - "f:subjects": {} - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-21T01:01:51Z" - } - ], - "name": "kubernetes-dashboard", - "namespace": "kube-system", - "resourceVersion": "449797", - "selfLink": "/apis/rbac.authorization.k8s.io/v1/namespaces/kube-system/rolebindings/kubernetes-dashboard", - "uid": "216b24d7-0611-4cb9-991b-fad53856241d" - }, - "roleRef": { - "apiGroup": "rbac.authorization.k8s.io", - "kind": "Role", - "name": "kubernetes-dashboard" - }, - "subjects": [ - { - "kind": "ServiceAccount", - "name": "kubernetes-dashboard", - "namespace": "kube-system" - } - ] -} diff --git a/infrastructure/kubequery/internal/k8s/rbac/testdata/role_policy_rule_test.json b/infrastructure/kubequery/internal/k8s/rbac/testdata/role_policy_rule_test.json deleted file mode 100644 index b67810d75e..0000000000 --- a/infrastructure/kubequery/internal/k8s/rbac/testdata/role_policy_rule_test.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - "apiVersion": "rbac.authorization.k8s.io/v1", - "kind": "Role", - "metadata": { - "annotations": { - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"rbac.authorization.k8s.io/v1\",\"kind\":\"Role\",\"metadata\":{\"annotations\":{},\"labels\":{\"k8s-app\":\"kubernetes-dashboard\"},\"name\":\"kubernetes-dashboard\",\"namespace\":\"kube-system\"},\"rules\":[{\"apiGroups\":[\"\"],\"resourceNames\":[\"kubernetes-dashboard-key-holder\",\"kubernetes-dashboard-certs\",\"kubernetes-dashboard-csrf\"],\"resources\":[\"secrets\"],\"verbs\":[\"get\",\"update\",\"delete\"]},{\"apiGroups\":[\"\"],\"resourceNames\":[\"kubernetes-dashboard-settings\"],\"resources\":[\"configmaps\"],\"verbs\":[\"get\",\"update\"]},{\"apiGroups\":[\"\"],\"resourceNames\":[\"heapster\",\"dashboard-metrics-scraper\"],\"resources\":[\"services\"],\"verbs\":[\"proxy\"]},{\"apiGroups\":[\"\"],\"resourceNames\":[\"heapster\",\"http:heapster:\",\"https:heapster:\",\"dashboard-metrics-scraper\",\"http:dashboard-metrics-scraper\"],\"resources\":[\"services/proxy\"],\"verbs\":[\"get\"]}]}\n" - }, - "creationTimestamp": "2021-01-21T01:01:51Z", - "labels": { - "k8s-app": "kubernetes-dashboard" - }, - "managedFields": [ - { - "apiVersion": "rbac.authorization.k8s.io/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:k8s-app": {} - } - }, - "f:rules": {} - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-21T01:01:51Z" - } - ], - "name": "kubernetes-dashboard", - "namespace": "kube-system", - "resourceVersion": "449795", - "selfLink": "/apis/rbac.authorization.k8s.io/v1/namespaces/kube-system/roles/kubernetes-dashboard", - "uid": "74e02baa-2c11-413f-828a-2cbe39011469" - }, - "rules": [ - { - "apiGroups": [ - "" - ], - "resourceNames": [ - "kubernetes-dashboard-key-holder", - "kubernetes-dashboard-certs", - "kubernetes-dashboard-csrf" - ], - "resources": [ - "secrets" - ], - "verbs": [ - "get", - "update", - "delete" - ] - }, - { - "apiGroups": [ - "" - ], - "resourceNames": [ - "kubernetes-dashboard-settings" - ], - "resources": [ - "configmaps" - ], - "verbs": [ - "get", - "update" - ] - }, - { - "apiGroups": [ - "" - ], - "resourceNames": [ - "heapster", - "dashboard-metrics-scraper" - ], - "resources": [ - "services" - ], - "verbs": [ - "proxy" - ] - }, - { - "apiGroups": [ - "" - ], - "resourceNames": [ - "heapster", - "http:heapster:", - "https:heapster:", - "dashboard-metrics-scraper", - "http:dashboard-metrics-scraper" - ], - "resources": [ - "services/proxy" - ], - "verbs": [ - "get" - ] - } - ] -} diff --git a/infrastructure/kubequery/internal/k8s/storage/csi_driver.go b/infrastructure/kubequery/internal/k8s/storage/csi_driver.go deleted file mode 100644 index 063e67febf..0000000000 --- a/infrastructure/kubequery/internal/k8s/storage/csi_driver.go +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package storage - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/storage/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type csiDriver struct { - k8s.CommonFields - v1.CSIDriverSpec -} - -// CSIDriverColumns returns kubernetes CSI driver fields as Osquery table columns. -func CSIDriverColumns() []table.ColumnDefinition { - return k8s.GetSchema(&csiDriver{}) -} - -// CSIDriversGenerate generates the kubernetes CSI drivers as Osquery table data. -func CSIDriversGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - drivers, err := k8s.GetClient().StorageV1().CSIDrivers().List(ctx, options) - if err != nil { - return nil, err - } - - for _, d := range drivers.Items { - item := &csiDriver{ - CommonFields: k8s.GetCommonFields(d.ObjectMeta), - CSIDriverSpec: d.Spec, - } - results = append(results, k8s.ToMap(item)) - } - - if drivers.Continue == "" { - break - } - options.Continue = drivers.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/storage/csi_driver_test.go b/infrastructure/kubequery/internal/k8s/storage/csi_driver_test.go deleted file mode 100644 index 6fc49f2589..0000000000 --- a/infrastructure/kubequery/internal/k8s/storage/csi_driver_test.go +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package storage - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestCSIDriversGenerate(t *testing.T) { - cds, err := CSIDriversGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "annotations": "{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"storage.k8s.io/v1beta1\\\",\\\"kind\\\":\\\"CSIDriver\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"name\\\":\\\"efs.csi.aws.com\\\"},\\\"spec\\\":{\\\"attachRequired\\\":false}}\\n\"}", - "attach_required": "0", - "cluster_uid": "e7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1609173285", - "name": "efs.csi.aws.com", - "pod_info_on_mount": "0", - "uid": "35613d4e-4f94-416c-bdad-88660302ce99", - "volume_lifecycle_modes": "[\"Persistent\"]", - }, - }, cds) -} diff --git a/infrastructure/kubequery/internal/k8s/storage/csi_node_driver.go b/infrastructure/kubequery/internal/k8s/storage/csi_node_driver.go deleted file mode 100644 index 5feb0f8e1c..0000000000 --- a/infrastructure/kubequery/internal/k8s/storage/csi_node_driver.go +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package storage - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/storage/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" -) - -type csiNodeDriver struct { - ClusterName string - ClusterUID types.UID - v1.CSINodeDriver -} - -// CSINodeDriverColumns returns kubernetes CSI node driver fields as Osquery table columns. -func CSINodeDriverColumns() []table.ColumnDefinition { - return k8s.GetSchema(&csiNodeDriver{}) -} - -// CSINodeDriversGenerate generates the kubernetes CSI node drivers as Osquery table data. -func CSINodeDriversGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - nodes, err := k8s.GetClient().StorageV1().CSINodes().List(ctx, options) - if err != nil { - return nil, err - } - - for _, n := range nodes.Items { - for _, d := range n.Spec.Drivers { - item := &csiNodeDriver{ - ClusterName: k8s.GetClusterName(), - ClusterUID: k8s.GetClusterUID(), - CSINodeDriver: d, - } - results = append(results, k8s.ToMap(item)) - } - } - - if nodes.Continue == "" { - break - } - options.Continue = nodes.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/storage/csi_node_driver_test.go b/infrastructure/kubequery/internal/k8s/storage/csi_node_driver_test.go deleted file mode 100644 index 903128b376..0000000000 --- a/infrastructure/kubequery/internal/k8s/storage/csi_node_driver_test.go +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package storage - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestCSINodeDriversGenerate(t *testing.T) { - cnds, err := CSINodeDriversGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{}, cnds) -} diff --git a/infrastructure/kubequery/internal/k8s/storage/csi_storage_capacity.go b/infrastructure/kubequery/internal/k8s/storage/csi_storage_capacity.go deleted file mode 100644 index f1579a5bab..0000000000 --- a/infrastructure/kubequery/internal/k8s/storage/csi_storage_capacity.go +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package storage - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type csiStorageCapacity struct { - k8s.CommonNamespacedFields - NodeTopology *metav1.LabelSelector - StorageClassName string - Capacity *resource.Quantity -} - -// CSIStorageCapacityColumns returns kubernetes CSI storage capacity fields as Osquery table columns. -func CSIStorageCapacityColumns() []table.ColumnDefinition { - return k8s.GetSchema(&csiStorageCapacity{}) -} - -// CSIStorageCapacitiesGenerate generates the kubernetes CSI storage capacities as Osquery table data. -func CSIStorageCapacitiesGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - scs, err := k8s.GetClient().StorageV1alpha1().CSIStorageCapacities(metav1.NamespaceAll).List(ctx, options) - if err != nil { - return nil, err - } - - for _, sc := range scs.Items { - item := &csiStorageCapacity{ - CommonNamespacedFields: k8s.GetCommonNamespacedFields(sc.ObjectMeta), - NodeTopology: sc.NodeTopology, - StorageClassName: sc.StorageClassName, - Capacity: sc.Capacity, - } - results = append(results, k8s.ToMap(item)) - } - - if scs.Continue == "" { - break - } - options.Continue = scs.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/storage/init_test.go b/infrastructure/kubequery/internal/k8s/storage/init_test.go deleted file mode 100644 index c7a1d50461..0000000000 --- a/infrastructure/kubequery/internal/k8s/storage/init_test.go +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package storage - -import ( - "encoding/json" - "io/ioutil" - "path/filepath" - - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/storage/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/fake" -) - -func loadTestResource(name string, v interface{}) { - path := filepath.Join("testdata", name) - data, err := ioutil.ReadFile(path) - if err != nil { - panic(err) - } - - err = json.Unmarshal(data, v) - if err != nil { - panic(err) - } -} - -func init() { - cd := &v1.CSIDriver{} - loadTestResource("csi_driver_test.json", cd) - cnd := &v1.CSINodeList{} - loadTestResource("csi_node_driver_test.json", cnd) - sc := &v1.StorageClass{} - loadTestResource("storage_class_test.json", sc) - - k8s.SetClient(fake.NewSimpleClientset(cd, cnd, sc), types.UID("e7fd8e77-93de-4742-9037-5db9a01e966a"), "") -} diff --git a/infrastructure/kubequery/internal/k8s/storage/storage_class.go b/infrastructure/kubequery/internal/k8s/storage/storage_class.go deleted file mode 100644 index fe1aeeb296..0000000000 --- a/infrastructure/kubequery/internal/k8s/storage/storage_class.go +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package storage - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - corev1 "k8s.io/api/core/v1" - v1 "k8s.io/api/storage/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type storageClass struct { - k8s.CommonFields - Provisioner string - Parameters map[string]string - ReclaimPolicy *corev1.PersistentVolumeReclaimPolicy - MountOptions []string - AllowVolumeExpansion *bool - VolumeBindingMode *v1.VolumeBindingMode - AllowedTopologies []corev1.TopologySelectorTerm -} - -// SCClassColumns returns kubernetes storage class fields as Osquery table columns. -func SCClassColumns() []table.ColumnDefinition { - return k8s.GetSchema(&storageClass{}) -} - -// SCClassesGenerate generates the kubernetes storage classes as Osquery table data. -func SCClassesGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - classes, err := k8s.GetClient().StorageV1().StorageClasses().List(ctx, options) - if err != nil { - return nil, err - } - - for _, c := range classes.Items { - item := &storageClass{ - CommonFields: k8s.GetCommonFields(c.ObjectMeta), - Provisioner: c.Provisioner, - Parameters: c.Parameters, - ReclaimPolicy: c.ReclaimPolicy, - MountOptions: c.MountOptions, - AllowVolumeExpansion: c.AllowVolumeExpansion, - VolumeBindingMode: c.VolumeBindingMode, - AllowedTopologies: c.AllowedTopologies, - } - results = append(results, k8s.ToMap(item)) - } - - if classes.Continue == "" { - break - } - options.Continue = classes.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/storage/storage_class_test.go b/infrastructure/kubequery/internal/k8s/storage/storage_class_test.go deleted file mode 100644 index 71f6c1f4ac..0000000000 --- a/infrastructure/kubequery/internal/k8s/storage/storage_class_test.go +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package storage - -import ( - "context" - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" -) - -func TestSGClassesGenerate(t *testing.T) { - cnds, err := SCClassesGenerate(context.TODO(), table.QueryContext{}) - assert.Nil(t, err) - assert.Equal(t, []map[string]string{ - { - "annotations": "{\"kubectl.kubernetes.io/last-applied-configuration\":\"{\\\"apiVersion\\\":\\\"storage.k8s.io/v1\\\",\\\"kind\\\":\\\"StorageClass\\\",\\\"metadata\\\":{\\\"annotations\\\":{\\\"storageclass.kubernetes.io/is-default-class\\\":\\\"true\\\"},\\\"name\\\":\\\"gp2\\\"},\\\"parameters\\\":{\\\"fsType\\\":\\\"ext4\\\",\\\"type\\\":\\\"gp2\\\"},\\\"provisioner\\\":\\\"kubernetes.io/aws-ebs\\\",\\\"volumeBindingMode\\\":\\\"WaitForFirstConsumer\\\"}\\n\",\"storageclass.kubernetes.io/is-default-class\":\"true\"}", - "cluster_uid": "e7fd8e77-93de-4742-9037-5db9a01e966a", - "creation_timestamp": "1609173285", - "name": "gp2", - "parameters": "{\"fsType\":\"ext4\",\"type\":\"gp2\"}", - "provisioner": "kubernetes.io/aws-ebs", - "reclaim_policy": "Delete", - "uid": "4dae2799-6576-403c-8644-7a2ad12b1fd7", - "volume_binding_mode": "WaitForFirstConsumer", - }, - }, cnds) -} diff --git a/infrastructure/kubequery/internal/k8s/storage/testdata/csi_driver_test.json b/infrastructure/kubequery/internal/k8s/storage/testdata/csi_driver_test.json deleted file mode 100644 index 97f455960d..0000000000 --- a/infrastructure/kubequery/internal/k8s/storage/testdata/csi_driver_test.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "apiVersion": "storage.k8s.io/v1beta1", - "kind": "CSIDriver", - "metadata": { - "annotations": { - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"storage.k8s.io/v1beta1\",\"kind\":\"CSIDriver\",\"metadata\":{\"annotations\":{},\"name\":\"efs.csi.aws.com\"},\"spec\":{\"attachRequired\":false}}\n" - }, - "creationTimestamp": "2020-12-28T16:34:45Z", - "name": "efs.csi.aws.com", - "resourceVersion": "98", - "selfLink": "/apis/storage.k8s.io/v1beta1/csidrivers/efs.csi.aws.com", - "uid": "35613d4e-4f94-416c-bdad-88660302ce99" - }, - "spec": { - "attachRequired": false, - "podInfoOnMount": false, - "volumeLifecycleModes": [ - "Persistent" - ] - } -} diff --git a/infrastructure/kubequery/internal/k8s/storage/testdata/csi_node_driver_test.json b/infrastructure/kubequery/internal/k8s/storage/testdata/csi_node_driver_test.json deleted file mode 100644 index 18d1652b5e..0000000000 --- a/infrastructure/kubequery/internal/k8s/storage/testdata/csi_node_driver_test.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "apiVersion": "v1", - "items": [ - { - "apiVersion": "storage.k8s.io/v1", - "kind": "CSINode", - "metadata": { - "creationTimestamp": "2020-12-28T16:40:02Z", - "name": "ip-10-250-0-92.us-west-2.compute.internal", - "ownerReferences": [ - { - "apiVersion": "v1", - "kind": "Node", - "name": "ip-10-250-0-92.us-west-2.compute.internal", - "uid": "a5c67dc3-2ec4-4d8e-9aa3-6da896481feb" - } - ], - "resourceVersion": "1166", - "selfLink": "/apis/storage.k8s.io/v1/csinodes/ip-10-250-0-92.us-west-2.compute.internal", - "uid": "63907da1-2e7b-42a0-8cfe-3379153feaa0" - }, - "spec": { - "drivers": null - } - }, - { - "apiVersion": "storage.k8s.io/v1", - "kind": "CSINode", - "metadata": { - "creationTimestamp": "2020-12-28T16:39:58Z", - "name": "ip-10-250-1-234.us-west-2.compute.internal", - "ownerReferences": [ - { - "apiVersion": "v1", - "kind": "Node", - "name": "ip-10-250-1-234.us-west-2.compute.internal", - "uid": "b9017865-5d2f-4780-8aa7-b90815d5fb4b" - } - ], - "resourceVersion": "1110", - "selfLink": "/apis/storage.k8s.io/v1/csinodes/ip-10-250-1-234.us-west-2.compute.internal", - "uid": "f29ffd82-dc51-4828-aa8d-1b5b28a6859f" - }, - "spec": { - "drivers": null - } - } - ], - "kind": "List", - "metadata": { - "resourceVersion": "", - "selfLink": "" - } -} diff --git a/infrastructure/kubequery/internal/k8s/storage/testdata/storage_class_test.json b/infrastructure/kubequery/internal/k8s/storage/testdata/storage_class_test.json deleted file mode 100644 index 797547b6eb..0000000000 --- a/infrastructure/kubequery/internal/k8s/storage/testdata/storage_class_test.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "apiVersion": "storage.k8s.io/v1", - "kind": "StorageClass", - "metadata": { - "annotations": { - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"storage.k8s.io/v1\",\"kind\":\"StorageClass\",\"metadata\":{\"annotations\":{\"storageclass.kubernetes.io/is-default-class\":\"true\"},\"name\":\"gp2\"},\"parameters\":{\"fsType\":\"ext4\",\"type\":\"gp2\"},\"provisioner\":\"kubernetes.io/aws-ebs\",\"volumeBindingMode\":\"WaitForFirstConsumer\"}\n", - "storageclass.kubernetes.io/is-default-class": "true" - }, - "creationTimestamp": "2020-12-28T16:34:45Z", - "name": "gp2", - "resourceVersion": "118", - "selfLink": "/apis/storage.k8s.io/v1/storageclasses/gp2", - "uid": "4dae2799-6576-403c-8644-7a2ad12b1fd7" - }, - "parameters": { - "fsType": "ext4", - "type": "gp2" - }, - "provisioner": "kubernetes.io/aws-ebs", - "reclaimPolicy": "Delete", - "volumeBindingMode": "WaitForFirstConsumer" -} diff --git a/infrastructure/kubequery/internal/k8s/storage/volume_attachment.go b/infrastructure/kubequery/internal/k8s/storage/volume_attachment.go deleted file mode 100644 index dd9f2c6731..0000000000 --- a/infrastructure/kubequery/internal/k8s/storage/volume_attachment.go +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package storage - -import ( - "context" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s" - v1 "k8s.io/api/storage/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type volumeAttachment struct { - k8s.CommonFields - v1.VolumeAttachmentSpec - v1.VolumeAttachmentStatus -} - -// VolumeAttachmentColumns returns kubernetes volume attachment fields as Osquery table columns. -func VolumeAttachmentColumns() []table.ColumnDefinition { - return k8s.GetSchema(&volumeAttachment{}) -} - -// VolumeAttachmentsGenerate generates the kubernetes volume attachments as Osquery table data. -func VolumeAttachmentsGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { - options := metav1.ListOptions{} - results := make([]map[string]string, 0) - - for { - vas, err := k8s.GetClient().StorageV1().VolumeAttachments().List(ctx, options) - if err != nil { - return nil, err - } - - for _, va := range vas.Items { - item := &volumeAttachment{ - CommonFields: k8s.GetCommonFields(va.ObjectMeta), - VolumeAttachmentSpec: va.Spec, - VolumeAttachmentStatus: va.Status, - } - results = append(results, k8s.ToMap(item)) - } - - if vas.Continue == "" { - break - } - options.Continue = vas.Continue - } - - return results, nil -} diff --git a/infrastructure/kubequery/internal/k8s/tables/tables.go b/infrastructure/kubequery/internal/k8s/tables/tables.go deleted file mode 100644 index c94cc9af60..0000000000 --- a/infrastructure/kubequery/internal/k8s/tables/tables.go +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package tables - -import ( - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/Uptycs/kubequery/internal/k8s/admissionregistration" - "github.com/Uptycs/kubequery/internal/k8s/apps" - "github.com/Uptycs/kubequery/internal/k8s/autoscaling" - "github.com/Uptycs/kubequery/internal/k8s/batch" - "github.com/Uptycs/kubequery/internal/k8s/core" - "github.com/Uptycs/kubequery/internal/k8s/discovery" - "github.com/Uptycs/kubequery/internal/k8s/event" - "github.com/Uptycs/kubequery/internal/k8s/networking" - "github.com/Uptycs/kubequery/internal/k8s/policy" - "github.com/Uptycs/kubequery/internal/k8s/rbac" - "github.com/Uptycs/kubequery/internal/k8s/storage" -) - -// Table structure holds Osquery extension table definition. -type Table struct { - Name string - Columns []table.ColumnDefinition - GenFunc table.GenerateFunc -} - -// GetTables returns the definition of all the tables supported by this extension. -func GetTables() []Table { - return []Table{ - // Admission Registration - {"kubernetes_mutating_webhooks", admissionregistration.MutatingWebhookColumns(), admissionregistration.MutatingWebhooksGenerate}, - {"kubernetes_validating_webhooks", admissionregistration.ValidatingWebhookColumns(), admissionregistration.ValidatingWebhooksGenerate}, - - // Apps - {"kubernetes_daemon_sets", apps.DaemonSetColumns(), apps.DaemonSetsGenerate}, - {"kubernetes_daemon_set_containers", apps.DaemonSetContainerColumns(), apps.DaemonSetContainersGenerate}, - {"kubernetes_daemon_set_volumes", apps.DaemonSetVolumeColumns(), apps.DaemonSetVolumesGenerate}, - {"kubernetes_deployments", apps.DeploymentColumns(), apps.DeploymentsGenerate}, - {"kubernetes_deployments_containers", apps.DeploymentContainerColumns(), apps.DeploymentContainersGenerate}, - {"kubernetes_deployments_volumes", apps.DeploymentVolumeColumns(), apps.DeploymentVolumesGenerate}, - {"kubernetes_replica_sets", apps.ReplicaSetColumns(), apps.ReplicaSetsGenerate}, - {"kubernetes_replica_set_containers", apps.ReplicaSetContainerColumns(), apps.ReplicaSetContainersGenerate}, - {"kubernetes_replica_set_volumes", apps.ReplicaSetVolumeColumns(), apps.ReplicaSetVolumesGenerate}, - {"kubernetes_stateful_sets", apps.StatefulSetColumns(), apps.StatefulSetsGenerate}, - {"kubernetes_stateful_set_containers", apps.StatefulSetContainerColumns(), apps.StatefulSetContainersGenerate}, - {"kubernetes_stateful_set_volumes", apps.StatefulSetVolumeColumns(), apps.StatefulSetVolumesGenerate}, - - // Autoscaling - {"kubernetes_horizontal_pod_autoscalers", autoscaling.HorizontalPodAutoscalersColumns(), autoscaling.HorizontalPodAutoscalerGenerate}, - - // Batch - {"kubernetes_cron_jobs", batch.CronJobColumns(), batch.CronJobsGenerate}, - {"kubernetes_jobs", batch.JobColumns(), batch.JobsGenerate}, - - // Core - {"kubernetes_component_statuses", core.ComponentStatusColumns(), core.ComponentStatusesGenerate}, - {"kubernetes_config_maps", core.ConfigMapColumns(), core.ConfigMapsGenerate}, - {"kubernetes_endpoint_subsets", core.EndpointSubsetColumns(), core.EndpointSubsetsGenerate}, - {"kubernetes_limit_ranges", core.LimitRangeColumns(), core.LimitRangesGenerate}, - {"kubernetes_namespaces", core.NamespaceColumns(), core.NamespacesGenerate}, - {"kubernetes_nodes", core.NodeColumns(), core.NodesGenerate}, - {"kubernetes_persistent_volume_claims", core.PersistentVolumeClaimColumns(), core.PersistentVolumeClaimsGenerate}, - {"kubernetes_persistent_volumes", core.PersistentVolumeColumns(), core.PersistentVolumesGenerate}, - {"kubernetes_pod_templates", core.PodTemplateColumns(), core.PodTemplatesGenerate}, - {"kubernetes_pod_template_containers", core.PodTemplateContainerColumns(), core.PodTemplateContainersGenerate}, - {"kubernetes_pod_templates_volumes", core.PodTemplateVolumeColumns(), core.PodTemplateVolumesGenerate}, - {"kubernetes_pods", core.PodColumns(), core.PodsGenerate}, - {"kubernetes_pod_containers", core.PodContainerColumns(), core.PodContainersGenerate}, - {"kubernetes_pod_volumes", core.PodVolumeColumns(), core.PodVolumesGenerate}, - {"kubernetes_resource_quotas", core.ResourceQuotaColumns(), core.ResourceQuotasGenerate}, - {"kubernetes_secrets", core.SecretColumns(), core.SecretsGenerate}, - {"kubernetes_service_accounts", core.ServiceAccountColumns(), core.ServiceAccountsGenerate}, - {"kubernetes_services", core.ServiceColumns(), core.ServicesGenerate}, - - // Discovery - {"kubernetes_api_resources", discovery.APIResourceColumns(), discovery.APIResourcesGenerate}, - {"kubernetes_info", discovery.InfoColumns(), discovery.InfoGenerate}, - - // Event - {"kubernetes_events", event.Columns(), event.Generate}, - - // Networking - {"kubernetes_ingress_classes", networking.IngressClassColumns(), networking.IngressClassesGenerate}, - {"kubernetes_ingresses", networking.IngressColumns(), networking.IngressesGenerate}, - {"kubernetes_network_policies", networking.NetworkPolicyColumns(), networking.NetworkPoliciesGenerate}, - - // Policy - {"kubernetes_pod_disruption_budgets", policy.PodDisruptionBudgetColumns(), policy.PodDisruptionBudgetsGenerate}, - {"kubernetes_pod_security_policies", policy.PodSecurityPolicyColumns(), policy.PodSecurityPoliciesGenerate}, - - // RBAC - {"kubernetes_cluster_role_binding_subjects", rbac.ClusterRoleBindingSubjectColumns(), rbac.ClusterRoleBindingSubjectsGenerate}, - {"kubernetes_cluster_role_policy_rules", rbac.ClusterRolePolicyRuleColumns(), rbac.ClusterRolePolicyRulesGenerate}, - {"kubernetes_role_binding_subjects", rbac.RoleBindingSubjectColumns(), rbac.RoleBindingSubjectsGenerate}, - {"kubernetes_role_policy_rules", rbac.RolePolicyRuleColumns(), rbac.RolePolicyRulesGenerate}, - - // Storage - {"kubernetes_csi_drivers", storage.CSIDriverColumns(), storage.CSIDriversGenerate}, - {"kubernetes_csi_node_drivers", storage.CSINodeDriverColumns(), storage.CSINodeDriversGenerate}, - // {"kubernetes_storage_capacities", storage.CSIStorageCapacityColumns(), storage.CSIStorageCapacitiesGenerate}, - {"kubernetes_storage_classes", storage.SCClassColumns(), storage.SCClassesGenerate}, - {"kubernetes_volume_attachments", storage.VolumeAttachmentColumns(), storage.VolumeAttachmentsGenerate}, - } -} diff --git a/infrastructure/kubequery/internal/k8s/tables/tables_test.go b/infrastructure/kubequery/internal/k8s/tables/tables_test.go deleted file mode 100644 index b52855a3f1..0000000000 --- a/infrastructure/kubequery/internal/k8s/tables/tables_test.go +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package tables - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGetTables(t *testing.T) { - ts := GetTables() - assert.NotNil(t, ts, "Invalid tables") - assert.True(t, len(ts) > 40, "Invalid tables count") -} diff --git a/infrastructure/kubequery/internal/k8s/utils.go b/infrastructure/kubequery/internal/k8s/utils.go deleted file mode 100644 index 56c47c5d1b..0000000000 --- a/infrastructure/kubequery/internal/k8s/utils.go +++ /dev/null @@ -1,181 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package k8s - -import ( - "encoding/json" - "fmt" - "reflect" - "strconv" - "strings" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/iancoleman/strcase" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -var replacements = map[string]string{ - "IPs": "Ips", - "URLs": "Urls", - "CIDRs": "Cidrs", - "WWIDs": "WwIds", - "WWNs": "WwNs", -} - -func makeKey(name string) string { - for k, v := range replacements { - name = strings.Replace(name, k, v, 1) - } - return strcase.ToSnake(name) -} - -func getFieldValue(field reflect.Value) string { - tp := field.Type() - kind := tp.Kind() - - if kind == reflect.Ptr { - if field.IsNil() { - return "" - } - - tp = field.Type().Elem() - kind = tp.Kind() - field = field.Elem() - } - - if tp.PkgPath() == "k8s.io/apimachinery/pkg/apis/meta/v1" && tp.Name() == "Time" { - i := field.Interface() - if i.(metav1.Time).UTC().IsZero() { - return "0" - } - return strconv.FormatInt(i.(metav1.Time).Unix(), 10) - } - - switch kind { - case reflect.Map, reflect.Slice: - if !field.IsNil() { - bytes, _ := json.Marshal(field.Interface()) - if bytes != nil { - return string(bytes) - } - } - case reflect.Struct: - bytes, _ := json.Marshal(field.Interface()) - if bytes != nil { - return string(bytes) - } - case reflect.String: - return string(field.String()) - case reflect.Bool: - if field.Bool() { - return "1" - } - return "0" - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return strconv.FormatInt(field.Int(), 10) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return strconv.FormatUint(field.Uint(), 10) - case reflect.Float32, reflect.Float64: - return fmt.Sprintf("%f", field.Float()) - default: - panic(fmt.Sprintf("Type not supported: %s", kind)) - } - - return "" -} - -// ToMap returns object fields as key/value map. Field names are converted to snake case. -// Values are converted to string. Complex value types like structures are serialized as JSON. -func ToMap(obj interface{}) map[string]string { - item := make(map[string]string) - val := reflect.ValueOf(obj) - if kind := val.Kind(); kind == reflect.Interface || kind == reflect.Ptr { - val = val.Elem() - } - - for i := 0; i < val.NumField(); i++ { - field := val.Field(i) - name := val.Type().Field(i).Name - - if val.Type().Field(i).Anonymous { - if field.Type().Kind() == reflect.Ptr { - panic(fmt.Sprintf("Embedded pointer to a struct not supported: %s", name)) - } - - for k, v := range ToMap(field.Interface()) { - item[k] = v - } - } else { - key := makeKey(name) - str := getFieldValue(val.Field(i)) - if str != "" { - item[key] = str - } - } - } - - return item -} - -func getFieldSchema(name string, field reflect.Value) table.ColumnDefinition { - tp := field.Type() - kind := tp.Kind() - key := makeKey(name) - - if kind == reflect.Ptr { - tp = field.Type().Elem() - kind = tp.Kind() - } - if tp.PkgPath() == "k8s.io/apimachinery/pkg/apis/meta/v1" && tp.Name() == "Time" { - return table.BigIntColumn(key) - } - - switch kind { - case reflect.Map, reflect.Slice, reflect.Struct, reflect.String: - return table.TextColumn(key) - case reflect.Float32, reflect.Float64: - return table.DoubleColumn(key) - case reflect.Int64, reflect.Uint64: - return table.BigIntColumn(key) - case reflect.Bool, reflect.Int, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint16, reflect.Uint32: - return table.IntegerColumn(key) - default: - panic(fmt.Sprintf("Type not supported: %s", kind)) - } -} - -// GetSchema takes a object and returns Osquery table column definitions. -// Object field names are converted to snake case. -// The object fields including anonymous ones are identified appropriate column definitions are identified. -func GetSchema(obj interface{}) []table.ColumnDefinition { - schema := make([]table.ColumnDefinition, 0) - val := reflect.ValueOf(obj) - if kind := val.Kind(); kind == reflect.Interface || kind == reflect.Ptr { - val = val.Elem() - } - - for i := 0; i < val.NumField(); i++ { - field := val.Field(i) - name := val.Type().Field(i).Name - - if val.Type().Field(i).Anonymous { - if field.Type().Kind() == reflect.Ptr { - panic(fmt.Sprintf("Embedded pointer to a struct not supported: %s", name)) - } - - s := GetSchema(field.Interface()) - schema = append(schema, s...) - } else { - schema = append(schema, getFieldSchema(name, field)) - } - } - - return schema -} diff --git a/infrastructure/kubequery/internal/k8s/utils_test.go b/infrastructure/kubequery/internal/k8s/utils_test.go deleted file mode 100644 index 44b7e310bf..0000000000 --- a/infrastructure/kubequery/internal/k8s/utils_test.go +++ /dev/null @@ -1,190 +0,0 @@ -/** - * Copyright (c) 2020-present, The kubequery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -package k8s - -import ( - "testing" - - "github.com/Uptycs/basequery-go/plugin/table" - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" -) - -func TestMakeKey(t *testing.T) { - assert.Equal(t, "host_ip", makeKey("HostIP")) - assert.Equal(t, "host_ips", makeKey("HostIPs")) - assert.Equal(t, "iscsi_interface", makeKey("ISCSIInterface")) -} - -func TestGetSchema(t *testing.T) { - assert.Equal(t, []table.ColumnDefinition{ - table.TextColumn("se_linux_options_user"), - table.TextColumn("se_linux_options_role"), - table.TextColumn("se_linux_options_type"), - table.TextColumn("se_linux_options_level"), - table.TextColumn("windows_options_gmsa_credential_spec_name"), - table.TextColumn("windows_options_gmsa_credential_spec"), - table.TextColumn("windows_options_run_as_user_name"), - table.TextColumn("seccomp_profile_type"), - table.TextColumn("seccomp_profile_localhost_profile"), - table.BigIntColumn("run_as_user"), - table.BigIntColumn("run_as_group"), - table.IntegerColumn("run_as_non_root"), - table.TextColumn("supplemental_groups"), - table.BigIntColumn("fs_group"), - table.TextColumn("sysctls"), - table.TextColumn("fs_group_change_policy"), - table.TextColumn("node_affinity"), - table.TextColumn("pod_affinity"), - table.TextColumn("pod_anti_affinity"), - table.TextColumn("dns_config_nameservers"), - table.TextColumn("dns_config_searches"), - table.TextColumn("dns_config_options"), - table.TextColumn("node_selector"), - table.TextColumn("restart_policy"), - table.BigIntColumn("termination_grace_period_seconds"), - table.BigIntColumn("active_deadline_seconds"), - table.TextColumn("dns_policy"), - table.TextColumn("service_account_name"), - table.IntegerColumn("automount_service_account_token"), - table.TextColumn("node_name"), - table.IntegerColumn("host_network"), - table.IntegerColumn("host_pid"), - table.IntegerColumn("host_ipc"), - table.IntegerColumn("share_process_namespace"), - table.TextColumn("image_pull_secrets"), - table.TextColumn("hostname"), - table.TextColumn("subdomain"), - table.TextColumn("scheduler_name"), - table.TextColumn("tolerations"), - table.TextColumn("host_aliases"), - table.TextColumn("priority_class_name"), - table.IntegerColumn("priority"), - table.TextColumn("readiness_gates"), - table.TextColumn("runtime_class_name"), - table.IntegerColumn("enable_service_links"), - table.TextColumn("preemption_policy"), - table.TextColumn("overhead"), - table.TextColumn("topology_spread_constraints"), - table.IntegerColumn("set_hostname_as_fqdn"), - }, GetSchema(CommonPodFields{})) -} - -func TestToMap(t *testing.T) { - i32 := int32(456) - i64 := int64(123) - b := bool(true) - s := string("s123") - assert.Equal(t, - map[string]string{ - "active_deadline_seconds": "123", - "automount_service_account_token": "1", - "dns_config_nameservers": "[\"\"]", - "dns_config_options": "[{},{},{}]", - "dns_config_searches": "[\"\",\"\"]", - "dns_policy": "ClusterFirst", - "enable_service_links": "1", - "fs_group": "123", - "fs_group_change_policy": "s123", - "host_aliases": "[{}]", - "host_ipc": "1", - "host_network": "1", - "host_pid": "1", - "hostname": "h123", - "image_pull_secrets": "[{},{},{}]", - "node_affinity": "{\"requiredDuringSchedulingIgnoredDuringExecution\":{\"nodeSelectorTerms\":null}}", - "node_name": "n123", - "node_selector": "{}", - "overhead": "{}", - "priority": "456", - "priority_class_name": "p123", - "readiness_gates": "[]", - "restart_policy": "Always", - "run_as_group": "123", - "run_as_non_root": "1", - "run_as_user": "123", - "runtime_class_name": "s123", - "scheduler_name": "sn123", - "se_linux_options_role": "r123", - "se_linux_options_type": "t123", - "se_linux_options_user": "u123", - "seccomp_profile_type": "Unconfined", - "service_account_name": "s123", - "set_hostname_as_fqdn": "1", - "share_process_namespace": "1", - "subdomain": "sub123", - "supplemental_groups": "[0]", - "sysctls": "[{\"name\":\"n1\",\"value\":\"v1\"}]", - "termination_grace_period_seconds": "123", - "tolerations": "[{},{}]", - "topology_spread_constraints": "[{\"maxSkew\":0,\"topologyKey\":\"\",\"whenUnsatisfiable\":\"\"},{\"maxSkew\":0,\"topologyKey\":\"\",\"whenUnsatisfiable\":\"\"},{\"maxSkew\":0,\"topologyKey\":\"\",\"whenUnsatisfiable\":\"\"},{\"maxSkew\":0,\"topologyKey\":\"\",\"whenUnsatisfiable\":\"\"}]", - }, - ToMap(CommonPodFields{ - RestartPolicy: v1.RestartPolicyAlways, - TerminationGracePeriodSeconds: &i64, - ActiveDeadlineSeconds: &i64, - DNSPolicy: v1.DNSClusterFirst, - ServiceAccountName: "s123", - AutomountServiceAccountToken: &b, - NodeSelector: make(map[string]string), - NodeName: "n123", - HostNetwork: true, - HostPID: true, - HostIPC: true, - ShareProcessNamespace: &b, - ImagePullSecrets: make([]v1.LocalObjectReference, 3), - Hostname: "h123", - Subdomain: "sub123", - SchedulerName: "sn123", - Tolerations: make([]v1.Toleration, 2), - HostAliases: make([]v1.HostAlias, 1), - PriorityClassName: "p123", - Priority: &i32, - ReadinessGates: []v1.PodReadinessGate{}, - RuntimeClassName: &s, - EnableServiceLinks: &b, - PreemptionPolicy: nil, - Overhead: make(v1.ResourceList), - TopologySpreadConstraints: make([]v1.TopologySpreadConstraint, 4), - SetHostnameAsFQDN: &b, - AffinityFields: AffinityFields{ - NodeAffinity: &v1.NodeAffinity{RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{}}, - PodAffinity: nil, - PodAntiAffinity: nil, - }, - DNSConfigFields: DNSConfigFields{ - DNSConfigNameservers: make([]string, 1), - DNSConfigSearches: make([]string, 2), - DNSConfigOptions: make([]v1.PodDNSConfigOption, 3), - }, - PodSecurityContextFields: PodSecurityContextFields{ - CommonSecurityContextFields: CommonSecurityContextFields{ - SELinuxOptionsFields: SELinuxOptionsFields{ - SELinuxOptionsUser: "u123", - SELinuxOptionsRole: "r123", - SELinuxOptionsType: "t123", - SELinuxOptionsLevel: "", - }, - WindowsOptionsFields: WindowsOptionsFields{}, - SeccompProfileFields: SeccompProfileFields{ - SeccompProfileType: v1.SeccompProfileTypeUnconfined, - SeccompProfileLocalhostProfile: nil, - }, - RunAsUser: &i64, - RunAsGroup: &i64, - RunAsNonRoot: &b, - }, - FSGroup: &i64, - FSGroupChangePolicy: (*v1.PodFSGroupChangePolicy)(&s), - Sysctls: []v1.Sysctl{{Name: "n1", Value: "v1"}}, - SupplementalGroups: make([]int64, 1), - }, - })) -} diff --git a/infrastructure/kubequery/internal/tools.go b/infrastructure/kubequery/internal/tools.go deleted file mode 100644 index 60502989c1..0000000000 --- a/infrastructure/kubequery/internal/tools.go +++ /dev/null @@ -1,7 +0,0 @@ -// +build tools - -package internal - -import ( - _ "golang.org/x/lint" -) diff --git a/infrastructure/kubequery/kubequery-template.yaml b/infrastructure/kubequery/kubequery-template.yaml deleted file mode 100644 index 9f890a6f95..0000000000 --- a/infrastructure/kubequery/kubequery-template.yaml +++ /dev/null @@ -1,123 +0,0 @@ -# Copyright (c) 2020-present, The kubequery authors -# -# This source code is licensed as defined by the LICENSE file found in the -# root directory of this source tree. -# -# SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - ---- -apiVersion: v1 -kind: Namespace -metadata: - name: kubequery - labels: - app.kubernetes.io/name: kubequery - app.kubernetes.io/part-of: kubequery - app.kubernetes.io/version: latest - ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: kubequery-sa - namespace: kubequery - labels: - app.kubernetes.io/name: kubequery-sa - app.kubernetes.io/part-of: kubequery - app.kubernetes.io/version: latest - ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: kubequery-clusterrole - labels: - app.kubernetes.io/name: kubequery-clusterrole - app.kubernetes.io/part-of: kubequery - app.kubernetes.io/version: latest -rules: -- apiGroups: ["", "admissionregistration.k8s.io", "apps", "autoscaling", "batch", "events.k8s.io", "networking.k8s.io", "policy", "rbac.authorization.k8s.io", "storage.k8s.io"] - resources: ["*"] - verbs: ["get", "list", "watch"] - ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: kubequery-clusterrolebinding - labels: - app.kubernetes.io/name: kubequery-clusterrolebinding - app.kubernetes.io/part-of: kubequery - app.kubernetes.io/version: latest -roleRef: - kind: ClusterRole - name: kubequery-clusterrole - apiGroup: rbac.authorization.k8s.io -subjects: -- kind: ServiceAccount - name: kubequery-sa - namespace: kubequery - ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: kubequery-config - namespace: kubequery - labels: - app.kubernetes.io/name: kubequery-config - app.kubernetes.io/part-of: kubequery - app.kubernetes.io/version: latest -data: - enroll.secret: TODO - kubequery.flags: | - kubequery.conf: | - ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: kubequery - namespace: kubequery - labels: - app.kubernetes.io/name: kubequery - app.kubernetes.io/part-of: kubequery - app.kubernetes.io/version: latest -spec: - replicas: 1 - selector: - matchLabels: - app.kubernetes.io/name: kubequery - template: - metadata: - labels: - app.kubernetes.io/name: kubequery - app.kubernetes.io/part-of: kubequery - app.kubernetes.io/version: latest - spec: - hostname: my-cluster # TODO: Give a friendly cluster name - securityContext: - runAsNonRoot: true - runAsUser: 1000 - runAsGroup: 1000 - fsGroup: 1000 - terminationGracePeriodSeconds: 10 - serviceAccountName: kubequery-sa - containers: - - name: kubequery - image: uptycs/kubequery:latest - imagePullPolicy: Always - resources: - requests: - cpu: 200m - memory: 128Mi - limits: - cpu: 1000m - memory: 512Mi - volumeMounts: - - name: config - mountPath: /opt/uptycs/config - volumes: - - name: config - configMap: - name: kubequery-config From 2cdead2dcec887f5398cacb6791080c03bb37575 Mon Sep 17 00:00:00 2001 From: Jahziel Villasana-Espinoza Date: Mon, 2 Dec 2024 17:30:18 -0500 Subject: [PATCH 78/92] fix: use the correct copy for a macos host (#24292) > Related issue: #23621 # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files) for more information. - [x] Manual QA for all new/changed functionality --- changes/23621-unlock-text | 1 + .../UnlockedHostActivityItem/UnlockedHostActivityItem.tsx | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changes/23621-unlock-text diff --git a/changes/23621-unlock-text b/changes/23621-unlock-text new file mode 100644 index 0000000000..6715062fdf --- /dev/null +++ b/changes/23621-unlock-text @@ -0,0 +1 @@ +- Fixes an issue with the copy for the activity generated by viewing a locked macOS host's PIN. \ No newline at end of file diff --git a/frontend/pages/hosts/details/cards/Activity/ActivityItems/UnlockedHostActivityItem/UnlockedHostActivityItem.tsx b/frontend/pages/hosts/details/cards/Activity/ActivityItems/UnlockedHostActivityItem/UnlockedHostActivityItem.tsx index 079f4463b7..6fa7cd0550 100644 --- a/frontend/pages/hosts/details/cards/Activity/ActivityItems/UnlockedHostActivityItem/UnlockedHostActivityItem.tsx +++ b/frontend/pages/hosts/details/cards/Activity/ActivityItems/UnlockedHostActivityItem/UnlockedHostActivityItem.tsx @@ -8,9 +8,13 @@ const baseClass = "unlocked-host-activity-item"; const UnlockedHostActivityItem = ({ activity, }: IHostActivityItemComponentProps) => { + let desc = "unlocked this host."; + if (activity.details?.host_platform === "darwin") { + desc = "viewed the six-digit unlock PIN for this host."; + } return ( - {activity.actor_full_name} unlocked this host. + {activity.actor_full_name} {desc} ); }; From ed8c3a3b98a6ff827fa2a6ef7a1ab0c006b8c5e3 Mon Sep 17 00:00:00 2001 From: jacobshandling <61553566+jacobshandling@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:28:28 -0800 Subject: [PATCH 79/92] UI - Only show 'follow instructions on My device' banner for encrypted and non-escrowed Linux hosts (#24277) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## #24248 For hosts with encrypted disks that Fleet does not have a key escrowed for: **Banner shown for Linux:** Screenshot 2024-12-02 at 10 52 08 AM **but not for Windows:** Screenshot 2024-12-02 at 10 49 54 AM - [x] Changes file added for user-visible changes in `changes/` - [x] Manual QA --------- Co-authored-by: Jacob Shandling --- changes/24248-host-details-encryption-banner | 2 ++ .../HostDetailsBanners/HostDetailsBanners.tsx | 16 ++++++---------- 2 files changed, 8 insertions(+), 10 deletions(-) create mode 100644 changes/24248-host-details-encryption-banner diff --git a/changes/24248-host-details-encryption-banner b/changes/24248-host-details-encryption-banner new file mode 100644 index 0000000000..7de5934177 --- /dev/null +++ b/changes/24248-host-details-encryption-banner @@ -0,0 +1,2 @@ +* Only show the "follow instructions on My device" banner for Linux hosts whose disks are encrypted +but for which Fleet hasn't escrowed a valid key. diff --git a/frontend/pages/hosts/details/HostDetailsPage/components/HostDetailsBanners/HostDetailsBanners.tsx b/frontend/pages/hosts/details/HostDetailsPage/components/HostDetailsBanners/HostDetailsBanners.tsx index 0e4eb8b851..cc1b6a73f6 100644 --- a/frontend/pages/hosts/details/HostDetailsPage/components/HostDetailsBanners/HostDetailsBanners.tsx +++ b/frontend/pages/hosts/details/HostDetailsPage/components/HostDetailsBanners/HostDetailsBanners.tsx @@ -8,13 +8,11 @@ import { IOSSettings } from "interfaces/host"; import { HostPlatform, isDiskEncryptionSupportedLinuxPlatform, - platformSupportsDiskEncryption, } from "interfaces/platform"; import InfoBanner from "components/InfoBanner"; import CustomLink from "components/CustomLink"; import { LEARN_MORE_ABOUT_BASE_LINK } from "utilities/constants"; -import { isDiskEncryptionProfile } from "pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingStatusCell/helpers"; const baseClass = "host-details-banners"; @@ -110,16 +108,13 @@ const HostDetailsBanners = ({
); } - // setting applies if ( hostPlatform && - platformSupportsDiskEncryption(hostPlatform, hostOsVersion) && + isDiskEncryptionSupportedLinuxPlatform(hostPlatform, hostOsVersion ?? "") && diskEncryptionOSSetting?.status ) { - if ( - !diskIsEncrypted && - isDiskEncryptionSupportedLinuxPlatform(hostPlatform, hostOsVersion ?? "") - ) { + // setting applies to a Linux host + if (!diskIsEncrypted) { // linux host not in compliance with setting return (
@@ -143,8 +138,9 @@ const HostDetailsBanners = ({ ); } if (!diskEncryptionKeyAvailable) { - // disk is encrypted, but Fleet doesn't yet have a disk - // encryption key escrowed (possible for Linux hosts) + // linux host's disk is encrypted, but Fleet doesn't yet have a disk + // encryption key escrowed (note that this state is also possible for Windows hosts, which we + // don't show this banner for currently) return (
From 763d050fd6a797a41713f6510667292b645337f4 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 2 Dec 2024 19:15:03 -0600 Subject: [PATCH 80/92] Website: update testimonials heading on landing pages. (#24304) Closes: #24255 Changes: - Updated the testimonials heading on landing pages to be the same across all landing pages/buying situations --- website/views/pages/device-management.ejs | 3 +-- website/views/pages/observability.ejs | 3 +-- website/views/pages/software-management.ejs | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/website/views/pages/device-management.ejs b/website/views/pages/device-management.ejs index 06b03c32f3..a5cfd2aaf4 100644 --- a/website/views/pages/device-management.ejs +++ b/website/views/pages/device-management.ejs @@ -510,8 +510,7 @@ <%/* End of page gradient */%>
-

Who else uses Fleet?

-

Empowering <%= ['vm', 'eo-security'].includes(primaryBuyingSituation) ? 'security and IT' : 'IT' %> teams

+

What people are saying

diff --git a/website/views/pages/observability.ejs b/website/views/pages/observability.ejs index 40bdc7bbd9..12e2ced0fd 100644 --- a/website/views/pages/observability.ejs +++ b/website/views/pages/observability.ejs @@ -289,8 +289,7 @@
-

Who else uses Fleet?

-

Empowering <%= ['mdm'].includes(pagePersonalization) ? 'IT and corporate engineering' : ['eo-it'].includes(pagePersonalization) ? 'IT and client platform' : ['eo-security'].includes(pagePersonalization) ? 'security and platform' : ['vm'].includes(pagePersonalization) ? 'security and IT' : 'IT and security' %> teams, globally

+

What people are saying

diff --git a/website/views/pages/software-management.ejs b/website/views/pages/software-management.ejs index 424077d3e3..bdb2f06c48 100644 --- a/website/views/pages/software-management.ejs +++ b/website/views/pages/software-management.ejs @@ -163,8 +163,7 @@
-

Who else uses Fleet?

-

Empowering teams, globally

+

What people are saying

From 528d158a62cb48f43b21e237ad4d3d83ca5f77bc Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 2 Dec 2024 19:20:32 -0600 Subject: [PATCH 81/92] Website: update spacing between elements on personalized homepage. (#24300) Closes: #24256 Changes: - updated margin between sections on the homepage. --- website/assets/styles/pages/homepage.less | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/assets/styles/pages/homepage.less b/website/assets/styles/pages/homepage.less index 42e6ab48b7..e00c3c2565 100644 --- a/website/assets/styles/pages/homepage.less +++ b/website/assets/styles/pages/homepage.less @@ -85,6 +85,7 @@ flex-direction: row; justify-content: center; margin-top: 64px; + margin-bottom: 64px; h4 { color: #515774; text-align: center; @@ -1663,6 +1664,11 @@ font-size: 16px; } } + [purpose='statistics'] { + margin-top: 32px; + margin-bottom: 32px; + } + [purpose='endpoints-banner'] { [purpose='endpoint-banner-text'] { padding-left: 24px; From 4f70fdac3bcde7c70c2532ed1d67a5711c954c1f Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 2 Dec 2024 19:25:29 -0600 Subject: [PATCH 82/92] Website: add link to /app-library to /docs sidebar (#24302) Closes: #24258 Changes: - Added a link to the /app-library page to the documentation sidebar --- website/views/pages/docs/basic-documentation.ejs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/views/pages/docs/basic-documentation.ejs b/website/views/pages/docs/basic-documentation.ejs index ae81fb56d2..10b4595701 100644 --- a/website/views/pages/docs/basic-documentation.ejs +++ b/website/views/pages/docs/basic-documentation.ejs @@ -112,6 +112,7 @@ Built-in checks Raw data + App library Release notes Contributing Support From 511ffd144f2908389216e242531efefeadcd65fa Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 2 Dec 2024 19:36:37 -0600 Subject: [PATCH 83/92] Website: Update /app-library page (#24301) Closes: #24257 Changes: - Added an app count and link to the contact form to the /app-library page. --- website/assets/styles/pages/app-library.less | 19 +++++++++++++++++++ website/views/pages/app-library.ejs | 6 ++++++ 2 files changed, 25 insertions(+) diff --git a/website/assets/styles/pages/app-library.less b/website/assets/styles/pages/app-library.less index 4373356385..0f5ba623ce 100644 --- a/website/assets/styles/pages/app-library.less +++ b/website/assets/styles/pages/app-library.less @@ -24,6 +24,25 @@ margin-bottom: 0px; } } + + [purpose='request-button'] { + display: flex; + padding: 8px 16px; + justify-content: center; + align-items: center; + border-radius: 16px; + background: #F9FAFC; + color: #515774; + text-align: center; + height: 49px; + font-size: 14px; + font-weight: 700; + line-height: 150%; + &:hover { + text-decoration: none; + } + } + [purpose='app-search'] { // Note: We're using classes here to override the default Docsearch styles; diff --git a/website/views/pages/app-library.ejs b/website/views/pages/app-library.ejs index cf67da5a9b..55b705546b 100644 --- a/website/views/pages/app-library.ejs +++ b/website/views/pages/app-library.ejs @@ -23,6 +23,12 @@
-->
+
+

{{allApps.length}} and counting....

+ +
<% for(let app of allApps) { %> From 93d6c029f4a739ea57031e47238a89816b46be82 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 2 Dec 2024 22:28:03 -0600 Subject: [PATCH 84/92] Msp dashboard: Update software endpoints (#24299) Changes: - Updated the software page to display different error messages when requests to software endpoints fail. - Updated the edit software endpoint to: - have three new exits, `softwareAlreadyExistsOnThisTeam`, `couldNotReadVersion` and `softwareDeletionFailed` - Create database records for newly undeployed software after it has been removed from all teams on a Fleet instance. - Return a `softwareAlreadyExistsOnThisTeam` response if undeployed software is being transferred to a team it is already deployed to. - Return a `couldNotReadVersion` response if the Fleet instance cannot read the version information from an uploaded software installer - Return a `softwareDeletionFailed` response if an installer cannot be deleted via an API request (If it is configured to be installed during the macOS setup experience) - Updated the upload-software endpoint to return a `couldNotReadVersion` response if the Fleet instance cannot read the version information from an uploaded software installer --- .../api/controllers/software/edit-software.js | 107 +++++++++++++++--- .../controllers/software/upload-software.js | 29 ++++- .../views/pages/software/software.ejs | 8 +- 3 files changed, 124 insertions(+), 20 deletions(-) diff --git a/ee/bulk-operations-dashboard/api/controllers/software/edit-software.js b/ee/bulk-operations-dashboard/api/controllers/software/edit-software.js index 983387100d..d97b0366a8 100644 --- a/ee/bulk-operations-dashboard/api/controllers/software/edit-software.js +++ b/ee/bulk-operations-dashboard/api/controllers/software/edit-software.js @@ -46,9 +46,23 @@ module.exports = { description: 'The provided replacement software\'s has the wrong extension.', statusCode: 400, }, + softwareUploadFailed: { description: 'The software upload failed' - } + }, + + softwareAlreadyExistsOnThisTeam: { + description: 'A software installer with this name already exists on the Fleet Instance', + }, + + couldNotReadVersion: { + description:'Fleet could not read version information from the provided software installer.' + }, + + softwareDeletionFailed: { + description: 'The specified software could not be deleted from the Fleet instance.', + statusCode: 409, + }, }, @@ -167,7 +181,35 @@ module.exports = { } }; }, - }) + } + ) + .intercept({response: {status: 409}}, async (error)=>{// handles errors related to duplicate software items. + if(!software.id) {// If the software does not have an ID, it not stored in the app's database/s3 bucket, so we can safely delete the file in s3. + await sails.rm(sails.config.uploads.prefixForFileDeletion+softwareFd); + } + return {'softwareAlreadyExistsOnThisTeam': error}; + }) + .intercept({name: 'AxiosError', response: {status: 400}}, async (error)=>{// Handles errors related to malformed installer packages + if(!software.id) {// If the software does not have an ID, it not stored in the app's database/s3 bucket, so we can safely delete the file in s3. + await sails.rm(sails.config.uploads.prefixForFileDeletion+softwareFd); + } + let axiosError = error; + if(axiosError.response.data) { + if(axiosError.response.data.errors && _.isArray(axiosError.response.data.errors)){ + if(axiosError.response.data.errors[0] && axiosError.response.data.errors[0].reason) { + let errorMessageFromFleetInstance = axiosError.response.data.errors[0].reason; + if(_.startsWith(errorMessageFromFleetInstance, `Couldn't add. Fleet couldn't read the version`)){ + return 'couldNotReadVersion'; + } else { + sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API. Error returned from Fleet API: ${errorMessageFromFleetInstance}`); + return {'softwareUploadFailed': error}; + } + } + } + } + sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 3})}`); + return {'softwareUploadFailed': error}; + }) .intercept(async (error)=>{ // Note: with this current behavior, all errors from this upload are currently swallowed and a softwareUploadFailed response is returned. // FUTURE: Test to make sure that uploading duplicate software to a team results in a 409 response. @@ -234,6 +276,33 @@ module.exports = { }; }, }) + .intercept({response: {status: 409}}, async (error)=>{// handles errors related to duplicate software items. + if(!software.id) {// If the software does not have an ID, it not stored in the app's database/s3 bucket, so we can safely delete the file in s3. + await sails.rm(sails.config.uploads.prefixForFileDeletion+softwareFd); + } + return {'softwareAlreadyExistsOnThisTeam': error}; + }) + .intercept({name: 'AxiosError', response: {status: 400}}, async (error)=>{// Handles errors related to malformed installer packages + if(!software.id) {// If the software does not have an ID, it not stored in the app's database/s3 bucket, so we can safely delete the file in s3. + await sails.rm(sails.config.uploads.prefixForFileDeletion+softwareFd); + } + let axiosError = error; + if(axiosError.response.data) { + if(axiosError.response.data.errors && _.isArray(axiosError.response.data.errors)){ + if(axiosError.response.data.errors[0] && axiosError.response.data.errors[0].reason) { + let errorMessageFromFleetInstance = axiosError.response.data.errors[0].reason; + if(_.startsWith(errorMessageFromFleetInstance, `Couldn't add. Fleet couldn't read the version`)){ + return 'couldNotReadVersion'; + } else { + sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API. Error returned from Fleet API: ${errorMessageFromFleetInstance}`); + return {'softwareUploadFailed': error}; + } + } + } + } + sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 3})}`); + return {'softwareUploadFailed': error}; + }) .intercept(async (error)=>{ // Note: with this current behavior, all errors from this upload are currently swallowed and a softwareUploadFailed response is returned. // FUTURE: Test to make sure that uploading duplicate software to a team results in a 409 response. @@ -279,6 +348,11 @@ module.exports = { headers: { Authorization: `Bearer ${sails.config.custom.fleetApiToken}`, } + }) + .intercept({raw:{statusCode: 409}}, (error)=>{ + // If the Fleet instance's returns a 409 response, then the software is configured to be installed as + // part of the macOS setup experience, and must be removed before it can be deleted via API requests. + return {softwareDeletionFailed: error}; }); } // If the software had been previously undeployed, delete the installer in s3 and the db record. @@ -289,9 +363,23 @@ module.exports = { } else if(software.teams && newTeamIds.length === 0) { // If this is a deployed software that is being unassigned, save information about the uploaded file in our s3 bucket. + for(let team of software.teams) { + // Now delete the software on the Fleet instance. + await sails.helpers.http.sendHttpRequest.with({ + method: 'DELETE', + baseUrl: sails.config.custom.fleetBaseUrl, + url: `/api/v1/fleet/software/titles/${software.fleetApid}/available_for_install?team_id=${team.fleetApid}`, + headers: { + Authorization: `Bearer ${sails.config.custom.fleetApiToken}`, + } + }) + .intercept({raw:{statusCode: 409}}, (error)=>{ + // If the Fleet instance's returns a 409 response, then the software is configured to be installed as + // part of the macOS setup experience, and must be removed before it can be deleted via API requests. + return {softwareDeletionFailed: error}; + }); + } if(newSoftware) { - // remove the old copy. - // console.log('Removing old package for ',softwareName); await UndeployedSoftware.create({ uploadFd: softwareFd, uploadMime: softwareMime, @@ -315,17 +403,6 @@ module.exports = { uninstallScript, }); } - // Now delete the software on the Fleet instance. - for(let team of software.teams) { - await sails.helpers.http.sendHttpRequest.with({ - method: 'DELETE', - baseUrl: sails.config.custom.fleetBaseUrl, - url: `/api/v1/fleet/software/titles/${software.fleetApid}/available_for_install?team_id=${team.fleetApid}`, - headers: { - Authorization: `Bearer ${sails.config.custom.fleetApiToken}`, - } - }); - } } else { // console.log('updating existing db record!'); diff --git a/ee/bulk-operations-dashboard/api/controllers/software/upload-software.js b/ee/bulk-operations-dashboard/api/controllers/software/upload-software.js index 8a1ea99ed5..987bfe8792 100644 --- a/ee/bulk-operations-dashboard/api/controllers/software/upload-software.js +++ b/ee/bulk-operations-dashboard/api/controllers/software/upload-software.js @@ -35,6 +35,10 @@ module.exports = { softwareUploadFailed: { description:'An unexpected error occurred communicating with the Fleet API' + }, + + couldNotReadVersion: { + description:'Fleet could not read version information from the provided software installer.' } }, @@ -100,13 +104,32 @@ module.exports = { }; } }) - .intercept({response: {status: 409}}, async (error)=>{ + .intercept({response: {status: 409}}, async (error)=>{// handles errors related to duplicate software items. await sails.rm(sails.config.uploads.prefixForFileDeletion+uploadedSoftware.fd); return {'softwareAlreadyExistsOnThisTeam': error}; }) - .intercept({name: 'AxiosError'}, async (error)=>{ + .intercept({name: 'AxiosError', response: {status: 400}}, async (error)=>{// Handles errors related to malformed installer packages await sails.rm(sails.config.uploads.prefixForFileDeletion+uploadedSoftware.fd); - sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 2})}`); + let axiosError = error; + if(axiosError.response.data) { + if(axiosError.response.data.errors && _.isArray(axiosError.response.data.errors)){ + if(axiosError.response.data.errors[0] && axiosError.response.data.errors[0].reason) { + let errorMessageFromFleetInstance = axiosError.response.data.errors[0].reason; + if(_.startsWith(errorMessageFromFleetInstance, `Couldn't add. Fleet couldn't read the version`)){ + return 'couldNotReadVersion'; + } else { + sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API. Error returned from Fleet API: ${errorMessageFromFleetInstance}`); + return {'softwareUploadFailed': error}; + } + } + } + } + sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 3})}`); + return {'softwareUploadFailed': error}; + }) + .intercept({name: 'AxiosError'}, async (error)=>{// Handles any other error. + await sails.rm(sails.config.uploads.prefixForFileDeletion+uploadedSoftware.fd); + sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 3})}`); return {'softwareUploadFailed': error}; }); } diff --git a/ee/bulk-operations-dashboard/views/pages/software/software.ejs b/ee/bulk-operations-dashboard/views/pages/software/software.ejs index eff9c8e989..dcc8b823aa 100644 --- a/ee/bulk-operations-dashboard/views/pages/software/software.ejs +++ b/ee/bulk-operations-dashboard/views/pages/software/software.ejs @@ -145,8 +145,11 @@

Teams

- {{cloudError.responseInfo.body}} - + {{cloudError.responseInfo.body}} + The Fleet instance could not read version information from the provided software installer. + This software has been configured to be installed as part of the macOS setup experience and cannot be removed from a team. Please remove this software from any teams you want to remove this from in the "Setup experience" tab of the Controls page on your Fleet instance and try again + An error occured when transfering this software to a new team. A software installer with the same name as this software already exists on one or more of the selected teams. +
Save
@@ -189,6 +192,7 @@
Please select the teams you want to deploy this software to.
A software with the same name as the uploaded software already exists on one or more of the selected teams. + The Fleet instance could not read version information from the provided software installer.
Cancel From f6425e686ce4fee38cd9f89fef87ab55101fe713 Mon Sep 17 00:00:00 2001 From: RachelElysia <71795832+RachelElysia@users.noreply.github.com> Date: Tue, 3 Dec 2024 09:08:11 -0500 Subject: [PATCH 85/92] Fleet UI: Update Dropdown to use react-select 5.4 and other cleanup (#24164) --- .../TeamsDropdown/TeamsDropdown.tsx | 2 +- .../DropdownOptionTooltipWrapper/_styles.scss | 5 +- .../DropdownWrapper.stories.tsx | 62 ++++ .../DropdownWrapper/DropdownWrapper.tests.tsx | 102 ++++++ .../DropdownWrapper/DropdownWrapper.tsx | 340 ++++++++++++++++++ .../forms/fields/DropdownWrapper/_styles.scss | 14 + .../forms/fields/DropdownWrapper/index.tsx | 1 + frontend/interfaces/role.ts | 16 - .../UsersPage/UsersPage.tsx | 8 +- .../UserManagementPage/UserManagementPage.tsx | 14 +- .../SelectRoleForm/SelectRoleForm.tsx | 65 ++-- .../SelectedTeamsForm/SelectedTeamsForm.tsx | 21 +- .../components/SelectedTeamsForm/_styles.scss | 2 +- .../components/UserForm/UserForm.tsx | 1 - .../helpers/userManagementHelpers.ts | 13 +- 15 files changed, 568 insertions(+), 98 deletions(-) create mode 100644 frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.stories.tsx create mode 100644 frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tests.tsx create mode 100644 frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tsx create mode 100644 frontend/components/forms/fields/DropdownWrapper/_styles.scss create mode 100644 frontend/components/forms/fields/DropdownWrapper/index.tsx delete mode 100644 frontend/interfaces/role.ts diff --git a/frontend/components/TeamsDropdown/TeamsDropdown.tsx b/frontend/components/TeamsDropdown/TeamsDropdown.tsx index e38b1c1b13..bc59c6fb0c 100644 --- a/frontend/components/TeamsDropdown/TeamsDropdown.tsx +++ b/frontend/components/TeamsDropdown/TeamsDropdown.tsx @@ -18,7 +18,7 @@ import { import Icon from "components/Icon"; -interface INumberDropdownOption extends Omit { +export interface INumberDropdownOption extends Omit { value: number; // Redefine the value property to be just number } diff --git a/frontend/components/forms/fields/Dropdown/DropdownOptionTooltipWrapper/_styles.scss b/frontend/components/forms/fields/Dropdown/DropdownOptionTooltipWrapper/_styles.scss index 7fcd24ad33..23bf82f070 100644 --- a/frontend/components/forms/fields/Dropdown/DropdownOptionTooltipWrapper/_styles.scss +++ b/frontend/components/forms/fields/Dropdown/DropdownOptionTooltipWrapper/_styles.scss @@ -1,7 +1,8 @@ // Used with old react-select dropdown and -// New react-select-5 ActionsDropdown.tsx +// New react-select-5: ActionsDropdown.tsx, DropdownWrapper.tsx .Select > .Select-menu-outer, -.actions-dropdown { +.actions-dropdown, +.react-select__option { .is-disabled * { color: $ui-fleet-black-50; } diff --git a/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.stories.tsx b/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.stories.tsx new file mode 100644 index 0000000000..ee0d223c58 --- /dev/null +++ b/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.stories.tsx @@ -0,0 +1,62 @@ +// stories/DropdownWrapper.stories.tsx + +import React from "react"; +import { Meta, Story } from "@storybook/react"; +import DropdownWrapper, { + IDropdownWrapper, + CustomOptionType, +} from "./DropdownWrapper"; + +// Define metadata for the story +export default { + title: "Components/DropdownWrapper", + component: DropdownWrapper, + argTypes: { + onChange: { action: "changed" }, + }, +} as Meta; + +// Define a template for the stories +const Template: Story = (args) => ( + +); + +// Sample options to be used in the dropdown +const sampleOptions: CustomOptionType[] = [ + { label: "Option 1", value: "option1", helpText: "Help text for option 1" }, + { + label: "Option 2", + value: "option2", + tooltipContent: "Tooltip for option 2", + }, + { label: "Option 3", value: "option3", isDisabled: true }, +]; + +// Default story +export const Default = Template.bind({}); +Default.args = { + options: sampleOptions, + name: "dropdown-example", + label: "Select an option", +}; + +// Disabled story +export const Disabled = Template.bind({}); +Disabled.args = { + ...Default.args, + isDisabled: true, +}; + +// With Help Text story +export const WithHelpText = Template.bind({}); +WithHelpText.args = { + ...Default.args, + helpText: "This is some help text for the dropdown", +}; + +// With Error story +export const WithError = Template.bind({}); +WithError.args = { + ...Default.args, + error: "This is an error message", +}; diff --git a/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tests.tsx b/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tests.tsx new file mode 100644 index 0000000000..2715876f8b --- /dev/null +++ b/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tests.tsx @@ -0,0 +1,102 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import DropdownWrapper, { CustomOptionType } from "./DropdownWrapper"; + +const sampleOptions: CustomOptionType[] = [ + { + label: "Option 1", + value: "option1", + tooltipContent: "Tooltip 1", + helpText: "Help text 1", + }, + { + label: "Option 2", + value: "option2", + tooltipContent: "Tooltip 2", + helpText: "Help text 2", + }, +]; + +describe("DropdownWrapper Component", () => { + const mockOnChange = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + test("renders with help text", () => { + render( + + ); + + expect(screen.getByText(/test dropdown/i)).toBeInTheDocument(); + expect(screen.getByText(/this is a help text/i)).toBeInTheDocument(); + }); + + test("calls onChange when an option is selected", async () => { + render( + + ); + + // Open the dropdown + await userEvent.click(screen.getByText(/option 1/i)); + + // Select Option 2 + await userEvent.click(screen.getByText(/option 2/i)); + + expect(mockOnChange).toHaveBeenCalledWith({ + helpText: "Help text 2", + label: "Option 2", + tooltipContent: "Tooltip 2", + value: "option2", + }); + }); + + test("renders error message when provided", () => { + render( + + ); + + expect(screen.getByText(/this is an error message/i)).toBeInTheDocument(); + }); + + test("displays no options message when no options are available", async () => { + render( + + ); + + // Open dropdown + await userEvent.click(screen.getByText(/choose option/i)); + + expect(screen.getByText(/no results found/i)).toBeInTheDocument(); + }); +}); diff --git a/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tsx b/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tsx new file mode 100644 index 0000000000..51c5c3cd69 --- /dev/null +++ b/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tsx @@ -0,0 +1,340 @@ +/** + * This is a new component built off react-select 5.4 + * meant to replace Dropdown.jsx built off react-select 1.3 + * + * See storybook component for current functionality + * + * Prototyped on UserForm.tsx but added and tested the following: + * Options: text, disabled, option helptext, option tooltip + * Other: label text, dropdown help text, dropdown error + */ + +import classnames from "classnames"; +import React from "react"; +import Select, { + StylesConfig, + DropdownIndicatorProps, + OptionProps, + components, + PropsValue, + SingleValue, +} from "react-select-5"; + +import { COLORS } from "styles/var/colors"; +import { PADDING } from "styles/var/padding"; + +import FormField from "components/forms/FormField"; +import DropdownOptionTooltipWrapper from "components/forms/fields/Dropdown/DropdownOptionTooltipWrapper"; +import Icon from "components/Icon"; + +const getOptionBackgroundColor = (state: any) => { + return state.isSelected || state.isFocused + ? COLORS["ui-vibrant-blue-10"] + : "transparent"; +}; + +export interface CustomOptionType { + label: string; + value: string; + tooltipContent?: string; + helpText?: string; + isDisabled?: boolean; +} + +export interface IDropdownWrapper { + options: CustomOptionType[]; + value?: PropsValue | string; + onChange: (newValue: SingleValue) => void; + name: string; + className?: string; + labelClassname?: string; + error?: string; + label?: JSX.Element | string; + helpText?: JSX.Element | string; + isSearchable?: boolean; + isDisabled?: boolean; + placeholder?: string; + menuPortalTarget?: HTMLElement | null; +} + +const baseClass = "dropdown-wrapper"; + +const DropdownWrapper = ({ + options, + value, + onChange, + name, + className, + labelClassname, + error, + label, + helpText, + isSearchable, + isDisabled = false, + placeholder, + menuPortalTarget, +}: IDropdownWrapper) => { + const wrapperClassNames = classnames(baseClass, className); + + const handleChange = (newValue: SingleValue) => { + onChange(newValue); + }; + + // Ability to handle value of type string or CustomOptionType + const getCurrentValue = () => { + if (typeof value === "string") { + return options.find((option) => option.value === value) || null; + } + return value; + }; + + interface CustomOptionProps + extends Omit, "data"> { + data: CustomOptionType; + } + + const CustomOption = (props: CustomOptionProps) => { + const { data, ...rest } = props; + + const optionContent = ( +
+ {data.label} + {data.helpText && ( + {data.helpText} + )} +
+ ); + + return ( + + {data.tooltipContent ? ( + + {optionContent} + + ) : ( + optionContent + )} + + ); + }; + + const CustomDropdownIndicator = ( + props: DropdownIndicatorProps + ) => { + const { isFocused, selectProps } = props; + const color = + isFocused || selectProps.menuIsOpen + ? "core-fleet-blue" + : "core-fleet-black"; + + return ( + + + + ); + }; + + const customStyles: StylesConfig = { + container: (provided) => ({ + ...provided, + width: "100%", + height: "40px", + }), + control: (provided, state) => ({ + ...provided, + display: "flex", + flexDirection: "row", + width: "100%", + backgroundColor: COLORS["ui-off-white"], + paddingLeft: "8px", // TODO: Update to match styleguide of (16px) when updating rest of UI (8px) + paddingRight: "8px", + cursor: "pointer", + boxShadow: "none", + borderRadius: "4px", + borderColor: state.isFocused + ? COLORS["core-fleet-blue"] + : COLORS["ui-fleet-black-10"], + "&:hover": { + boxShadow: "none", + borderColor: COLORS["core-fleet-blue"], + ".dropdown-wrapper__single-value": { + color: COLORS["core-vibrant-blue-over"], + }, + ".dropdown-wrapper__indicator path": { + stroke: COLORS["core-vibrant-blue-over"], + }, + }, + // When tabbing + // Relies on --is-focused for styling as &:focus-visible cannot be applied + "&.dropdown-wrapper__control--is-focused": { + ".dropdown-wrapper__single-value": { + color: COLORS["core-vibrant-blue-over"], + }, + ".dropdown-wrapper__indicator path": { + stroke: COLORS["core-vibrant-blue-over"], + }, + }, + ...(state.isDisabled && { + ".dropdown-wrapper__single-value": { + color: COLORS["ui-fleet-black-50"], + }, + ".dropdown-wrapper__indicator path": { + stroke: COLORS["ui-fleet-black-50"], + }, + }), + "&:active": { + ".dropdown-wrapper__single-value": { + color: COLORS["core-vibrant-blue-down"], + }, + ".dropdown-wrapper__indicator path": { + stroke: COLORS["core-vibrant-blue-down"], + }, + }, + ...(state.menuIsOpen && { + ".dropdown-wrapper__indicator svg": { + transform: "rotate(180deg)", + transition: "transform 0.25s ease", + }, + }), + }), + singleValue: (provided) => ({ + ...provided, + fontSize: "16px", + margin: 0, + padding: 0, + }), + dropdownIndicator: (provided) => ({ + ...provided, + display: "flex", + padding: "2px", + svg: { + transition: "transform 0.25s ease", + }, + }), + menu: (provided) => ({ + ...provided, + boxShadow: "0 2px 6px rgba(0, 0, 0, 0.1)", + borderRadius: "4px", + zIndex: 6, + overflow: "hidden", + border: 0, + marginTop: 0, + maxHeight: "none", + position: "absolute", + left: "0", + animation: "fade-in 150ms ease-out", + }), + menuList: (provided) => ({ + ...provided, + padding: PADDING["pad-small"], + }), + valueContainer: (provided) => ({ + ...provided, + padding: 0, + }), + option: (provided, state) => ({ + ...provided, + padding: "10px 8px", + fontSize: "14px", + backgroundColor: getOptionBackgroundColor(state), + color: COLORS["core-fleet-black"], + "&:hover": { + backgroundColor: state.isDisabled + ? "transparent" + : COLORS["ui-vibrant-blue-10"], + }, + "&:active": { + backgroundColor: state.isDisabled + ? "transparent" + : COLORS["ui-vibrant-blue-10"], + }, + ...(state.isDisabled && { + color: COLORS["ui-fleet-black-50"], + fontStyle: "italic", + cursor: "not-allowed", + pointerEvents: "none", + }), + // Styles for custom option + ".dropdown-wrapper__option": { + display: "flex", + flexDirection: "column", + gap: "8px", + width: "100%", + }, + ".dropdown-wrapper__help-text": { + fontSize: "12px", + whiteSpace: "normal", + color: COLORS["ui-fleet-black-50"], + fontStyle: "italic", + }, + }), + menuPortal: (base) => ({ ...base, zIndex: 999 }), // Not hidden beneath scrollable sections + noOptionsMessage: (provided) => ({ + ...provided, + textAlign: "left", + fontSize: "14px", + padding: "10px 8px", + }), + }; + + const renderLabel = () => { + const labelWrapperClasses = classnames( + `${baseClass}__label`, + labelClassname, + { + [`${baseClass}__label--error`]: !!error, + [`${baseClass}__label--disabled`]: isDisabled, + } + ); + + if (!label) { + return ""; + } + + return ( + + ); + }; + + return ( + + + classNamePrefix="react-select" + isSearchable={isSearchable} + styles={customStyles} + options={options} + components={{ + Option: CustomOption, + DropdownIndicator: CustomDropdownIndicator, + IndicatorSeparator: () => null, + }} + value={getCurrentValue()} + onChange={handleChange} + isDisabled={isDisabled} + menuPortalTarget={ + menuPortalTarget === undefined ? document.body : menuPortalTarget + } + noOptionsMessage={() => "No results found"} + tabIndex={isDisabled ? -1 : 0} // Ensures disabled dropdown has no keyboard accessibility + placeholder={placeholder} + /> + + ); +}; + +export default DropdownWrapper; diff --git a/frontend/components/forms/fields/DropdownWrapper/_styles.scss b/frontend/components/forms/fields/DropdownWrapper/_styles.scss new file mode 100644 index 0000000000..2a92f5ced6 --- /dev/null +++ b/frontend/components/forms/fields/DropdownWrapper/_styles.scss @@ -0,0 +1,14 @@ +// react-select's