diff --git a/changes/14500-disable-scripts-impl b/changes/14500-disable-scripts-impl new file mode 100644 index 0000000000..d26e5e832f --- /dev/null +++ b/changes/14500-disable-scripts-impl @@ -0,0 +1 @@ +- Adds ability to disable Scripts feature in Fleet through a new config flag: `scripts_disabled`. \ No newline at end of file diff --git a/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json b/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json index 05d1de8ef3..cf01e91342 100644 --- a/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json +++ b/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json @@ -13,7 +13,8 @@ "live_query_disabled": false, "query_reports_disabled": false, "enable_analytics": false, - "deferred_save_host": false + "deferred_save_host": false, + "scripts_disabled": false }, "smtp_settings": { "enable_smtp": false, diff --git a/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml b/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml index 9d4d4a6e3a..7597345851 100644 --- a/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml +++ b/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml @@ -56,6 +56,7 @@ spec: live_query_disabled: false query_reports_disabled: false server_url: "" + scripts_disabled: false smtp_settings: authentication_method: "" authentication_type: "" diff --git a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json index 2964165e03..216dbd75b3 100644 --- a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json +++ b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json @@ -13,7 +13,8 @@ "live_query_disabled": false, "query_reports_disabled": false, "enable_analytics": false, - "deferred_save_host": false + "deferred_save_host": false, + "scripts_disabled": false }, "smtp_settings": { "enable_smtp": false, diff --git a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml index 69539a4d27..3d614097fa 100644 --- a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml +++ b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml @@ -95,6 +95,7 @@ spec: live_query_disabled: false query_reports_disabled: false server_url: "" + scripts_disabled: false smtp_settings: authentication_method: "" authentication_type: "" diff --git a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml index 1ce4b5737f..a453a2f7f5 100644 --- a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml +++ b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml @@ -56,6 +56,7 @@ spec: live_query_disabled: false query_reports_disabled: false server_url: https://example.org + scripts_disabled: false smtp_settings: authentication_method: "" authentication_type: "" diff --git a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml index bff74ddc1c..5826922729 100644 --- a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml +++ b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml @@ -56,6 +56,7 @@ spec: live_query_disabled: false query_reports_disabled: false server_url: https://example.org + scripts_disabled: false smtp_settings: authentication_method: "" authentication_type: "" diff --git a/frontend/__mocks__/configMock.ts b/frontend/__mocks__/configMock.ts index e73ce1a729..26b996662a 100644 --- a/frontend/__mocks__/configMock.ts +++ b/frontend/__mocks__/configMock.ts @@ -13,6 +13,7 @@ const DEFAULT_CONFIG_MOCK: IConfig = { enable_analytics: true, deferred_save_host: false, query_reports_disabled: false, + scripts_disabled: false, }, smtp_settings: { enable_smtp: false, diff --git a/frontend/components/TableContainer/DataTable/DropdownCell/DropdownCell.tsx b/frontend/components/TableContainer/DataTable/DropdownCell/DropdownCell.tsx index 24cdb8521f..6eb4fad3ea 100644 --- a/frontend/components/TableContainer/DataTable/DropdownCell/DropdownCell.tsx +++ b/frontend/components/TableContainer/DataTable/DropdownCell/DropdownCell.tsx @@ -12,12 +12,14 @@ interface IDropdownCellProps { options: IDropdownOption[]; placeholder: string; onChange: (value: string) => void; + disabled?: boolean; } const DropdownCell = ({ options, placeholder, onChange, + disabled, }: IDropdownCellProps): JSX.Element => { return (
@@ -26,6 +28,7 @@ const DropdownCell = ({ placeholder={placeholder} searchable={false} options={options} + disabled={disabled ?? false} />
); diff --git a/frontend/components/TableContainer/DataTable/DropdownCell/_styles.scss b/frontend/components/TableContainer/DataTable/DropdownCell/_styles.scss index f04bca94d7..eaf1d1c3c2 100644 --- a/frontend/components/TableContainer/DataTable/DropdownCell/_styles.scss +++ b/frontend/components/TableContainer/DataTable/DropdownCell/_styles.scss @@ -21,6 +21,14 @@ } } + &.is-disabled { + .Select-control { + .Select-placeholder { + @include disabled; + } + } + } + .Select-control { display: flex; background-color: initial; diff --git a/frontend/interfaces/config.ts b/frontend/interfaces/config.ts index 3305330548..e34992fdcc 100644 --- a/frontend/interfaces/config.ts +++ b/frontend/interfaces/config.ts @@ -118,6 +118,7 @@ export interface IConfig { enable_analytics: boolean; deferred_save_host: boolean; query_reports_disabled: boolean; + scripts_disabled: boolean; }; smtp_settings: { enable_smtp: boolean; diff --git a/frontend/pages/ManageControlsPage/Scripts/Scripts.tsx b/frontend/pages/ManageControlsPage/Scripts/Scripts.tsx index 0140fdc7f0..4e0d8eff2c 100644 --- a/frontend/pages/ManageControlsPage/Scripts/Scripts.tsx +++ b/frontend/pages/ManageControlsPage/Scripts/Scripts.tsx @@ -15,6 +15,7 @@ import CustomLink from "components/CustomLink"; import Spinner from "components/Spinner"; import DataError from "components/DataError"; import PremiumFeatureMessage from "components/PremiumFeatureMessage"; +import InfoBanner from "components/InfoBanner"; import ScriptListHeading from "./components/ScriptListHeading"; import ScriptListItem from "./components/ScriptListItem"; import ScriptListPagination from "./components/ScriptListPagination"; @@ -75,6 +76,9 @@ const Scripts = ({ router, currentPage, teamIdForApi }: IScriptsProps) => { router.push(path.concat(`&page=${currentPage + 1}`)); }, [router, path, currentPage]); + const { config } = useContext(AppContext); + if (!config) return null; + // The user is not a premium tier, so show the premium feature message. if (!isPremiumTier) { return ( @@ -137,6 +141,15 @@ const Scripts = ({ router, currentPage, teamIdForApi }: IScriptsProps) => { ); }; + const renderScriptsDisabledBanner = () => ( + +
+ Running scripts is disabled in organization settings. You can + still manage your library of macOS and Windows scripts below. +
+
+ ); + return (

@@ -148,6 +161,8 @@ const Scripts = ({ router, currentPage, teamIdForApi }: IScriptsProps) => { newTab />

+ + {config.server_settings.scripts_disabled && renderScriptsDisabledBanner()} {renderScriptsList()} {showDeleteScriptModal && selectedScript.current && ( diff --git a/frontend/pages/admin/OrgSettingsPage/cards/Advanced/Advanced.tsx b/frontend/pages/admin/OrgSettingsPage/cards/Advanced/Advanced.tsx index ae20bbbbf6..98999e1102 100644 --- a/frontend/pages/admin/OrgSettingsPage/cards/Advanced/Advanced.tsx +++ b/frontend/pages/admin/OrgSettingsPage/cards/Advanced/Advanced.tsx @@ -28,6 +28,7 @@ const Advanced = ({ disableLiveQuery: appConfig.server_settings.live_query_disabled || false, disableQueryReports: appConfig.server_settings.query_reports_disabled || false, + disableScripts: appConfig.server_settings.scripts_disabled || false, }); const { @@ -37,6 +38,7 @@ const Advanced = ({ enableHostExpiry, hostExpiryWindow, disableLiveQuery, + disableScripts, disableQueryReports, } = formData; @@ -68,6 +70,7 @@ const Advanced = ({ live_query_disabled: disableLiveQuery, enable_analytics: appConfig.server_settings.enable_analytics, query_reports_disabled: disableQueryReports, + scripts_disabled: disableScripts, }, smtp_settings: { enable_smtp: appConfig.smtp_settings.enable_smtp || false, @@ -210,6 +213,24 @@ const Advanced = ({ > Disable live queries + + Disabling scripts will block access to run scripts. Scripts{" "} +
may still be added and removed in the UI and API.{" "} +
+ + (Default: Off) + +

+ } + > + Disable scripts +
{ @@ -93,7 +97,10 @@ const Scripts = ({ if (isErrorScriptData) { return ; } - const scriptColumnConfigs = generateTableColumnConfigs(onActionSelection); + const scriptColumnConfigs = generateTableColumnConfigs( + onActionSelection, + config.server_settings.scripts_disabled + ); const data = generateDataSet( currentUser, host, diff --git a/frontend/pages/hosts/details/cards/Scripts/ScriptsTableConfig.tsx b/frontend/pages/hosts/details/cards/Scripts/ScriptsTableConfig.tsx index 16010fb87b..ec24c041f4 100644 --- a/frontend/pages/hosts/details/cards/Scripts/ScriptsTableConfig.tsx +++ b/frontend/pages/hosts/details/cards/Scripts/ScriptsTableConfig.tsx @@ -17,6 +17,7 @@ import { isGlobalObserver, isTeamObserver, } from "utilities/permissions/permissions"; +import TooltipWrapper from "components/TooltipWrapper"; import ScriptStatusCell from "./components/ScriptStatusCell"; @@ -67,7 +68,8 @@ const ScriptRunActionDropdownLabel = ({ // eslint-disable-next-line import/prefer-default-export export const generateTableColumnConfigs = ( - actionSelectHandler: (value: string, script: IHostScript) => void + actionSelectHandler: (value: string, script: IHostScript) => void, + disableActions = false ) => { return [ { @@ -90,15 +92,35 @@ export const generateTableColumnConfigs = ( Header: "", disableSortBy: true, accessor: "actions", - Cell: (cellProps: IDropdownCellProps) => ( - - actionSelectHandler(value, cellProps.row.original) - } - placeholder={"Actions"} - /> - ), + Cell: (cellProps: IDropdownCellProps) => + disableActions ? ( + + Running scripts is disabled in organization settings
+ } + > + + actionSelectHandler(value, cellProps.row.original) + } + placeholder={"Actions"} + disabled={disableActions} + /> + + + ) : ( + + actionSelectHandler(value, cellProps.row.original) + } + placeholder={"Actions"} + disabled={disableActions} + /> + ), }, ]; }; diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql index 4a294028df..b2c221cd40 100644 --- a/server/datastore/mysql/schema.sql +++ b/server/datastore/mysql/schema.sql @@ -41,7 +41,7 @@ CREATE TABLE `app_config_json` ( UNIQUE KEY `id` (`id`) ) 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\": {\"macos_setup\": {\"bootstrap_package\": null, \"macos_setup_assistant\": null, \"enable_end_user_authentication\": false}, \"macos_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}, \"windows_settings\": {\"custom_settings\": null}, \"apple_bm_default_team\": \"\", \"apple_bm_terms_expired\": false, \"enable_disk_encryption\": false, \"enabled_and_configured\": false, \"end_user_authentication\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"issuer_uri\": \"\", \"metadata_url\": \"\"}, \"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}, \"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, \"deferred_save_host\": false, \"live_query_disabled\": false, \"query_reports_disabled\": false}, \"webhook_settings\": {\"interval\": \"0s\", \"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\": \"\"}}','2020-01-01 01:01:01','2020-01-01 01:01:01'); +INSERT INTO `app_config_json` VALUES (1,'{\"mdm\": {\"macos_setup\": {\"bootstrap_package\": null, \"macos_setup_assistant\": null, \"enable_end_user_authentication\": false}, \"macos_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}, \"windows_settings\": {\"custom_settings\": null}, \"apple_bm_default_team\": \"\", \"apple_bm_terms_expired\": false, \"enable_disk_encryption\": false, \"enabled_and_configured\": false, \"end_user_authentication\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"issuer_uri\": \"\", \"metadata_url\": \"\"}, \"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}, \"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, \"scripts_disabled\": false, \"deferred_save_host\": false, \"live_query_disabled\": false, \"query_reports_disabled\": false}, \"webhook_settings\": {\"interval\": \"0s\", \"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\": \"\"}}','2020-01-01 01:01:01','2020-01-01 01:01:01'); /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `carve_blocks` ( diff --git a/server/fleet/app.go b/server/fleet/app.go index a3f2cc122d..edb106876c 100644 --- a/server/fleet/app.go +++ b/server/fleet/app.go @@ -840,6 +840,7 @@ type ServerSettings struct { DebugHostIDs []uint `json:"debug_host_ids,omitempty"` DeferredSaveHost bool `json:"deferred_save_host"` QueryReportsDisabled bool `json:"query_reports_disabled"` + ScriptsDisabled bool `json:"scripts_disabled"` } // HostExpirySettings contains settings pertaining to automatic host expiry. diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go index 3575977411..5eab467a97 100644 --- a/server/service/integration_enterprise_test.go +++ b/server/service/integration_enterprise_test.go @@ -4343,8 +4343,30 @@ func (s *integrationEnterpriseTestSuite) TestRunHostScript() { require.False(t, scriptResultResp.HostTimeout) require.Contains(t, scriptResultResp.Message, fleet.RunScriptAlreadyRunningErrMsg) - // verify that orbit would get the notification that it has a script to run + // Disable scripts and verify that there are no Orbit notifs + acr := appConfigResponse{} + s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{ + "server_settings": { + "scripts_disabled": true + } + }`), http.StatusOK, &acr) + require.True(t, acr.AppConfig.ServerSettings.ScriptsDisabled) + var orbitResp orbitGetConfigResponse + s.DoJSON("POST", "/api/fleet/orbit/config", + json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *host.OrbitNodeKey)), + http.StatusOK, &orbitResp) + require.Empty(t, orbitResp.Notifications.PendingScriptExecutionIDs) + + acr = appConfigResponse{} + s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{ + "server_settings": { + "scripts_disabled": false + } + }`), http.StatusOK, &acr) + require.False(t, acr.AppConfig.ServerSettings.ScriptsDisabled) + + // verify that orbit would get the notification that it has a script to run s.DoJSON("POST", "/api/fleet/orbit/config", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *host.OrbitNodeKey)), http.StatusOK, &orbitResp) diff --git a/server/service/orbit.go b/server/service/orbit.go index a74472de46..9dff69620e 100644 --- a/server/service/orbit.go +++ b/server/service/orbit.go @@ -229,16 +229,18 @@ func (svc *Service) GetOrbitConfig(ctx context.Context) (fleet.OrbitConfig, erro } // load the pending script executions for that host - pending, err := svc.ds.ListPendingHostScriptExecutions(ctx, host.ID, pendingScriptMaxAge) - if err != nil { - return fleet.OrbitConfig{}, err - } - if len(pending) > 0 { - execIDs := make([]string, 0, len(pending)) - for _, p := range pending { - execIDs = append(execIDs, p.ExecutionID) + if !appConfig.ServerSettings.ScriptsDisabled { + pending, err := svc.ds.ListPendingHostScriptExecutions(ctx, host.ID, pendingScriptMaxAge) + if err != nil { + return fleet.OrbitConfig{}, err + } + if len(pending) > 0 { + execIDs := make([]string, 0, len(pending)) + for _, p := range pending { + execIDs = append(execIDs, p.ExecutionID) + } + notifs.PendingScriptExecutionIDs = execIDs } - notifs.PendingScriptExecutionIDs = execIDs } // team ID is not nil, get team specific flags and options diff --git a/tools/cloner-check/generated_files/appconfig.txt b/tools/cloner-check/generated_files/appconfig.txt index 4ee37214f6..bc42602a84 100644 --- a/tools/cloner-check/generated_files/appconfig.txt +++ b/tools/cloner-check/generated_files/appconfig.txt @@ -10,6 +10,7 @@ github.com/fleetdm/fleet/v4/server/fleet/ServerSettings EnableAnalytics bool github.com/fleetdm/fleet/v4/server/fleet/ServerSettings DebugHostIDs []uint github.com/fleetdm/fleet/v4/server/fleet/ServerSettings DeferredSaveHost bool github.com/fleetdm/fleet/v4/server/fleet/ServerSettings QueryReportsDisabled bool +github.com/fleetdm/fleet/v4/server/fleet/ServerSettings ScriptsDisabled bool github.com/fleetdm/fleet/v4/server/fleet/AppConfig SMTPSettings *fleet.SMTPSettings github.com/fleetdm/fleet/v4/server/fleet/SMTPSettings SMTPEnabled bool github.com/fleetdm/fleet/v4/server/fleet/SMTPSettings SMTPConfigured bool