diff --git a/src/core/server/saved_objects/migrations/integration_tests/type_registrations.test.ts b/src/core/server/saved_objects/migrations/integration_tests/type_registrations.test.ts index 844f57c8bf5c5..7f8d10b50edf5 100644 --- a/src/core/server/saved_objects/migrations/integration_tests/type_registrations.test.ts +++ b/src/core/server/saved_objects/migrations/integration_tests/type_registrations.test.ts @@ -75,6 +75,7 @@ const previouslyRegisteredTypes = [ 'ml-telemetry', 'monitoring-telemetry', 'osquery-pack', + 'osquery-pack-asset', 'osquery-saved-query', 'osquery-usage-metric', 'osquery-manager-usage-metric', diff --git a/x-pack/plugins/fleet/.storybook/context/fixtures/integration.nginx.ts b/x-pack/plugins/fleet/.storybook/context/fixtures/integration.nginx.ts index 0b4f30a137192..3a2bdc1c00faf 100644 --- a/x-pack/plugins/fleet/.storybook/context/fixtures/integration.nginx.ts +++ b/x-pack/plugins/fleet/.storybook/context/fixtures/integration.nginx.ts @@ -254,6 +254,7 @@ export const item: GetInfoResponse['item'] = { security_rule: [], csp_rule_template: [], tag: [], + osquery_pack_asset: [], }, elasticsearch: { ingest_pipeline: [ diff --git a/x-pack/plugins/fleet/.storybook/context/fixtures/integration.okta.ts b/x-pack/plugins/fleet/.storybook/context/fixtures/integration.okta.ts index 5c08120084cb9..7bba58dcaac7b 100644 --- a/x-pack/plugins/fleet/.storybook/context/fixtures/integration.okta.ts +++ b/x-pack/plugins/fleet/.storybook/context/fixtures/integration.okta.ts @@ -104,6 +104,7 @@ export const item: GetInfoResponse['item'] = { index_pattern: [], lens: [], ml_module: [], + osquery_pack_asset: [], security_rule: [], csp_rule_template: [], tag: [], diff --git a/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts b/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts index ee47c3faa305a..edffbdabc6c4e 100644 --- a/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts +++ b/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts @@ -35,6 +35,7 @@ describe('Fleet - packageToPackagePolicy', () => { ml_module: [], security_rule: [], tag: [], + osquery_pack_asset: [], }, elasticsearch: { ingest_pipeline: [], diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index 93be8684698ca..060606251a6a5 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -75,6 +75,7 @@ export enum KibanaAssetType { cloudSecurityPostureRuleTemplate = 'csp_rule_template', mlModule = 'ml_module', tag = 'tag', + osqueryPackAsset = 'osquery_pack_asset', } /* @@ -91,6 +92,7 @@ export enum KibanaSavedObjectType { securityRule = 'security-rule', cloudSecurityPostureRuleTemplate = 'csp-rule-template', tag = 'tag', + osqueryPackAsset = 'osquery-pack-asset', } export enum ElasticsearchAssetType { diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/assets_facet_group.stories.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/assets_facet_group.stories.tsx index f460005722b41..713d026726926 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/assets_facet_group.stories.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/assets_facet_group.stories.tsx @@ -38,6 +38,7 @@ export const AssetsFacetGroup = ({ width }: Args) => { security_rule: [], ml_module: [], tag: [], + osquery_pack_asset: [], }, elasticsearch: { component_template: [], diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx index 69abb3f451dd9..3af6002e014c1 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; import type { ServiceName } from '../../types'; import { ElasticsearchAssetType, KibanaAssetType } from '../../types'; -// only allow Kibana assets for the kibana key, ES asssets for elasticsearch, etc +// only allow Kibana assets for the kibana key, ES assets for elasticsearch, etc type ServiceNameToAssetTypes = Record, KibanaAssetType[]> & Record, ElasticsearchAssetType[]>; @@ -62,6 +62,9 @@ export const AssetTitleMap: Record = { security_rule: i18n.translate('xpack.fleet.epm.assetTitles.securityRules', { defaultMessage: 'Security rules', }), + osquery_pack_asset: i18n.translate('xpack.fleet.epm.assetTitles.osqueryPackAsset', { + defaultMessage: 'Osquery packs', + }), ml_module: i18n.translate('xpack.fleet.epm.assetTitles.mlModules', { defaultMessage: 'ML modules', }), @@ -98,6 +101,7 @@ export const AssetIcons: Record = { csp_rule_template: 'securityApp', // TODO ICON ml_module: 'mlApp', tag: 'tagApp', + osquery_pack_asset: 'osqueryApp', }; export const ServiceIcons: Record = { diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts index 491e4e27825c4..b8ef796f8817c 100644 --- a/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts @@ -55,6 +55,7 @@ const KibanaSavedObjectTypeMapping: Record ArchiveAsset[]> = { @@ -252,7 +253,7 @@ export async function installKibanaSavedObjects({ /* A reference error here means that a saved object reference in the references array cannot be found. This is an error in the package its-self but not a fatal - one. For example a dashboard may still refer to the legacy `metricbeat-*` index + one. For example a dashboard may still refer to the legacy `metricbeat-*` index pattern. We ignore reference errors here so that legacy version of a package can still be installed, but if a warning is logged it should be reported to the integrations team. */ diff --git a/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts index ccfad3631eab1..fbf0dbe22c51a 100644 --- a/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts @@ -68,6 +68,7 @@ describe('storedPackagePoliciesToAgentPermissions()', () => { security_rule: [], ml_module: [], tag: [], + osquery_pack_asset: [], }, elasticsearch: { component_template: [], @@ -181,6 +182,7 @@ describe('storedPackagePoliciesToAgentPermissions()', () => { security_rule: [], ml_module: [], tag: [], + osquery_pack_asset: [], }, elasticsearch: { component_template: [], @@ -274,6 +276,7 @@ describe('storedPackagePoliciesToAgentPermissions()', () => { security_rule: [], ml_module: [], tag: [], + osquery_pack_asset: [], }, elasticsearch: { component_template: [], @@ -399,6 +402,7 @@ describe('storedPackagePoliciesToAgentPermissions()', () => { security_rule: [], ml_module: [], tag: [], + osquery_pack_asset: [], }, elasticsearch: { component_template: [], diff --git a/x-pack/plugins/osquery/common/types.ts b/x-pack/plugins/osquery/common/types.ts index f543057d773fe..2d3fc6e972c7c 100644 --- a/x-pack/plugins/osquery/common/types.ts +++ b/x-pack/plugins/osquery/common/types.ts @@ -9,6 +9,7 @@ import { PackagePolicy, PackagePolicyInput, PackagePolicyInputStream } from '../ export const savedQuerySavedObjectType = 'osquery-saved-query'; export const packSavedObjectType = 'osquery-pack'; +export const packAssetSavedObjectType = 'osquery-pack-asset'; export const usageMetricSavedObjectType = 'osquery-manager-usage-metric'; export type SavedObjectType = | 'osquery-saved-query' @@ -68,4 +69,5 @@ export interface OsqueryManagerPackagePolicyInput extends Omit { inputs: OsqueryManagerPackagePolicyInput[]; + read_only?: boolean; } diff --git a/x-pack/plugins/osquery/public/assets/constants.ts b/x-pack/plugins/osquery/public/assets/constants.ts new file mode 100644 index 0000000000000..00b9067d83089 --- /dev/null +++ b/x-pack/plugins/osquery/public/assets/constants.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const INTEGRATION_ASSETS_STATUS_ID = 'integrationAssetsStatus'; diff --git a/x-pack/plugins/osquery/public/assets/use_assets_status.ts b/x-pack/plugins/osquery/public/assets/use_assets_status.ts new file mode 100644 index 0000000000000..41307952b222c --- /dev/null +++ b/x-pack/plugins/osquery/public/assets/use_assets_status.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObject } from 'kibana/public'; +import { useQuery } from 'react-query'; +import { useKibana } from '../common/lib/kibana'; +import { INTEGRATION_ASSETS_STATUS_ID } from './constants'; + +export const useAssetsStatus = () => { + const { http } = useKibana().services; + + return useQuery<{ install: SavedObject[]; update: SavedObject[]; upToDate: SavedObject[] }>( + [INTEGRATION_ASSETS_STATUS_ID], + () => http.get('/internal/osquery/assets'), + { + keepPreviousData: true, + } + ); +}; diff --git a/x-pack/plugins/osquery/public/assets/use_import_assets.ts b/x-pack/plugins/osquery/public/assets/use_import_assets.ts new file mode 100644 index 0000000000000..f63f3e7096f03 --- /dev/null +++ b/x-pack/plugins/osquery/public/assets/use_import_assets.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useMutation, useQueryClient } from 'react-query'; +import { useKibana } from '../common/lib/kibana'; +import { useErrorToast } from '../common/hooks/use_error_toast'; +import { PACKS_ID } from '../packs/constants'; +import { INTEGRATION_ASSETS_STATUS_ID } from './constants'; + +interface UseImportAssetsProps { + successToastText: string; +} + +export const useImportAssets = ({ successToastText }: UseImportAssetsProps) => { + const queryClient = useQueryClient(); + const { + http, + notifications: { toasts }, + } = useKibana().services; + const setErrorToast = useErrorToast(); + + return useMutation( + () => + // eslint-disable-next-line @typescript-eslint/no-explicit-any + http.post('/internal/osquery/assets/update'), + { + onSuccess: () => { + setErrorToast(); + queryClient.invalidateQueries(PACKS_ID); + queryClient.invalidateQueries(INTEGRATION_ASSETS_STATUS_ID); + toasts.addSuccess(successToastText); + }, + onError: (error) => { + setErrorToast(error); + }, + } + ); +}; diff --git a/x-pack/plugins/osquery/public/packs/active_state_switch.tsx b/x-pack/plugins/osquery/public/packs/active_state_switch.tsx index da1581f5f7bfe..1dbb145430a3e 100644 --- a/x-pack/plugins/osquery/public/packs/active_state_switch.tsx +++ b/x-pack/plugins/osquery/public/packs/active_state_switch.tsx @@ -17,6 +17,7 @@ import { useAgentPolicies } from '../agent_policies/use_agent_policies'; import { ConfirmDeployAgentPolicyModal } from './form/confirmation_modal'; import { useErrorToast } from '../common/hooks/use_error_toast'; import { useUpdatePack } from './use_update_pack'; +import { PACKS_ID } from './constants'; const StyledEuiLoadingSpinner = styled(EuiLoadingSpinner)` margin-right: ${({ theme }) => theme.eui.paddingSizes.s}; @@ -55,7 +56,7 @@ const ActiveStateSwitchComponent: React.FC = ({ item }) options: { // @ts-expect-error update types onSuccess: (response) => { - queryClient.invalidateQueries('packList'); + queryClient.invalidateQueries(PACKS_ID); setErrorToast(); toasts.addSuccess( response.attributes.enabled diff --git a/x-pack/plugins/osquery/public/packs/add_pack_button.tsx b/x-pack/plugins/osquery/public/packs/add_pack_button.tsx new file mode 100644 index 0000000000000..1473cee6e7aa2 --- /dev/null +++ b/x-pack/plugins/osquery/public/packs/add_pack_button.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiButton, EuiButtonProps } from '@elastic/eui'; +import { useKibana, useRouterNavigate } from '../common/lib/kibana'; + +interface AddPackButtonComponentProps { + fill?: EuiButtonProps['fill']; +} + +const AddPackButtonComponent: React.FC = ({ fill = true }) => { + const permissions = useKibana().services.application.capabilities.osquery; + const newQueryLinkProps = useRouterNavigate('packs/add'); + + return ( + + + + ); +}; + +export const AddPackButton = React.memo(AddPackButtonComponent); diff --git a/x-pack/plugins/osquery/public/packs/form/index.tsx b/x-pack/plugins/osquery/public/packs/form/index.tsx index b68336e6705be..f327239560345 100644 --- a/x-pack/plugins/osquery/public/packs/form/index.tsx +++ b/x-pack/plugins/osquery/public/packs/form/index.tsx @@ -51,6 +51,7 @@ interface PackFormProps { } const PackFormComponent: React.FC = ({ defaultValue, editMode = false }) => { + const isReadOnly = !!defaultValue?.read_only; const [showConfirmationModal, setShowConfirmationModal] = useState(false); const handleHideConfirmationModal = useCallback(() => setShowConfirmationModal(false), []); @@ -183,18 +184,20 @@ const PackFormComponent: React.FC = ({ defaultValue, editMode = f setShowConfirmationModal(false); }, [submit]); + const euiFieldProps = useMemo(() => ({ isDisabled: isReadOnly }), [isReadOnly]); + return ( <>
- + - + @@ -213,6 +216,7 @@ const PackFormComponent: React.FC = ({ defaultValue, editMode = f path="queries" component={QueriesField} handleNameChange={handleNameChange} + euiFieldProps={euiFieldProps} /> diff --git a/x-pack/plugins/osquery/public/packs/form/queries_field.tsx b/x-pack/plugins/osquery/public/packs/form/queries_field.tsx index 2ae946a0f2e8f..8e049e3fc5fc1 100644 --- a/x-pack/plugins/osquery/public/packs/form/queries_field.tsx +++ b/x-pack/plugins/osquery/public/packs/form/queries_field.tsx @@ -6,7 +6,7 @@ */ import { isEmpty, findIndex, forEach, pullAt, pullAllBy, pickBy } from 'lodash'; -import { EuiFlexGroup, EuiFlexItem, EuiButton, EuiSpacer } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiButton, EuiSpacer, EuiComboBoxProps } from '@elastic/eui'; import { produce } from 'immer'; import React, { useCallback, useMemo, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -21,9 +21,15 @@ import { getSupportedPlatforms } from '../queries/platforms/helpers'; interface QueriesFieldProps { handleNameChange: (name: string) => void; field: FieldHook>>; + euiFieldProps: EuiComboBoxProps<{}>; } -const QueriesFieldComponent: React.FC = ({ field, handleNameChange }) => { +const QueriesFieldComponent: React.FC = ({ + field, + handleNameChange, + euiFieldProps, +}) => { + const isReadOnly = !!euiFieldProps?.isDisabled; const [showAddQueryFlyout, setShowAddQueryFlyout] = useState(false); const [showEditQueryFlyout, setShowEditQueryFlyout] = useState(-1); const [tableSelectedItems, setTableSelectedItems] = useState< @@ -174,34 +180,39 @@ const QueriesFieldComponent: React.FC = ({ field, handleNameC return ( <> - - - {!tableSelectedItems.length ? ( - - - - ) : ( - - - - )} - - - + {!isReadOnly && ( + <> + + + {!tableSelectedItems.length ? ( + + + + ) : ( + + + + )} + + + + + )} {field.value?.length ? ( = ({ field, handleNameC /> ) : null} - {} + {!isReadOnly && } {showAddQueryFlyout && ( void; onEditClick?: (item: OsqueryManagerPackagePolicyInputStream) => void; selectedItems?: OsqueryManagerPackagePolicyInputStream[]; @@ -23,6 +24,7 @@ export interface PackQueriesTableProps { const PackQueriesTableComponent: React.FC = ({ data, + isReadOnly, onDeleteClick, onEditClick, selectedItems, @@ -127,22 +129,27 @@ const PackQueriesTableComponent: React.FC = ({ }), render: renderVersionColumn, }, - { - name: i18n.translate('xpack.osquery.pack.queriesTable.actionsColumnTitle', { - defaultMessage: 'Actions', - }), - width: '120px', - actions: [ - { - render: renderEditAction, - }, - { - render: renderDeleteAction, - }, - ], - }, + ...(!isReadOnly + ? [ + { + name: i18n.translate('xpack.osquery.pack.queriesTable.actionsColumnTitle', { + defaultMessage: 'Actions', + }), + width: '120px', + actions: [ + { + render: renderEditAction, + }, + { + render: renderDeleteAction, + }, + ], + }, + ] + : []), ], [ + isReadOnly, renderDeleteAction, renderEditAction, renderPlatformColumn, @@ -177,8 +184,7 @@ const PackQueriesTableComponent: React.FC = ({ itemId={itemId} columns={columns} sorting={sorting} - selection={selection} - isSelectable + {...(!isReadOnly ? { selection, isSelectable: true } : {})} /> ); }; diff --git a/x-pack/plugins/osquery/public/packs/types.ts b/x-pack/plugins/osquery/public/packs/types.ts index 95e488b8cc698..07f4149cab1ef 100644 --- a/x-pack/plugins/osquery/public/packs/types.ts +++ b/x-pack/plugins/osquery/public/packs/types.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { SavedObject } from 'kibana/server'; +import { SavedObject } from 'kibana/public'; export interface IQueryPayload { attributes?: { @@ -16,7 +16,13 @@ export interface IQueryPayload { export type PackSavedObject = SavedObject<{ name: string; description: string | undefined; - queries: Array>; + queries: Array<{ + id: string; + name: string; + interval: number; + ecs_mapping: Record; + }>; + version?: number; enabled: boolean | undefined; created_at: string; created_by: string | undefined; diff --git a/x-pack/plugins/osquery/public/routes/packs/edit/index.tsx b/x-pack/plugins/osquery/public/routes/packs/edit/index.tsx index 2409a9524a8c2..341312a45ae8a 100644 --- a/x-pack/plugins/osquery/public/routes/packs/edit/index.tsx +++ b/x-pack/plugins/osquery/public/routes/packs/edit/index.tsx @@ -112,6 +112,7 @@ const EditPackPageComponent = () => { } onCancel={handleCloseDeleteConfirmationModal} onConfirm={handleDeleteConfirmClick} + confirmButtonDisabled={deletePackMutation.isLoading} cancelButtonText={ { + const actions = useMemo( + () => ( + + + + + + + + + ), + [] + ); + + return ( + } + color="transparent" + title={

{PRE_BUILT_TITLE}

} + body={

{PRE_BUILT_MSG}

} + actions={actions} + /> + ); +}; + +export const PacksTableEmptyState = React.memo(PacksTableEmptyStateComponent); diff --git a/x-pack/plugins/osquery/public/routes/packs/list/index.tsx b/x-pack/plugins/osquery/public/routes/packs/list/index.tsx index c4b9f94b32287..1c4cf2d49186f 100644 --- a/x-pack/plugins/osquery/public/routes/packs/list/index.tsx +++ b/x-pack/plugins/osquery/public/routes/packs/list/index.tsx @@ -5,17 +5,25 @@ * 2.0. */ -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiText, EuiLoadingContent } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import React, { useMemo } from 'react'; -import { useKibana, useRouterNavigate } from '../../../common/lib/kibana'; import { WithHeaderLayout } from '../../../components/layouts'; import { PacksTable } from '../../../packs/packs_table'; +import { AddPackButton } from '../../../packs/add_pack_button'; +import { LoadIntegrationAssetsButton } from './load_integration_assets'; +import { PacksTableEmptyState } from './empty_state'; +import { useAssetsStatus } from '../../../assets/use_assets_status'; +import { usePacks } from '../../../packs/use_packs'; const PacksPageComponent = () => { - const permissions = useKibana().services.application.capabilities.osquery; - const newQueryLinkProps = useRouterNavigate('packs/add'); + const { data: assetsData, isLoading: isLoadingAssetsStatus } = useAssetsStatus(); + const { data: packsData, isLoading: isLoadingPacks } = usePacks({}); + const showEmptyState = useMemo( + () => !packsData?.total && assetsData?.install?.length, + [assetsData?.install?.length, packsData?.total] + ); const LeftColumn = useMemo( () => ( @@ -44,24 +52,33 @@ const PacksPageComponent = () => { const RightColumn = useMemo( () => ( - - - + + + + + + + + ), - [newQueryLinkProps, permissions.writePacks] + [showEmptyState] ); + const Content = useMemo(() => { + if (isLoadingAssetsStatus || isLoadingPacks) { + return ; + } + + if (showEmptyState) { + return ; + } + + return ; + }, [isLoadingAssetsStatus, isLoadingPacks, showEmptyState]); + return ( - + {Content} ); }; diff --git a/x-pack/plugins/osquery/public/routes/packs/list/load_integration_assets.tsx b/x-pack/plugins/osquery/public/routes/packs/list/load_integration_assets.tsx new file mode 100644 index 0000000000000..a4d7374d21697 --- /dev/null +++ b/x-pack/plugins/osquery/public/routes/packs/list/load_integration_assets.tsx @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback } from 'react'; +import { EuiButton, EuiButtonProps } from '@elastic/eui'; +import { useImportAssets } from '../../../assets/use_import_assets'; +import { useAssetsStatus } from '../../../assets/use_assets_status'; +import { + LOAD_PREBUILT_PACKS_BUTTON, + UPDATE_PREBUILT_PACKS_BUTTON, + LOAD_PREBUILT_PACKS_SUCCESS_TEXT, + UPDATE_PREBUILT_PACKS_SUCCESS_TEXT, +} from './translations'; + +interface LoadIntegrationAssetsButtonProps { + fill?: EuiButtonProps['fill']; +} + +const LoadIntegrationAssetsButtonComponent: React.FC = ({ + fill, +}) => { + const { data } = useAssetsStatus(); + const { isLoading, mutateAsync } = useImportAssets({ + successToastText: data?.upToDate?.length + ? UPDATE_PREBUILT_PACKS_SUCCESS_TEXT + : LOAD_PREBUILT_PACKS_SUCCESS_TEXT, + }); + + const handleClick = useCallback(() => mutateAsync(), [mutateAsync]); + + if (data?.install.length || data?.update.length) { + return ( + + {data?.upToDate?.length ? UPDATE_PREBUILT_PACKS_BUTTON : LOAD_PREBUILT_PACKS_BUTTON} + + ); + } + + return null; +}; + +export const LoadIntegrationAssetsButton = React.memo(LoadIntegrationAssetsButtonComponent); diff --git a/x-pack/plugins/osquery/public/routes/packs/list/translations.ts b/x-pack/plugins/osquery/public/routes/packs/list/translations.ts new file mode 100644 index 0000000000000..6bbee2a2eb59d --- /dev/null +++ b/x-pack/plugins/osquery/public/routes/packs/list/translations.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const PRE_BUILT_TITLE = i18n.translate( + 'xpack.osquery.packList.prePackagedPacks.emptyPromptTitle', + { + defaultMessage: 'Load Elastic prebuilt packs', + } +); + +export const LOAD_PREBUILT_PACKS_BUTTON = i18n.translate( + 'xpack.osquery.packList.prePackagedPacks.loadButtonLabel', + { + defaultMessage: 'Load Elastic prebuilt packs', + } +); + +export const UPDATE_PREBUILT_PACKS_BUTTON = i18n.translate( + 'xpack.osquery.packList.prePackagedPacks.updateButtonLabel', + { + defaultMessage: 'Update Elastic prebuilt packs', + } +); + +export const LOAD_PREBUILT_PACKS_SUCCESS_TEXT = i18n.translate( + 'xpack.osquery.packList.integrationAssets.loadSuccessToastMessageText', + { + defaultMessage: 'Successfully loaded prebuilt packs', + } +); + +export const UPDATE_PREBUILT_PACKS_SUCCESS_TEXT = i18n.translate( + 'xpack.osquery.packList.integrationAssets.updateSuccessToastMessageText', + { + defaultMessage: 'Successfully updated prebuilt packs', + } +); + +export const PRE_BUILT_MSG = i18n.translate( + 'xpack.osquery.packList.prePackagedPacks.emptyPromptTitle.emptyPromptMessage', + { + defaultMessage: + 'A pack is a set of queries that you can schedule. Load prebuilt packs or create your own.', + } +); diff --git a/x-pack/plugins/osquery/server/common/types.ts b/x-pack/plugins/osquery/server/common/types.ts new file mode 100644 index 0000000000000..3021cadb6cae3 --- /dev/null +++ b/x-pack/plugins/osquery/server/common/types.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObject } from 'kibana/server'; + +export interface IQueryPayload { + attributes?: { + name: string; + id: string; + }; +} + +export interface PackSavedObjectAttributes { + name: string; + description: string | undefined; + queries: Array<{ + id: string; + name: string; + interval: number; + ecs_mapping: Record; + }>; + version?: number; + enabled: boolean | undefined; + created_at: string; + created_by: string | undefined; + updated_at: string; + updated_by: string | undefined; +} + +export type PackSavedObject = SavedObject; diff --git a/x-pack/plugins/osquery/server/lib/saved_query/saved_object_mappings.ts b/x-pack/plugins/osquery/server/lib/saved_query/saved_object_mappings.ts index bed2ba2efe688..274ab89355b47 100644 --- a/x-pack/plugins/osquery/server/lib/saved_query/saved_object_mappings.ts +++ b/x-pack/plugins/osquery/server/lib/saved_query/saved_object_mappings.ts @@ -7,7 +7,11 @@ import { produce } from 'immer'; import { SavedObjectsType } from '../../../../../../src/core/server'; -import { savedQuerySavedObjectType, packSavedObjectType } from '../../../common/types'; +import { + savedQuerySavedObjectType, + packSavedObjectType, + packAssetSavedObjectType, +} from '../../../common/types'; export const savedQuerySavedObjectMappings: SavedObjectsType['mappings'] = { properties: { @@ -87,6 +91,9 @@ export const packSavedObjectMappings: SavedObjectsType['mappings'] = { enabled: { type: 'boolean', }, + version: { + type: 'long', + }, queries: { properties: { id: { @@ -137,3 +144,52 @@ export const packType: SavedObjectsType = { }), }, }; + +export const packAssetSavedObjectMappings: SavedObjectsType['mappings'] = { + dynamic: false, + properties: { + description: { + type: 'text', + }, + name: { + type: 'text', + }, + version: { + type: 'long', + }, + queries: { + properties: { + id: { + type: 'keyword', + }, + query: { + type: 'text', + }, + interval: { + type: 'text', + }, + platform: { + type: 'keyword', + }, + version: { + type: 'keyword', + }, + ecs_mapping: { + type: 'object', + enabled: false, + }, + }, + }, + }, +}; + +export const packAssetType: SavedObjectsType = { + name: packAssetSavedObjectType, + hidden: false, + management: { + importableAndExportable: true, + visibleInManagement: false, + }, + namespaceType: 'agnostic', + mappings: packAssetSavedObjectMappings, +}; diff --git a/x-pack/plugins/osquery/server/routes/asset/get_assets_status_route.ts b/x-pack/plugins/osquery/server/routes/asset/get_assets_status_route.ts new file mode 100644 index 0000000000000..539f7083f0f2e --- /dev/null +++ b/x-pack/plugins/osquery/server/routes/asset/get_assets_status_route.ts @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { filter } from 'lodash/fp'; +import { schema } from '@kbn/config-schema'; +import { asyncForEach } from '@kbn/std'; +import { IRouter } from 'kibana/server'; + +import { packAssetSavedObjectType, packSavedObjectType } from '../../../common/types'; +import { PLUGIN_ID, OSQUERY_INTEGRATION_NAME } from '../../../common'; +import { OsqueryAppContext } from '../../lib/osquery_app_context_services'; +import { KibanaAssetReference } from '../../../../fleet/common'; + +export const getAssetsStatusRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => { + router.get( + { + path: '/internal/osquery/assets', + validate: { + params: schema.object({}, { unknowns: 'allow' }), + }, + options: { tags: [`access:${PLUGIN_ID}-writePacks`] }, + }, + async (context, request, response) => { + const savedObjectsClient = context.core.savedObjects.client; + + let installation; + + try { + installation = await osqueryContext.service + .getPackageService() + ?.asInternalUser?.getInstallation(OSQUERY_INTEGRATION_NAME); + } catch (err) { + return response.notFound(); + } + + if (installation) { + const installationPackAssets = filter( + ['type', packAssetSavedObjectType], + installation.installed_kibana + ); + + const install: KibanaAssetReference[] = []; + const update: KibanaAssetReference[] = []; + const upToDate: KibanaAssetReference[] = []; + + await asyncForEach(installationPackAssets, async (installationPackAsset) => { + const isInstalled = await savedObjectsClient.find<{ version: number }>({ + type: packSavedObjectType, + hasReference: { + type: installationPackAsset.type, + id: installationPackAsset.id, + }, + }); + + if (!isInstalled.total) { + install.push(installationPackAsset); + } + + if (isInstalled.total) { + const packAssetSavedObject = await savedObjectsClient.get<{ version: number }>( + installationPackAsset.type, + installationPackAsset.id + ); + + if (packAssetSavedObject) { + if ( + !packAssetSavedObject.attributes.version || + !isInstalled.saved_objects[0].attributes.version + ) { + install.push(installationPackAsset); + } else if ( + packAssetSavedObject.attributes.version > + isInstalled.saved_objects[0].attributes.version + ) { + update.push(installationPackAsset); + } else { + upToDate.push(installationPackAsset); + } + } + } + }); + + return response.ok({ + body: { + install, + update, + upToDate, + }, + }); + } + + return response.ok(); + } + ); +}; diff --git a/x-pack/plugins/osquery/server/routes/asset/index.ts b/x-pack/plugins/osquery/server/routes/asset/index.ts new file mode 100644 index 0000000000000..d232d499f9bd0 --- /dev/null +++ b/x-pack/plugins/osquery/server/routes/asset/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IRouter } from '../../../../../../src/core/server'; +import { getAssetsStatusRoute } from './get_assets_status_route'; +import { updateAssetsRoute } from './update_assets_route'; +import { OsqueryAppContext } from '../../lib/osquery_app_context_services'; + +export const initAssetRoutes = (router: IRouter, context: OsqueryAppContext) => { + getAssetsStatusRoute(router, context); + updateAssetsRoute(router, context); +}; diff --git a/x-pack/plugins/osquery/server/routes/asset/update_assets_route.ts b/x-pack/plugins/osquery/server/routes/asset/update_assets_route.ts new file mode 100644 index 0000000000000..8cafdc11bd124 --- /dev/null +++ b/x-pack/plugins/osquery/server/routes/asset/update_assets_route.ts @@ -0,0 +1,200 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment-timezone'; +import { filter, omit } from 'lodash'; +import { schema } from '@kbn/config-schema'; +import { asyncForEach } from '@kbn/std'; +import deepmerge from 'deepmerge'; + +import { packAssetSavedObjectType, packSavedObjectType } from '../../../common/types'; +import { combineMerge } from './utils'; +import { PLUGIN_ID, OSQUERY_INTEGRATION_NAME } from '../../../common'; +import { IRouter } from '../../../../../../src/core/server'; +import { OsqueryAppContext } from '../../lib/osquery_app_context_services'; +import { convertSOQueriesToPack, convertPackQueriesToSO } from '../pack/utils'; +import { KibanaAssetReference } from '../../../../fleet/common'; +import { PackSavedObjectAttributes } from '../../common/types'; + +export const updateAssetsRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => { + router.post( + { + path: '/internal/osquery/assets/update', + validate: { + params: schema.object({}, { unknowns: 'allow' }), + }, + options: { tags: [`access:${PLUGIN_ID}-all`] }, + }, + async (context, request, response) => { + const savedObjectsClient = context.core.savedObjects.client; + const currentUser = await osqueryContext.security.authc.getCurrentUser(request)?.username; + + let installation; + + try { + installation = await osqueryContext.service + .getPackageService() + ?.asInternalUser?.getInstallation(OSQUERY_INTEGRATION_NAME); + } catch (err) { + return response.notFound(); + } + + if (installation) { + const installationPackAssets = filter(installation.installed_kibana, [ + 'type', + packAssetSavedObjectType, + ]); + + const install: KibanaAssetReference[] = []; + const update: KibanaAssetReference[] = []; + const upToDate: KibanaAssetReference[] = []; + + await asyncForEach(installationPackAssets, async (installationPackAsset) => { + const isInstalled = await savedObjectsClient.find<{ version: number }>({ + type: packSavedObjectType, + hasReference: { + type: installationPackAsset.type, + id: installationPackAsset.id, + }, + }); + + if (!isInstalled.total) { + install.push(installationPackAsset); + } + + if (isInstalled.total) { + const packAssetSavedObject = await savedObjectsClient.get<{ version: number }>( + installationPackAsset.type, + installationPackAsset.id + ); + + if (packAssetSavedObject) { + if ( + !packAssetSavedObject.attributes.version || + !isInstalled.saved_objects[0].attributes.version + ) { + install.push(installationPackAsset); + } else if ( + packAssetSavedObject.attributes.version > + isInstalled.saved_objects[0].attributes.version + ) { + update.push(installationPackAsset); + } else { + upToDate.push(installationPackAsset); + } + } + } + }); + + await Promise.all([ + ...install.map(async (installationPackAsset) => { + const packAssetSavedObject = await savedObjectsClient.get( + installationPackAsset.type, + installationPackAsset.id + ); + + const conflictingEntries = await savedObjectsClient.find({ + type: packSavedObjectType, + filter: `${packSavedObjectType}.attributes.name: "${packAssetSavedObject.attributes.name}"`, + }); + + const name = conflictingEntries.saved_objects.length + ? `${packAssetSavedObject.attributes.name}-elastic` + : packAssetSavedObject.attributes.name; + + await savedObjectsClient.create( + packSavedObjectType, + { + name, + description: packAssetSavedObject.attributes.description, + queries: packAssetSavedObject.attributes.queries, + enabled: false, + created_at: moment().toISOString(), + created_by: currentUser, + updated_at: moment().toISOString(), + updated_by: currentUser, + version: packAssetSavedObject.attributes.version ?? 1, + }, + { + references: [ + ...packAssetSavedObject.references, + { + type: packAssetSavedObject.type, + id: packAssetSavedObject.id, + name: packAssetSavedObject.attributes.name, + }, + ], + refresh: 'wait_for', + } + ); + }), + ...update.map(async (updatePackAsset) => { + const packAssetSavedObject = await savedObjectsClient.get( + updatePackAsset.type, + updatePackAsset.id + ); + + const packSavedObjectsResponse = + await savedObjectsClient.find({ + type: 'osquery-pack', + hasReference: { + type: updatePackAsset.type, + id: updatePackAsset.id, + }, + }); + + if (packSavedObjectsResponse.total) { + await savedObjectsClient.update( + packSavedObjectsResponse.saved_objects[0].type, + packSavedObjectsResponse.saved_objects[0].id, + deepmerge.all([ + omit(packSavedObjectsResponse.saved_objects[0].attributes, 'queries'), + omit(packAssetSavedObject.attributes, 'queries'), + { + updated_at: moment().toISOString(), + updated_by: currentUser, + queries: convertPackQueriesToSO( + deepmerge( + convertSOQueriesToPack( + packSavedObjectsResponse.saved_objects[0].attributes.queries + ), + convertSOQueriesToPack(packAssetSavedObject.attributes.queries), + { + arrayMerge: combineMerge, + } + ) + ), + }, + { + arrayMerge: combineMerge, + }, + ]), + { refresh: 'wait_for' } + ); + } + }), + ]); + + return response.ok({ + body: { + install, + update, + upToDate, + }, + }); + } + + return response.ok({ + body: { + install: 0, + update: 0, + upToDate: 0, + }, + }); + } + ); +}; diff --git a/x-pack/plugins/osquery/server/routes/asset/utils.ts b/x-pack/plugins/osquery/server/routes/asset/utils.ts new file mode 100644 index 0000000000000..4ad80c924920d --- /dev/null +++ b/x-pack/plugins/osquery/server/routes/asset/utils.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import deepmerge from 'deepmerge'; + +// https://www.npmjs.com/package/deepmerge#arraymerge-example-combine-arrays +// @ts-expect-error update types +export const combineMerge = (target, source, options) => { + const destination = target.slice(); + + // @ts-expect-error update types + source.forEach((item, index) => { + if (typeof destination[index] === 'undefined') { + destination[index] = options.cloneUnlessOtherwiseSpecified(item, options); + } else if (options.isMergeableObject(item)) { + destination[index] = deepmerge(target[index], item, options); + } else if (target.indexOf(item) === -1) { + destination.push(item); + } + }); + return destination; +}; diff --git a/x-pack/plugins/osquery/server/routes/index.ts b/x-pack/plugins/osquery/server/routes/index.ts index b32f0c5578207..5eb35f2a444a8 100644 --- a/x-pack/plugins/osquery/server/routes/index.ts +++ b/x-pack/plugins/osquery/server/routes/index.ts @@ -13,6 +13,7 @@ import { initStatusRoutes } from './status'; import { initFleetWrapperRoutes } from './fleet_wrapper'; import { initPackRoutes } from './pack'; import { initPrivilegesCheckRoutes } from './privileges_check'; +import { initAssetRoutes } from './asset'; export const defineRoutes = (router: IRouter, context: OsqueryAppContext) => { initActionRoutes(router, context); @@ -21,4 +22,5 @@ export const defineRoutes = (router: IRouter, context: OsqueryAppContext) => { initFleetWrapperRoutes(router, context); initPrivilegesCheckRoutes(router, context); initSavedQueryRoutes(router, context); + initAssetRoutes(router, context); }; diff --git a/x-pack/plugins/osquery/server/routes/pack/find_pack_route.ts b/x-pack/plugins/osquery/server/routes/pack/find_pack_route.ts index 12ca65143f587..4f09ad1dcffbe 100644 --- a/x-pack/plugins/osquery/server/routes/pack/find_pack_route.ts +++ b/x-pack/plugins/osquery/server/routes/pack/find_pack_route.ts @@ -13,6 +13,7 @@ import { IRouter } from '../../../../../../src/core/server'; import { packSavedObjectType } from '../../../common/types'; import { OsqueryAppContext } from '../../lib/osquery_app_context_services'; import { PLUGIN_ID } from '../../../common'; +import { PackSavedObjectAttributes } from '../../common/types'; // eslint-disable-next-line @typescript-eslint/no-unused-vars export const findPackRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => { @@ -35,12 +36,7 @@ export const findPackRoute = (router: IRouter, osqueryContext: OsqueryAppContext async (context, request, response) => { const savedObjectsClient = context.core.savedObjects.client; - const soClientResponse = await savedObjectsClient.find<{ - name: string; - description: string; - queries: Array<{ name: string; interval: number }>; - policy_ids: string[]; - }>({ + const soClientResponse = await savedObjectsClient.find({ type: packSavedObjectType, page: parseInt(request.query.pageIndex ?? '0', 10) + 1, perPage: request.query.pageSize ?? 20, diff --git a/x-pack/plugins/osquery/server/routes/pack/read_pack_route.ts b/x-pack/plugins/osquery/server/routes/pack/read_pack_route.ts index 066938603a2d6..a181b4c52a730 100644 --- a/x-pack/plugins/osquery/server/routes/pack/read_pack_route.ts +++ b/x-pack/plugins/osquery/server/routes/pack/read_pack_route.ts @@ -7,6 +7,7 @@ import { filter, map } from 'lodash'; import { schema } from '@kbn/config-schema'; +import { PackSavedObjectAttributes } from '../../common/types'; import { PLUGIN_ID } from '../../../common'; import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../../../fleet/common'; @@ -30,18 +31,14 @@ export const readPackRoute = (router: IRouter, osqueryContext: OsqueryAppContext async (context, request, response) => { const savedObjectsClient = context.core.savedObjects.client; - const { attributes, references, ...rest } = await savedObjectsClient.get<{ - name: string; - description: string; - queries: Array<{ - id: string; - name: string; - interval: number; - ecs_mapping: Record; - }>; - }>(packSavedObjectType, request.params.id); + const { attributes, references, ...rest } = + await savedObjectsClient.get( + packSavedObjectType, + request.params.id + ); const policyIds = map(filter(references, ['type', AGENT_POLICY_SAVED_OBJECT_TYPE]), 'id'); + const osqueryPackAssetReference = !!filter(references, ['type', 'osquery-pack-asset']); return response.ok({ body: { @@ -49,6 +46,7 @@ export const readPackRoute = (router: IRouter, osqueryContext: OsqueryAppContext ...attributes, queries: convertSOQueriesToPack(attributes.queries), policy_ids: policyIds, + read_only: attributes.version !== undefined && osqueryPackAssetReference, }, }); } diff --git a/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts b/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts index b2cff1b769d1c..f04a0a37d0c5d 100644 --- a/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts +++ b/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts @@ -22,6 +22,7 @@ import { OsqueryAppContext } from '../../lib/osquery_app_context_services'; import { PLUGIN_ID } from '../../../common'; import { convertSOQueriesToPack, convertPackQueriesToSO } from './utils'; import { getInternalSavedObjectsClient } from '../../usage/collector'; +import { PackSavedObjectAttributes } from '../../common/types'; export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => { router.put( @@ -87,14 +88,17 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte ); if (name) { - const conflictingEntries = await savedObjectsClient.find({ + const conflictingEntries = await savedObjectsClient.find({ type: packSavedObjectType, filter: `${packSavedObjectType}.attributes.name: "${name}"`, }); if ( - filter(conflictingEntries.saved_objects, (packSO) => packSO.id !== currentPackSO.id) - .length + filter( + conflictingEntries.saved_objects, + (packSO) => + packSO.id !== currentPackSO.id && packSO.attributes.name.length === name.length + ).length ) { return response.conflict({ body: `Pack with name "${name}" already exists.` }); } @@ -116,6 +120,26 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte : {}; const agentPolicyIds = Object.keys(agentPolicies); + const nonAgentPolicyReferences = filter( + currentPackSO.references, + (reference) => reference.type !== AGENT_POLICY_SAVED_OBJECT_TYPE + ); + + const getUpdatedReferences = () => { + if (policy_ids) { + return [ + ...nonAgentPolicyReferences, + ...policy_ids.map((id) => ({ + id, + name: agentPolicies[id].name, + type: AGENT_POLICY_SAVED_OBJECT_TYPE, + })), + ]; + } + + return currentPackSO.references; + }; + await savedObjectsClient.update( packSavedObjectType, request.params.id, @@ -127,18 +151,10 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte updated_at: moment().toISOString(), updated_by: currentUser, }, - policy_ids - ? { - refresh: 'wait_for', - references: policy_ids.map((id) => ({ - id, - name: agentPolicies[id].name, - type: AGENT_POLICY_SAVED_OBJECT_TYPE, - })), - } - : { - refresh: 'wait_for', - } + { + refresh: 'wait_for', + references: getUpdatedReferences(), + } ); const currentAgentPolicyIds = map( diff --git a/x-pack/plugins/osquery/server/saved_objects.ts b/x-pack/plugins/osquery/server/saved_objects.ts index 16a1f2efb7e9d..3080c728a4d3c 100644 --- a/x-pack/plugins/osquery/server/saved_objects.ts +++ b/x-pack/plugins/osquery/server/saved_objects.ts @@ -7,11 +7,12 @@ import { CoreSetup } from '../../../../src/core/server'; -import { savedQueryType, packType } from './lib/saved_query/saved_object_mappings'; +import { savedQueryType, packType, packAssetType } from './lib/saved_query/saved_object_mappings'; import { usageMetricType } from './routes/usage/saved_object_mappings'; export const initSavedObjects = (savedObjects: CoreSetup['savedObjects']) => { savedObjects.registerType(usageMetricType); savedObjects.registerType(savedQueryType); savedObjects.registerType(packType); + savedObjects.registerType(packAssetType); }; diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts b/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts index 82b19cb02faf8..4212ca46fc3c9 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts @@ -253,6 +253,16 @@ export default function (providerContext: FtrProviderContext) { resIndexPattern = err; } expect(resIndexPattern.response.data.statusCode).equal(404); + let resOsqueryPackAsset; + try { + resOsqueryPackAsset = await kibanaServer.savedObjects.get({ + type: 'osquery-pack-asset', + id: 'sample_osquery_pack_asset', + }); + } catch (err) { + resOsqueryPackAsset = err; + } + expect(resOsqueryPackAsset.response.data.statusCode).equal(404); }); it('should have removed the saved object', async function () { let res; @@ -447,6 +457,11 @@ const expectAssetsInstalled = ({ id: 'sample_security_rule', }); expect(resSecurityRule.id).equal('sample_security_rule'); + const resOsqueryPackAsset = await kibanaServer.savedObjects.get({ + type: 'osquery-pack-asset', + id: 'sample_osquery_pack_asset', + }); + expect(resOsqueryPackAsset.id).equal('sample_osquery_pack_asset'); const resCloudSecurityPostureRuleTemplate = await kibanaServer.savedObjects.get({ type: 'csp-rule-template', id: 'sample_csp_rule_template', @@ -526,6 +541,10 @@ const expectAssetsInstalled = ({ id: 'sample_ml_module', type: 'ml-module', }, + { + id: 'sample_osquery_pack_asset', + type: 'osquery-pack-asset', + }, { id: 'sample_search', type: 'search', @@ -687,6 +706,10 @@ const expectAssetsInstalled = ({ id: '4c758d70-ecf1-56b3-b704-6d8374841b34', type: 'epm-packages-assets', }, + { + id: '313ddb31-e70a-59e8-8287-310d4652a9b7', + type: 'epm-packages-assets', + }, { id: 'e786cbd9-0f3b-5a0b-82a6-db25145ebf58', type: 'epm-packages-assets', diff --git a/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts b/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts index 844a6abe3da06..7a69d5635f9ac 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts @@ -405,6 +405,10 @@ export default function (providerContext: FtrProviderContext) { id: 'sample_tag', type: 'tag', }, + { + id: 'sample_osquery_pack_asset', + type: 'osquery-pack-asset', + }, ], installed_es: [ { @@ -496,6 +500,7 @@ export default function (providerContext: FtrProviderContext) { { id: 'bf3b0b65-9fdc-53c6-a9ca-e76140e56490', type: 'epm-packages-assets' }, { id: '7f4c5aca-b4f5-5f0a-95af-051da37513fc', type: 'epm-packages-assets' }, { id: '4281a436-45a8-54ab-9724-fda6849f789d', type: 'epm-packages-assets' }, + { id: 'cb0bbdd7-e043-508b-91c0-09e4cc0f5a3c', type: 'epm-packages-assets' }, { id: '2e56f08b-1d06-55ed-abee-4708e1ccf0aa', type: 'epm-packages-assets' }, { id: '4035007b-9c33-5227-9803-2de8a17523b5', type: 'epm-packages-assets' }, { id: 'e6ae7d31-6920-5408-9219-91ef1662044b', type: 'epm-packages-assets' }, diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/kibana/osquery_pack_asset/sample_osquery_pack_asset.json b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/kibana/osquery_pack_asset/sample_osquery_pack_asset.json new file mode 100644 index 0000000000000..d22f8eb083d3e --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/kibana/osquery_pack_asset/sample_osquery_pack_asset.json @@ -0,0 +1,133 @@ +{ + "attributes": { + "name": "vuln-management", + "version": 1, + "queries": [ + { + "id": "kernel_info", + "interval": 86400, + "query": "select * from kernel_info;", + "version": "1.4.5" + }, + { + "id": "os_version", + "interval": 86400, + "query": "select * from os_version;", + "version": "1.4.5" + }, + { + "id": "kextstat", + "interval": 86400, + "platform": "darwin", + "query": "select * from kernel_extensions;", + "version": "1.4.5" + }, + { + "id": "kernel_modules", + "interval": 86400, + "platform": "linux", + "query": "select * from kernel_modules;", + "version": "1.4.5" + }, + { + "id": "installed_applications", + "interval": 86400, + "platform": "darwin", + "query": "select * from apps;", + "version": "1.4.5" + }, + { + "id": "browser_plugins", + "interval": 86400, + "platform": "darwin", + "query": "select browser_plugins.* from users join browser_plugins using (uid);", + "version": "1.6.1" + }, + { + "id": "safari_extensions", + "interval": 86400, + "platform": "darwin", + "query": "select safari_extensions.* from users join safari_extensions using (uid);", + "version": "1.6.1" + }, + { + "id": "opera_extensions", + "interval": 86400, + "platform": "darwin,linux", + "query": "select opera_extensions.* from users join opera_extensions using (uid);", + "version": "1.6.1" + }, + { + "id": "chrome_extensions", + "interval": 86400, + "query": "select chrome_extensions.* from users join chrome_extensions using (uid);", + "version": "1.6.1" + }, + { + "id": "firefox_addons", + "interval": 86400, + "platform": "darwin,linux", + "query": "select firefox_addons.* from users join firefox_addons using (uid);", + "version": "1.6.1" + }, + { + "id": "homebrew_packages", + "interval": 86400, + "platform": "darwin", + "query": "select * from homebrew_packages;", + "version": "1.4.5" + }, + { + "id": "package_receipts", + "interval": 86400, + "platform": "darwin", + "query": "select * from package_receipts;", + "version": "1.4.5" + }, + { + "id": "deb_packages", + "interval": 86400, + "platform": "linux", + "query": "select * from deb_packages;", + "version": "1.4.5" + }, + { + "id": "apt_sources", + "interval": 86400, + "platform": "linux", + "query": "select * from apt_sources;", + "version": "1.4.5" + }, + { + "id": "portage_packages", + "interval": 86400, + "platform": "linux", + "query": "select * from portage_packages;", + "version": "2.0.0" + }, + { + "id": "rpm_packages", + "interval": 86400, + "platform": "linux", + "query": "select * from rpm_packages;", + "version": "1.4.5" + }, + { + "id": "unauthenticated_sparkle_feeds", + "interval": 86400, + "platform": "darwin", + "query": "select feeds.*, p2.value as sparkle_version from (select a.name as app_name, a.path as app_path, a.bundle_identifier as bundle_id, p.value as feed_url from (select name, path, bundle_identifier from apps) a, plist p where p.path = a.path || '/Contents/Info.plist' and p.key = 'SUFeedURL' and feed_url like 'http://%') feeds left outer join plist p2 on p2.path = app_path || '/Contents/Frameworks/Sparkle.framework/Resources/Info.plist' where (p2.key = 'CFBundleShortVersionString' OR coalesce(p2.key, '') = '');", + "version": "1.4.5" + }, + { + "id": "backdoored_python_packages", + "interval": 86400, + "platform": "darwin,linux", + "query": "select name as package_name, version as package_version, path as package_path from python_packages where package_name = 'acqusition' or package_name = 'apidev-coop' or package_name = 'bzip' or package_name = 'crypt' or package_name = 'django-server' or package_name = 'pwd' or package_name = 'setup-tools' or package_name = 'telnet' or package_name = 'urlib3' or package_name = 'urllib';", + "version": "1.4.5" + } + ] + }, + "id": "sample_osquery_pack_asset", + "type": "osquery-pack-asset" +} diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.2.0/kibana/osquery_pack_asset/sample_osquery_pack_asset.json b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.2.0/kibana/osquery_pack_asset/sample_osquery_pack_asset.json new file mode 100644 index 0000000000000..d22f8eb083d3e --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.2.0/kibana/osquery_pack_asset/sample_osquery_pack_asset.json @@ -0,0 +1,133 @@ +{ + "attributes": { + "name": "vuln-management", + "version": 1, + "queries": [ + { + "id": "kernel_info", + "interval": 86400, + "query": "select * from kernel_info;", + "version": "1.4.5" + }, + { + "id": "os_version", + "interval": 86400, + "query": "select * from os_version;", + "version": "1.4.5" + }, + { + "id": "kextstat", + "interval": 86400, + "platform": "darwin", + "query": "select * from kernel_extensions;", + "version": "1.4.5" + }, + { + "id": "kernel_modules", + "interval": 86400, + "platform": "linux", + "query": "select * from kernel_modules;", + "version": "1.4.5" + }, + { + "id": "installed_applications", + "interval": 86400, + "platform": "darwin", + "query": "select * from apps;", + "version": "1.4.5" + }, + { + "id": "browser_plugins", + "interval": 86400, + "platform": "darwin", + "query": "select browser_plugins.* from users join browser_plugins using (uid);", + "version": "1.6.1" + }, + { + "id": "safari_extensions", + "interval": 86400, + "platform": "darwin", + "query": "select safari_extensions.* from users join safari_extensions using (uid);", + "version": "1.6.1" + }, + { + "id": "opera_extensions", + "interval": 86400, + "platform": "darwin,linux", + "query": "select opera_extensions.* from users join opera_extensions using (uid);", + "version": "1.6.1" + }, + { + "id": "chrome_extensions", + "interval": 86400, + "query": "select chrome_extensions.* from users join chrome_extensions using (uid);", + "version": "1.6.1" + }, + { + "id": "firefox_addons", + "interval": 86400, + "platform": "darwin,linux", + "query": "select firefox_addons.* from users join firefox_addons using (uid);", + "version": "1.6.1" + }, + { + "id": "homebrew_packages", + "interval": 86400, + "platform": "darwin", + "query": "select * from homebrew_packages;", + "version": "1.4.5" + }, + { + "id": "package_receipts", + "interval": 86400, + "platform": "darwin", + "query": "select * from package_receipts;", + "version": "1.4.5" + }, + { + "id": "deb_packages", + "interval": 86400, + "platform": "linux", + "query": "select * from deb_packages;", + "version": "1.4.5" + }, + { + "id": "apt_sources", + "interval": 86400, + "platform": "linux", + "query": "select * from apt_sources;", + "version": "1.4.5" + }, + { + "id": "portage_packages", + "interval": 86400, + "platform": "linux", + "query": "select * from portage_packages;", + "version": "2.0.0" + }, + { + "id": "rpm_packages", + "interval": 86400, + "platform": "linux", + "query": "select * from rpm_packages;", + "version": "1.4.5" + }, + { + "id": "unauthenticated_sparkle_feeds", + "interval": 86400, + "platform": "darwin", + "query": "select feeds.*, p2.value as sparkle_version from (select a.name as app_name, a.path as app_path, a.bundle_identifier as bundle_id, p.value as feed_url from (select name, path, bundle_identifier from apps) a, plist p where p.path = a.path || '/Contents/Info.plist' and p.key = 'SUFeedURL' and feed_url like 'http://%') feeds left outer join plist p2 on p2.path = app_path || '/Contents/Frameworks/Sparkle.framework/Resources/Info.plist' where (p2.key = 'CFBundleShortVersionString' OR coalesce(p2.key, '') = '');", + "version": "1.4.5" + }, + { + "id": "backdoored_python_packages", + "interval": 86400, + "platform": "darwin,linux", + "query": "select name as package_name, version as package_version, path as package_path from python_packages where package_name = 'acqusition' or package_name = 'apidev-coop' or package_name = 'bzip' or package_name = 'crypt' or package_name = 'django-server' or package_name = 'pwd' or package_name = 'setup-tools' or package_name = 'telnet' or package_name = 'urlib3' or package_name = 'urllib';", + "version": "1.4.5" + } + ] + }, + "id": "sample_osquery_pack_asset", + "type": "osquery-pack-asset" +}