diff --git a/oas_docs/output/kibana.serverless.staging.yaml b/oas_docs/output/kibana.serverless.staging.yaml index a4362db15cc7d..6df65e8ae2e3e 100644 --- a/oas_docs/output/kibana.serverless.staging.yaml +++ b/oas_docs/output/kibana.serverless.staging.yaml @@ -48009,6 +48009,7 @@ components: - started - stopped - updating + - error type: string Security_Entity_Analytics_API_Entity: oneOf: diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index a4362db15cc7d..6df65e8ae2e3e 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -48009,6 +48009,7 @@ components: - started - stopped - updating + - error type: string Security_Entity_Analytics_API_Entity: oneOf: diff --git a/oas_docs/output/kibana.staging.yaml b/oas_docs/output/kibana.staging.yaml index 16a6a94d34d81..76e217fcba16d 100644 --- a/oas_docs/output/kibana.staging.yaml +++ b/oas_docs/output/kibana.staging.yaml @@ -56775,6 +56775,7 @@ components: - started - stopped - updating + - error type: string Security_Entity_Analytics_API_Entity: oneOf: diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index 16a6a94d34d81..76e217fcba16d 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -56775,6 +56775,7 @@ components: - started - stopped - updating + - error type: string Security_Entity_Analytics_API_Entity: oneOf: diff --git a/packages/deeplinks/security/deep_links.ts b/packages/deeplinks/security/deep_links.ts index 54b18dcaf9206..644691bd5b8bc 100644 --- a/packages/deeplinks/security/deep_links.ts +++ b/packages/deeplinks/security/deep_links.ts @@ -86,6 +86,7 @@ export enum SecurityPageName { entityAnalytics = 'entity_analytics', entityAnalyticsManagement = 'entity_analytics-management', entityAnalyticsAssetClassification = 'entity_analytics-asset-classification', + entityAnalyticsEntityStoreManagement = 'entity_analytics-entity_store_management', coverageOverview = 'coverage-overview', notes = 'notes', } diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts index ed0806b798dd6..2dd83ca89bee0 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts @@ -25,7 +25,7 @@ export type IndexPattern = z.infer; export const IndexPattern = z.string(); export type EngineStatus = z.infer; -export const EngineStatus = z.enum(['installing', 'started', 'stopped', 'updating']); +export const EngineStatus = z.enum(['installing', 'started', 'stopped', 'updating', 'error']); export type EngineStatusEnum = typeof EngineStatus.enum; export const EngineStatusEnum = EngineStatus.enum; diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml index b06f484e4e29a..810961392aad1 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml @@ -38,6 +38,7 @@ components: - started - stopped - updating + - error IndexPattern: type: string diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index e7bb823c04ec8..d4cb8f088df88 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -124,6 +124,8 @@ export const ENTITY_ANALYTICS_PATH = '/entity_analytics' as const; export const ENTITY_ANALYTICS_MANAGEMENT_PATH = `/entity_analytics_management` as const; export const ENTITY_ANALYTICS_ASSET_CRITICALITY_PATH = `/entity_analytics_asset_criticality` as const; +export const ENTITY_ANALYTICS_ENTITY_STORE_MANAGEMENT_PATH = + `/entity_analytics_entity_store` as const; export const APP_ALERTS_PATH = `${APP_PATH}${ALERTS_PATH}` as const; export const APP_CASES_PATH = `${APP_PATH}${CASES_PATH}` as const; export const APP_ENDPOINTS_PATH = `${APP_PATH}${ENDPOINTS_PATH}` as const; diff --git a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml index 730ea240fe7b7..d3cce9170ae6a 100644 --- a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml @@ -806,6 +806,7 @@ components: - started - stopped - updating + - error type: string Entity: oneOf: diff --git a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml index 2522f3cb192ae..eecca3fe07ae6 100644 --- a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml @@ -806,6 +806,7 @@ components: - started - stopped - updating + - error type: string Entity: oneOf: diff --git a/x-pack/plugins/security_solution/public/app/solution_navigation/categories.ts b/x-pack/plugins/security_solution/public/app/solution_navigation/categories.ts index 86324b9ce9924..8d815ded5a3c4 100644 --- a/x-pack/plugins/security_solution/public/app/solution_navigation/categories.ts +++ b/x-pack/plugins/security_solution/public/app/solution_navigation/categories.ts @@ -50,7 +50,7 @@ export const CATEGORIES: Array> = [ type: LinkCategoryType.separator, linkIds: [ SecurityPageName.entityAnalyticsManagement, - SecurityPageName.entityAnalyticsAssetClassification, + SecurityPageName.entityAnalyticsEntityStoreManagement, ], // Linked from the management cards landing. }, ]; diff --git a/x-pack/plugins/security_solution/public/app/solution_navigation/links/sections/settings_links.ts b/x-pack/plugins/security_solution/public/app/solution_navigation/links/sections/settings_links.ts index ed08596fe6c79..abe7cc68603bd 100644 --- a/x-pack/plugins/security_solution/public/app/solution_navigation/links/sections/settings_links.ts +++ b/x-pack/plugins/security_solution/public/app/solution_navigation/links/sections/settings_links.ts @@ -12,7 +12,7 @@ import * as i18n from './settings_translations'; const ENTITY_ANALYTICS_LINKS = [ SecurityPageName.entityAnalyticsManagement, - SecurityPageName.entityAnalyticsAssetClassification, + SecurityPageName.entityAnalyticsEntityStoreManagement, ]; export const createSettingsLinksFromManage = (manageLink: LinkItem): LinkItem[] => { diff --git a/x-pack/plugins/security_solution/public/app/translations.ts b/x-pack/plugins/security_solution/public/app/translations.ts index 97f07ee6706b9..709bb5f614f7b 100644 --- a/x-pack/plugins/security_solution/public/app/translations.ts +++ b/x-pack/plugins/security_solution/public/app/translations.ts @@ -25,6 +25,10 @@ export const ENTITY_ANALYTICS_RISK_SCORE = i18n.translate( } ); +export const ENTITY_STORE = i18n.translate('xpack.securitySolution.navigation.entityStore', { + defaultMessage: 'Entity Store', +}); + export const NOTES = i18n.translate('xpack.securitySolution.navigation.notes', { defaultMessage: 'Notes', }); diff --git a/x-pack/plugins/security_solution/public/common/links/links.test.tsx b/x-pack/plugins/security_solution/public/common/links/links.test.tsx index aeffa1d44f823..c0f8c8cc48da4 100644 --- a/x-pack/plugins/security_solution/public/common/links/links.test.tsx +++ b/x-pack/plugins/security_solution/public/common/links/links.test.tsx @@ -547,7 +547,7 @@ describe('Security links', () => { describe('isLinkUiSettingsAllowed', () => { const SETTING_KEY = 'test setting'; const mockedLink: LinkItem = { - id: SecurityPageName.entityAnalyticsAssetClassification, + id: SecurityPageName.entityAnalyticsEntityStoreManagement, title: 'test title', path: '/test_path', }; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/entity_store.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/entity_store.ts new file mode 100644 index 0000000000000..34789402c89a5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/entity_store.ts @@ -0,0 +1,68 @@ +/* + * 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 { useMemo } from 'react'; +import type { + DeleteEntityEngineResponse, + EntityType, + GetEntityEngineResponse, + InitEntityEngineResponse, + ListEntityEnginesResponse, + StopEntityEngineResponse, +} from '../../../common/api/entity_analytics'; +import { API_VERSIONS } from '../../../common/entity_analytics/constants'; +import { useKibana } from '../../common/lib/kibana/kibana_react'; + +export const useEntityStoreRoutes = () => { + const http = useKibana().services.http; + + return useMemo(() => { + const initEntityStore = async (entityType: EntityType) => { + return http.fetch(`/api/entity_store/engines/${entityType}/init`, { + method: 'POST', + version: API_VERSIONS.public.v1, + body: JSON.stringify({}), + }); + }; + + const stopEntityStore = async (entityType: EntityType) => { + return http.fetch(`/api/entity_store/engines/${entityType}/stop`, { + method: 'POST', + version: API_VERSIONS.public.v1, + body: JSON.stringify({}), + }); + }; + + const getEntityEngine = async (entityType: EntityType) => { + return http.fetch(`/api/entity_store/engines/${entityType}`, { + method: 'GET', + version: API_VERSIONS.public.v1, + }); + }; + + const deleteEntityEngine = async (entityType: EntityType) => { + return http.fetch(`/api/entity_store/engines/${entityType}`, { + method: 'DELETE', + version: API_VERSIONS.public.v1, + }); + }; + + const listEntityEngines = async () => { + return http.fetch(`/api/entity_store/engines`, { + method: 'GET', + version: API_VERSIONS.public.v1, + }); + }; + + return { + initEntityStore, + stopEntityStore, + getEntityEngine, + deleteEntityEngine, + listEntityEngines, + }; + }, [http]); +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/dashboard_panels.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/dashboard_panels.tsx new file mode 100644 index 0000000000000..3b4f661e949f2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/dashboard_panels.tsx @@ -0,0 +1,249 @@ +/* + * 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, { useState } from 'react'; +import { + EuiEmptyPrompt, + EuiToolTip, + EuiButton, + EuiLoadingSpinner, + EuiFlexItem, + EuiFlexGroup, + EuiLoadingLogo, + EuiPanel, + EuiImage, +} from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n-react'; +import { RiskEngineStatusEnum } from '../../../../../common/api/entity_analytics'; +import { RiskScoreEntity } from '../../../../../common/search_strategy'; + +import { EntitiesList } from '../entities_list'; + +import { useEntityStoreEnablement } from '../hooks/use_entity_store'; +import { EntityStoreEnablementModal, type Enablements } from './enablement_modal'; + +import { EntityAnalyticsRiskScores } from '../../entity_analytics_risk_score'; +import { useInitRiskEngineMutation } from '../../../api/hooks/use_init_risk_engine_mutation'; +import { useEntityEngineStatus } from '../hooks/use_entity_engine_status'; + +import dashboardEnableImg from '../../../images/entity_store_dashboard.png'; +import { + ENABLEMENT_DESCRIPTION_BOTH, + ENABLEMENT_DESCRIPTION_ENTITY_STORE_ONLY, + ENABLEMENT_DESCRIPTION_RISK_ENGINE_ONLY, + ENABLEMENT_INITIALIZING_ENTITY_STORE, + ENABLEMENT_INITIALIZING_RISK_ENGINE, + ENABLE_ALL_TITLE, + ENABLE_ENTITY_STORE_TITLE, + ENABLE_RISK_SCORE_TITLE, +} from '../translations'; +import { useRiskEngineStatus } from '../../../api/hooks/use_risk_engine_status'; + +const EntityStoreDashboardPanelsComponent = () => { + const [modal, setModalState] = useState({ visible: false }); + const [riskEngineInitializing, setRiskEngineInitializing] = useState(false); + + const entityStore = useEntityEngineStatus(); + const riskEngineStatus = useRiskEngineStatus(); + + const { enable: enableStore } = useEntityStoreEnablement(); + const { mutate: initRiskEngine } = useInitRiskEngineMutation(); + + const enableEntityStore = (enable: Enablements) => () => { + setModalState({ visible: false }); + if (enable.riskScore) { + const options = { + onSuccess: () => { + setRiskEngineInitializing(false); + if (enable.entityStore) { + enableStore(); + } + }, + }; + setRiskEngineInitializing(true); + initRiskEngine(undefined, options); + } + + if (enable.entityStore) { + enableStore(); + } + }; + + if (entityStore.status === 'loading') { + return ( + + } + title={

{ENABLEMENT_INITIALIZING_ENTITY_STORE}

} + /> +
+ ); + } + + if (entityStore.status === 'installing') { + return ( + + } + title={

{ENABLEMENT_INITIALIZING_ENTITY_STORE}

} + body={ +

+ +

+ } + /> +
+ ); + } + + const isRiskScoreAvailable = + riskEngineStatus.data && + riskEngineStatus.data.risk_engine_status !== RiskEngineStatusEnum.NOT_INSTALLED; + + return ( + + {entityStore.status === 'enabled' && isRiskScoreAvailable && ( + <> + + + + + + + + + + + )} + {entityStore.status === 'enabled' && !isRiskScoreAvailable && ( + <> + + setModalState({ visible: true })} + loadingRiskEngine={riskEngineInitializing} + enablements="riskScore" + /> + + + + + + + )} + + {entityStore.status === 'not_installed' && !isRiskScoreAvailable && ( + // TODO: Move modal inside EnableEntityStore component, eliminating the onEnable prop in favour of forwarding the riskScoreEnabled status + setModalState({ visible: true })} + loadingRiskEngine={riskEngineInitializing} + /> + )} + + {entityStore.status === 'not_installed' && isRiskScoreAvailable && ( + <> + + + setModalState({ + visible: true, + }) + } + /> + + + + + + + + + )} + + setModalState({ visible })} + enableStore={enableEntityStore} + riskScore={{ disabled: isRiskScoreAvailable, checked: !isRiskScoreAvailable }} + entityStore={{ + disabled: entityStore.status === 'enabled', + checked: entityStore.status !== 'enabled', + }} + /> + + ); +}; + +interface EnableEntityStoreProps { + onEnable: () => void; + enablements: 'store' | 'riskScore' | 'both'; + loadingRiskEngine?: boolean; +} + +export const EnableEntityStore: React.FC = ({ + onEnable, + enablements, + loadingRiskEngine, +}) => { + const title = + enablements === 'store' + ? ENABLE_ENTITY_STORE_TITLE + : enablements === 'riskScore' + ? ENABLE_RISK_SCORE_TITLE + : ENABLE_ALL_TITLE; + + const body = + enablements === 'store' + ? ENABLEMENT_DESCRIPTION_ENTITY_STORE_ONLY + : enablements === 'riskScore' + ? ENABLEMENT_DESCRIPTION_RISK_ENGINE_ONLY + : ENABLEMENT_DESCRIPTION_BOTH; + + if (loadingRiskEngine) { + return ( + + } + title={

{ENABLEMENT_INITIALIZING_RISK_ENGINE}

} + /> +
+ ); + } + return ( + {title}} + body={

{body}

} + actions={ + + + + + + } + icon={} + /> + ); +}; + +export const EntityStoreDashboardPanels = React.memo(EntityStoreDashboardPanelsComponent); +EntityStoreDashboardPanels.displayName = 'EntityStoreDashboardPanels'; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/enablement_modal.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/enablement_modal.tsx new file mode 100644 index 0000000000000..94a3b6cd48edf --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/enablement_modal.tsx @@ -0,0 +1,141 @@ +/* + * 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 { + EuiModal, + EuiModalHeader, + EuiModalHeaderTitle, + EuiModalBody, + EuiFlexGroup, + EuiFlexItem, + EuiSwitch, + EuiModalFooter, + EuiButton, + EuiHorizontalRule, + EuiText, + EuiButtonEmpty, + EuiBetaBadge, + EuiToolTip, +} from '@elastic/eui'; +import React, { useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { TECHNICAL_PREVIEW, TECHNICAL_PREVIEW_TOOLTIP } from '../../../../common/translations'; +import { + ENABLEMENT_DESCRIPTION_RISK_ENGINE_ONLY, + ENABLEMENT_DESCRIPTION_ENTITY_STORE_ONLY, +} from '../translations'; + +export interface Enablements { + riskScore: boolean; + entityStore: boolean; +} + +interface EntityStoreEnablementModalProps { + visible: boolean; + toggle: (visible: boolean) => void; + enableStore: (enablements: Enablements) => () => void; + riskScore: { + disabled?: boolean; + checked?: boolean; + }; + entityStore: { + disabled?: boolean; + checked?: boolean; + }; +} + +export const EntityStoreEnablementModal: React.FC = ({ + visible, + toggle, + enableStore, + riskScore, + entityStore, +}) => { + const [enablements, setEnablements] = useState({ + riskScore: !!riskScore.checked, + entityStore: !!entityStore.checked, + }); + + if (!visible) { + return null; + } + return ( + toggle(false)}> + + + + + + + + + + + + + + + } + checked={enablements.riskScore} + disabled={riskScore.disabled || false} + onChange={() => setEnablements((prev) => ({ ...prev, riskScore: !prev.riskScore }))} + /> + + + {ENABLEMENT_DESCRIPTION_RISK_ENGINE_ONLY} + + + + + + + } + checked={enablements.entityStore} + disabled={entityStore.disabled || false} + onChange={() => + setEnablements((prev) => ({ ...prev, entityStore: !prev.entityStore })) + } + /> + + + + + + + {ENABLEMENT_DESCRIPTION_ENTITY_STORE_ONLY} + + + + + + toggle(false)}>{'Cancel'} + + + + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/entity_source_filter.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/entity_source_filter.tsx index b324adca0945e..aac8aad170f3f 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/entity_source_filter.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/entity_source_filter.tsx @@ -18,7 +18,7 @@ export enum EntitySource { CSV_UPLOAD = 'CSV upload', EVENTS = 'Events', } - +// TODO Fix the Entity Source field before using it export const EntitySourceFilter: React.FC = ({ selectedItems, onChange }) => { return ( diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/entities_list.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/entities_list.tsx index c02cbbb930c5c..a6e058af34392 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/entities_list.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/entities_list.tsx @@ -22,7 +22,6 @@ import type { Criteria } from '../../../explore/components/paginated_table'; import { PaginatedTable } from '../../../explore/components/paginated_table'; import { SeverityFilter } from '../severity/severity_filter'; import type { EntitySource } from './components/entity_source_filter'; -import { EntitySourceFilter } from './components/entity_source_filter'; import { useEntitiesListFilters } from './hooks/use_entities_list_filters'; import { AssetCriticalityFilter } from '../asset_criticality/asset_criticality_filter'; import { useEntitiesListQuery } from './hooks/use_entities_list_query'; @@ -41,7 +40,7 @@ export const EntitiesList: React.FC = () => { const [selectedSeverities, setSelectedSeverities] = useState([]); const [selectedCriticalities, setSelectedCriticalities] = useState([]); - const [selectedSources, setSelectedSources] = useState([]); + const [selectedSources, _] = useState([]); const filter = useEntitiesListFilters({ selectedSeverities, @@ -148,7 +147,6 @@ export const EntitiesList: React.FC = () => { selectedItems={selectedCriticalities} onChange={setSelectedCriticalities} /> - diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_columns.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_columns.tsx index cebc55693c9e8..52439d10a0000 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_columns.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_columns.tsx @@ -20,6 +20,7 @@ import type { Entity } from '../../../../../common/api/entity_analytics/entity_s import type { CriticalityLevels } from '../../../../../common/constants'; import { ENTITIES_LIST_TABLE_ID } from '../constants'; import { isUserEntity } from '../helpers'; +import { CRITICALITY_LEVEL_TITLE } from '../../asset_criticality/translations'; export type EntitiesListColumns = [ Columns, @@ -86,6 +87,7 @@ export const useEntitiesListColumns = (): EntitiesListColumns => { /> ), sortable: true, + truncateText: { lines: 2 }, render: (_: string, record: Entity) => { return ( @@ -94,7 +96,7 @@ export const useEntitiesListColumns = (): EntitiesListColumns => { ); }, - width: '30%', + width: '25%', }, { field: 'entity.source', @@ -104,7 +106,8 @@ export const useEntitiesListColumns = (): EntitiesListColumns => { defaultMessage="Source" /> ), - width: '10%', + width: '25%', + truncateText: { lines: 2 }, render: (source: string | undefined) => { if (source != null) { return {source}; @@ -124,7 +127,7 @@ export const useEntitiesListColumns = (): EntitiesListColumns => { width: '10%', render: (criticality: CriticalityLevels) => { if (criticality != null) { - return criticality; + return {CRITICALITY_LEVEL_TITLE[criticality]}; } return getEmptyTagValue(); @@ -173,7 +176,7 @@ export const useEntitiesListColumns = (): EntitiesListColumns => { }, }, { - field: 'entity.lastSeenTimestamp', + field: '@timestamp', name: ( { render: (lastUpdate: string) => { return ; }, - width: '25%', + width: '15%', }, ]; }; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_filters.test.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_filters.test.ts index f2fcd3e4f7685..de5f706d4524c 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_filters.test.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_filters.test.ts @@ -22,7 +22,7 @@ describe('useEntitiesListFilters', () => { mockUseGlobalFilterQuery.mockReturnValue({ filterQuery: null }); }); - it('should return empty array when no filters are selected', () => { + it('should return empty filter when no filters are selected', () => { const { result } = renderHook(() => useEntitiesListFilters({ selectedSeverities: [], @@ -49,13 +49,6 @@ describe('useEntitiesListFilters', () => { should: [ { term: { 'host.risk.calculated_level': RiskSeverity.Low } }, { term: { 'user.risk.calculated_level': RiskSeverity.Low } }, - ], - minimum_should_match: 1, - }, - }, - { - bool: { - should: [ { term: { 'host.risk.calculated_level': RiskSeverity.High } }, { term: { 'user.risk.calculated_level': RiskSeverity.High } }, ], @@ -77,8 +70,23 @@ describe('useEntitiesListFilters', () => { ); const expectedFilters: QueryDslQueryContainer[] = [ - { term: { 'asset.criticality': CriticalityLevels.EXTREME_IMPACT } }, - { term: { 'asset.criticality': CriticalityLevels.MEDIUM_IMPACT } }, + { + bool: { + minimum_should_match: 1, + should: [ + { + term: { + 'asset.criticality': CriticalityLevels.EXTREME_IMPACT, + }, + }, + { + term: { + 'asset.criticality': CriticalityLevels.MEDIUM_IMPACT, + }, + }, + ], + }, + }, ]; expect(result.current).toEqual(expectedFilters); @@ -138,7 +146,12 @@ describe('useEntitiesListFilters', () => { minimum_should_match: 1, }, }, - { term: { 'asset.criticality': CriticalityLevels.HIGH_IMPACT } }, + { + bool: { + should: [{ term: { 'asset.criticality': CriticalityLevels.HIGH_IMPACT } }], + minimum_should_match: 1, + }, + }, { term: { 'entity.source': EntitySource.CSV_UPLOAD } }, globalQuery, ]; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_filters.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_filters.ts index 7e9c25441a501..634f3f61c1590 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_filters.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entities_list_filters.ts @@ -26,11 +26,20 @@ export const useEntitiesListFilters = ({ const { filterQuery: globalQuery } = useGlobalFilterQuery(); return useMemo(() => { - const criticalityFilter: QueryDslQueryContainer[] = selectedCriticalities.map((value) => ({ - term: { - 'asset.criticality': value, - }, - })); + const criticalityFilter: QueryDslQueryContainer[] = selectedCriticalities.length + ? [ + { + bool: { + should: selectedCriticalities.map((value) => ({ + term: { + 'asset.criticality': value, + }, + })), + minimum_should_match: 1, + }, + }, + ] + : []; const sourceFilter: QueryDslQueryContainer[] = selectedSources.map((value) => ({ term: { @@ -38,23 +47,27 @@ export const useEntitiesListFilters = ({ }, })); - const severityFilter: QueryDslQueryContainer[] = selectedSeverities.map((value) => ({ - bool: { - should: [ + const severityFilter: QueryDslQueryContainer[] = selectedSeverities.length + ? [ { - term: { - 'host.risk.calculated_level': value, + bool: { + should: selectedSeverities.flatMap((value) => [ + { + term: { + 'host.risk.calculated_level': value, + }, + }, + { + term: { + 'user.risk.calculated_level': value, + }, + }, + ]), + minimum_should_match: 1, }, }, - { - term: { - 'user.risk.calculated_level': value, - }, - }, - ], - minimum_should_match: 1, - }, - })); + ] + : []; const filterList: QueryDslQueryContainer[] = [ ...severityFilter, diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_engine_status.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_engine_status.ts new file mode 100644 index 0000000000000..ef6ccd5d6fe20 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_engine_status.ts @@ -0,0 +1,58 @@ +/* + * 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 type { UseQueryOptions } from '@tanstack/react-query'; +import { useQuery } from '@tanstack/react-query'; +import type { ListEntityEnginesResponse } from '../../../../../common/api/entity_analytics'; +import { useEntityStoreRoutes } from '../../../api/entity_store'; + +export const ENTITY_STORE_ENGINE_STATUS = 'ENTITY_STORE_ENGINE_STATUS'; + +interface Options { + disabled?: boolean; + polling?: UseQueryOptions['refetchInterval']; +} + +export const useEntityEngineStatus = (opts: Options = {}) => { + // QUESTION: Maybe we should have an `EnablementStatus` API route for this? + const { listEntityEngines } = useEntityStoreRoutes(); + + const { isLoading, data } = useQuery({ + queryKey: [ENTITY_STORE_ENGINE_STATUS], + queryFn: () => listEntityEngines(), + refetchInterval: opts.polling, + enabled: !opts.disabled, + }); + + const status = (() => { + if (data?.count === 0) { + return 'not_installed'; + } + + if (data?.engines?.every((engine) => engine.status === 'stopped')) { + return 'stopped'; + } + + if (data?.engines?.some((engine) => engine.status === 'installing')) { + return 'installing'; + } + + if (isLoading) { + return 'loading'; + } + + if (!data) { + return 'error'; + } + + return 'enabled'; + })(); + + return { + status, + }; +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_store.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_store.ts new file mode 100644 index 0000000000000..29e9e6c5098c4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_store.ts @@ -0,0 +1,126 @@ +/* + * 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 type { UseMutationOptions } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { useCallback, useState } from 'react'; + +import type { + DeleteEntityEngineResponse, + InitEntityEngineResponse, + StopEntityEngineResponse, +} from '../../../../../common/api/entity_analytics'; +import { useEntityStoreRoutes } from '../../../api/entity_store'; +import { ENTITY_STORE_ENGINE_STATUS, useEntityEngineStatus } from './use_entity_engine_status'; + +const ENTITY_STORE_ENABLEMENT_INIT = 'ENTITY_STORE_ENABLEMENT_INIT'; + +export const useEntityStoreEnablement = () => { + const [polling, setPolling] = useState(false); + + useEntityEngineStatus({ + disabled: !polling, + polling: (data) => { + const shouldStopPolling = + data?.engines && + data.engines.length > 0 && + data.engines.every((engine) => engine.status === 'started'); + + if (shouldStopPolling) { + setPolling(false); + return false; + } + return 5000; + }, + }); + + const { initEntityStore } = useEntityStoreRoutes(); + const { refetch: initialize } = useQuery({ + queryKey: [ENTITY_STORE_ENABLEMENT_INIT], + queryFn: () => Promise.all([initEntityStore('user'), initEntityStore('host')]), + enabled: false, + }); + + const enable = useCallback(() => { + initialize().then(() => setPolling(true)); + }, [initialize]); + + return { enable }; +}; + +export const INIT_ENTITY_ENGINE_STATUS_KEY = ['POST', 'INIT_ENTITY_ENGINE']; + +export const useInvalidateEntityEngineStatusQuery = () => { + const queryClient = useQueryClient(); + + return useCallback(() => { + queryClient.invalidateQueries([ENTITY_STORE_ENGINE_STATUS], { + refetchType: 'active', + }); + }, [queryClient]); +}; + +export const useInitEntityEngineMutation = (options?: UseMutationOptions<{}>) => { + const invalidateEntityEngineStatusQuery = useInvalidateEntityEngineStatusQuery(); + const { initEntityStore } = useEntityStoreRoutes(); + return useMutation( + () => Promise.all([initEntityStore('user'), initEntityStore('host')]), + { + ...options, + mutationKey: INIT_ENTITY_ENGINE_STATUS_KEY, + onSettled: (...args) => { + invalidateEntityEngineStatusQuery(); + + if (options?.onSettled) { + options.onSettled(...args); + } + }, + } + ); +}; + +export const STOP_ENTITY_ENGINE_STATUS_KEY = ['POST', 'STOP_ENTITY_ENGINE']; + +export const useStopEntityEngineMutation = (options?: UseMutationOptions<{}>) => { + const invalidateEntityEngineStatusQuery = useInvalidateEntityEngineStatusQuery(); + const { stopEntityStore } = useEntityStoreRoutes(); + return useMutation( + () => Promise.all([stopEntityStore('user'), stopEntityStore('host')]), + { + ...options, + mutationKey: STOP_ENTITY_ENGINE_STATUS_KEY, + onSettled: (...args) => { + invalidateEntityEngineStatusQuery(); + + if (options?.onSettled) { + options.onSettled(...args); + } + }, + } + ); +}; + +export const DELETE_ENTITY_ENGINE_STATUS_KEY = ['POST', 'STOP_ENTITY_ENGINE']; + +export const useDeleteEntityEngineMutation = (options?: UseMutationOptions<{}>) => { + const invalidateEntityEngineStatusQuery = useInvalidateEntityEngineStatusQuery(); + const { deleteEntityEngine } = useEntityStoreRoutes(); + return useMutation( + () => Promise.all([deleteEntityEngine('user'), deleteEntityEngine('host')]), + { + ...options, + mutationKey: DELETE_ENTITY_ENGINE_STATUS_KEY, + onSettled: (...args) => { + invalidateEntityEngineStatusQuery(); + + if (options?.onSettled) { + options.onSettled(...args); + } + }, + } + ); +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/translations.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/translations.ts new file mode 100644 index 0000000000000..127ff5c88506b --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/translations.ts @@ -0,0 +1,64 @@ +/* + * 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 ENABLE_ENTITY_STORE_TITLE = i18n.translate( + 'xpack.securitySolution.entityAnalytics.entityStore.enablement.title.store', + { + defaultMessage: 'Enable entity store', + } +); +export const ENABLE_RISK_SCORE_TITLE = i18n.translate( + 'xpack.securitySolution.entityAnalytics.entityStore.enablement.title.risk', + { + defaultMessage: 'Enable entity risk score', + } +); +export const ENABLE_ALL_TITLE = i18n.translate( + 'xpack.securitySolution.entityAnalytics.entityStore.enablement.title.both', + { + defaultMessage: 'Enable entity store and risk score', + } +); + +export const ENABLEMENT_INITIALIZING_RISK_ENGINE = i18n.translate( + 'xpack.securitySolution.entityAnalytics.entityStore.enablement.initializing.risk', + { + defaultMessage: 'Initializing risk engine', + } +); + +export const ENABLEMENT_INITIALIZING_ENTITY_STORE = i18n.translate( + 'xpack.securitySolution.entityAnalytics.entityStore.enablement.initializing.store', + { + defaultMessage: 'Initializing entity store', + } +); + +export const ENABLEMENT_DESCRIPTION_RISK_ENGINE_ONLY = i18n.translate( + 'xpack.securitySolution.entityAnalytics.entityStore.enablement.description.risk', + { + defaultMessage: + 'Provides real-time visibility into user activity, helping you identify and mitigate potential security risks.', + } +); + +export const ENABLEMENT_DESCRIPTION_ENTITY_STORE_ONLY = i18n.translate( + 'xpack.securitySolution.entityAnalytics.entityStore.enablement.description.store', + { + defaultMessage: "Allows comprehensive monitoring of your system's hosts and users.", + } +); + +export const ENABLEMENT_DESCRIPTION_BOTH = i18n.translate( + 'xpack.securitySolution.entityAnalytics.entityStore.enablement.description.both', + { + defaultMessage: + 'Your entity store is currently empty. Add information about your entities directly from your logs, or import them using a text file.', + } +); diff --git a/x-pack/plugins/security_solution/public/entity_analytics/images/entity_store_dashboard.png b/x-pack/plugins/security_solution/public/entity_analytics/images/entity_store_dashboard.png new file mode 100644 index 0000000000000..b1a6ae31fadcc Binary files /dev/null and b/x-pack/plugins/security_solution/public/entity_analytics/images/entity_store_dashboard.png differ diff --git a/x-pack/plugins/security_solution/public/entity_analytics/pages/asset_criticality_upload_page.tsx b/x-pack/plugins/security_solution/public/entity_analytics/pages/asset_criticality_upload_page.tsx deleted file mode 100644 index eced6e59031ad..0000000000000 --- a/x-pack/plugins/security_solution/public/entity_analytics/pages/asset_criticality_upload_page.tsx +++ /dev/null @@ -1,186 +0,0 @@ -/* - * 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 { - EuiFlexGroup, - EuiFlexItem, - EuiHorizontalRule, - EuiIcon, - EuiLink, - EuiPageHeader, - EuiPanel, - EuiSpacer, - EuiText, - EuiTitle, - EuiEmptyPrompt, - EuiCallOut, - EuiCode, -} from '@elastic/eui'; -import React from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { ASSET_CRITICALITY_INDEX_PATTERN } from '../../../common/entity_analytics/asset_criticality'; -import { useUiSetting$, useKibana } from '../../common/lib/kibana'; -import { ENABLE_ASSET_CRITICALITY_SETTING } from '../../../common/constants'; -import { AssetCriticalityFileUploader } from '../components/asset_criticality_file_uploader/asset_criticality_file_uploader'; -import { useAssetCriticalityPrivileges } from '../components/asset_criticality/use_asset_criticality'; -import { useHasSecurityCapability } from '../../helper_hooks'; - -export const AssetCriticalityUploadPage = () => { - const { docLinks } = useKibana().services; - const entityAnalyticsLinks = docLinks.links.securitySolution.entityAnalytics; - const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics'); - const [isAssetCriticalityEnabled] = useUiSetting$(ENABLE_ASSET_CRITICALITY_SETTING); - const { - data: privileges, - error: privilegesError, - isLoading, - } = useAssetCriticalityPrivileges('AssetCriticalityUploadPage'); - const hasWritePermissions = privileges?.has_write_permissions; - - if (isLoading) { - // Wait for permission before rendering content to avoid flickering - return null; - } - - if ( - !hasEntityAnalyticsCapability || - !isAssetCriticalityEnabled || - privilegesError?.body.status_code === 403 - ) { - const errorMessage = privilegesError?.body.message ?? ( - - ); - - return ( - - - - } - body={

{errorMessage}

} - /> - ); - } - - if (!hasWritePermissions) { - return ( - - } - color="primary" - iconType="iInCircle" - > - - {ASSET_CRITICALITY_INDEX_PATTERN}, - }} - /> - - - ); - } - - return ( - <> - - } - /> - - - - - -

- -

-
- - - - - - -
- - - - - - -

- -

-
- - - - - - -

- -

-
- - - - - -
-
-
- - ); -}; - -AssetCriticalityUploadPage.displayName = 'AssetCriticalityUploadPage'; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_analytics_dashboard.tsx b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_analytics_dashboard.tsx index 90f5ec66c8a38..2fbc4f67ab6ef 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_analytics_dashboard.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_analytics_dashboard.tsx @@ -23,16 +23,16 @@ import { RiskScoreUpdatePanel } from '../components/risk_score_update_panel'; import { useHasSecurityCapability } from '../../helper_hooks'; import { EntityAnalyticsHeader } from '../components/entity_analytics_header'; import { EntityAnalyticsAnomalies } from '../components/entity_analytics_anomalies'; + +import { EntityStoreDashboardPanels } from '../components/entity_store/components/dashboard_panels'; import { EntityAnalyticsRiskScores } from '../components/entity_analytics_risk_score'; -import { EntitiesList } from '../components/entity_store/entities_list'; import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; const EntityAnalyticsComponent = () => { const { data: riskScoreEngineStatus } = useRiskEngineStatus(); const { indicesExist, loading: isSourcererLoading, sourcererDataView } = useSourcererDataView(); const isRiskScoreModuleLicenseAvailable = useHasSecurityCapability('entity-analytics'); - - const isEntityStoreDisabled = useIsExperimentalFeatureEnabled('entityStoreDisabled'); + const isEntityStoreFeatureFlagDisabled = useIsExperimentalFeatureEnabled('entityStoreDisabled'); return ( <> @@ -59,23 +59,25 @@ const EntityAnalyticsComponent = () => { - - - + {!isEntityStoreFeatureFlagDisabled ? ( + + + + ) : ( + <> + + + - - - + + + + + )} - - {!isEntityStoreDisabled ? ( - - - - ) : null} )} diff --git a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx new file mode 100644 index 0000000000000..0e09e5ceac3ef --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx @@ -0,0 +1,456 @@ +/* + * 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 { + EuiFlexGroup, + EuiFlexItem, + EuiConfirmModal, + EuiHorizontalRule, + EuiIcon, + EuiLink, + EuiPageHeader, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, + EuiCallOut, + EuiCode, + EuiSwitch, + EuiHealth, + EuiButton, + EuiLoadingSpinner, + EuiToolTip, + EuiBetaBadge, +} from '@elastic/eui'; +import React, { useCallback, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useEntityEngineStatus } from '../components/entity_store/hooks/use_entity_engine_status'; +import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; +import { ASSET_CRITICALITY_INDEX_PATTERN } from '../../../common/entity_analytics/asset_criticality'; +import { useUiSetting$, useKibana } from '../../common/lib/kibana'; +import { ENABLE_ASSET_CRITICALITY_SETTING } from '../../../common/constants'; +import { AssetCriticalityFileUploader } from '../components/asset_criticality_file_uploader/asset_criticality_file_uploader'; +import { useAssetCriticalityPrivileges } from '../components/asset_criticality/use_asset_criticality'; +import { useHasSecurityCapability } from '../../helper_hooks'; +import { + useDeleteEntityEngineMutation, + useInitEntityEngineMutation, + useStopEntityEngineMutation, +} from '../components/entity_store/hooks/use_entity_store'; +import { TECHNICAL_PREVIEW, TECHNICAL_PREVIEW_TOOLTIP } from '../../common/translations'; + +const entityStoreEnabledStatuses = ['enabled']; +const switchDisabledStatuses = ['error', 'loading', 'installing']; +const entityStoreInstallingStatuses = ['installing', 'loading']; + +export const EntityStoreManagementPage = () => { + const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics'); + const isEntityStoreFeatureFlagDisabled = useIsExperimentalFeatureEnabled('entityStoreDisabled'); + const [isAssetCriticalityEnabled] = useUiSetting$(ENABLE_ASSET_CRITICALITY_SETTING); + const { + data: assetCriticalityPrivileges, + error: assetCriticalityPrivilegesError, + isLoading: assetCriticalityIsLoading, + } = useAssetCriticalityPrivileges('AssetCriticalityUploadPage'); + const hasAssetCriticalityWritePermissions = assetCriticalityPrivileges?.has_write_permissions; + + const [polling, setPolling] = useState(false); + const entityStoreStatus = useEntityEngineStatus({ + disabled: false, + polling: !polling + ? undefined + : (data) => { + const shouldStopPolling = + data?.engines && + data.engines.length > 0 && + data.engines.every((engine) => engine.status === 'started'); + + if (shouldStopPolling) { + setPolling(false); + return false; + } + return 1000; + }, + }); + const initEntityEngineMutation = useInitEntityEngineMutation(); + const stopEntityEngineMutation = useStopEntityEngineMutation(); + const deleteEntityEngineMutation = useDeleteEntityEngineMutation({ + onSuccess: () => { + closeClearModal(); + }, + }); + + const [isClearModalVisible, setIsClearModalVisible] = useState(false); + const closeClearModal = useCallback(() => setIsClearModalVisible(false), []); + const showClearModal = useCallback(() => setIsClearModalVisible(true), []); + + const onSwitchClick = useCallback(() => { + if (switchDisabledStatuses.includes(entityStoreStatus.status)) { + return; + } + + if (entityStoreEnabledStatuses.includes(entityStoreStatus.status)) { + stopEntityEngineMutation.mutate(); + } else { + setPolling(true); + initEntityEngineMutation.mutate(); + } + }, [initEntityEngineMutation, stopEntityEngineMutation, entityStoreStatus]); + + if (assetCriticalityIsLoading) { + // Wait for permission before rendering content to avoid flickering + return null; + } + + const AssetCriticalityIssueCallout: React.FC = () => { + const errorMessage = assetCriticalityPrivilegesError?.body.message ?? ( + + ); + + return ( + + + } + color="primary" + iconType="iInCircle" + > + {errorMessage} + + + ); + }; + + const ClearEntityDataPanel: React.FC = () => { + return ( + <> + + +

+ +

+ + +
+ + { + showClearModal(); + }} + > + + +
+ {isClearModalVisible && ( + + } + onCancel={closeClearModal} + onConfirm={() => { + deleteEntityEngineMutation.mutate(); + }} + cancelButtonText={ + + } + confirmButtonText={ + + } + buttonColor="danger" + defaultFocusedButton="confirm" + > + + + )} + + ); + }; + + const FileUploadSection: React.FC = () => { + if ( + !hasEntityAnalyticsCapability || + !isAssetCriticalityEnabled || + assetCriticalityPrivilegesError?.body.status_code === 403 + ) { + return ; + } + if (!hasAssetCriticalityWritePermissions) { + return ; + } + return ( + + +

+ +

+
+ + + + + + +
+ ); + }; + + const canDeleteEntityEngine = !['not_installed', 'loading', 'installing'].includes( + entityStoreStatus.status + ); + + const isMutationLoading = + initEntityEngineMutation.isLoading || + stopEntityEngineMutation.isLoading || + deleteEntityEngineMutation.isLoading; + + return ( + <> + + {' '} + + + + + } + alignItems="center" + rightSideItems={ + !isEntityStoreFeatureFlagDisabled + ? [ + , + ] + : [] + } + /> + + + + + {isEntityStoreFeatureFlagDisabled && } + + + + + + + + {!isEntityStoreFeatureFlagDisabled && canDeleteEntityEngine && } + + + + + ); +}; + +EntityStoreManagementPage.displayName = 'EntityStoreManagementPage'; + +const WhatIsAssetCriticalityPanel: React.FC = () => { + const { docLinks } = useKibana().services; + const entityAnalyticsLinks = docLinks.links.securitySolution.entityAnalytics; + + return ( + + + + +

+ +

+
+
+ + + + + + +

+ +

+
+ + + + + +
+ ); +}; + +const EntityStoreFeatureFlagNotAvailableCallout: React.FC = () => { + return ( + <> + + + } + color="primary" + iconType="iInCircle" + > + + + + + + ); +}; + +const EntityStoreHealth: React.FC<{ currentEntityStoreStatus: string }> = ({ + currentEntityStoreStatus, +}) => { + return ( + + {entityStoreEnabledStatuses.includes(currentEntityStoreStatus) ? 'On' : 'Off'} + + ); +}; + +const EnablementButton: React.FC<{ + isLoading: boolean; + isDisabled: boolean; + status: string; + onSwitch: () => void; +}> = ({ isLoading, isDisabled, status, onSwitch }) => { + return ( + + {isLoading && ( + + + + )} + + + + ); +}; + +const InsufficientAssetCriticalityPrivilegesCallout: React.FC = () => { + return ( + + } + color="primary" + iconType="iInCircle" + > + + {ASSET_CRITICALITY_INDEX_PATTERN}, + }} + /> + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/routes.tsx b/x-pack/plugins/security_solution/public/entity_analytics/routes.tsx index 835265c7402fe..1cc1a24b020cb 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/routes.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/routes.tsx @@ -14,12 +14,13 @@ import { NotFoundPage } from '../app/404'; import { ENTITY_ANALYTICS_ASSET_CRITICALITY_PATH, + ENTITY_ANALYTICS_ENTITY_STORE_MANAGEMENT_PATH, ENTITY_ANALYTICS_MANAGEMENT_PATH, SecurityPageName, } from '../../common/constants'; import { EntityAnalyticsManagementPage } from './pages/entity_analytics_management_page'; import { PluginTemplateWrapper } from '../common/components/plugin_template_wrapper'; -import { AssetCriticalityUploadPage } from './pages/asset_criticality_upload_page'; +import { EntityStoreManagementPage } from './pages/entity_store_management_page'; const EntityAnalyticsManagementTelemetry = () => ( @@ -47,7 +48,7 @@ EntityAnalyticsManagementContainer.displayName = 'EntityAnalyticsManagementConta const EntityAnalyticsAssetClassificationTelemetry = () => ( - + @@ -69,6 +70,30 @@ const EntityAnalyticsAssetClassificationContainer: React.FC = React.memo(() => { EntityAnalyticsAssetClassificationContainer.displayName = 'EntityAnalyticsAssetClassificationContainer'; +const EntityAnalyticsEntityStoreTelemetry = () => ( + + + + + + +); + +const EntityAnalyticsEntityStoreContainer: React.FC = React.memo(() => { + return ( + + + + + ); +}); + +EntityAnalyticsEntityStoreContainer.displayName = 'EntityAnalyticsEntityStoreContainer'; + export const routes = [ { path: ENTITY_ANALYTICS_MANAGEMENT_PATH, @@ -78,4 +103,8 @@ export const routes = [ path: ENTITY_ANALYTICS_ASSET_CRITICALITY_PATH, component: EntityAnalyticsAssetClassificationContainer, }, + { + path: ENTITY_ANALYTICS_ENTITY_STORE_MANAGEMENT_PATH, + component: EntityAnalyticsEntityStoreContainer, + }, ]; diff --git a/x-pack/plugins/security_solution/public/explore/components/paginated_table/index.tsx b/x-pack/plugins/security_solution/public/explore/components/paginated_table/index.tsx index 09019820ab548..82cde8a24f153 100644 --- a/x-pack/plugins/security_solution/public/explore/components/paginated_table/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/components/paginated_table/index.tsx @@ -138,7 +138,7 @@ export interface Columns { name: string | React.ReactNode; render?: (item: T, node: U) => React.ReactNode; sortable?: boolean | Func; - truncateText?: boolean; + truncateText?: boolean | { lines: number }; width?: string; } diff --git a/x-pack/plugins/security_solution/public/management/links.ts b/x-pack/plugins/security_solution/public/management/links.ts index bc64f26a768a6..c83a7360910fa 100644 --- a/x-pack/plugins/security_solution/public/management/links.ts +++ b/x-pack/plugins/security_solution/public/management/links.ts @@ -15,9 +15,8 @@ import { } from '../../common/endpoint/service/authz'; import { BLOCKLIST_PATH, - ENABLE_ASSET_CRITICALITY_SETTING, ENDPOINTS_PATH, - ENTITY_ANALYTICS_ASSET_CRITICALITY_PATH, + ENTITY_ANALYTICS_ENTITY_STORE_MANAGEMENT_PATH, ENTITY_ANALYTICS_MANAGEMENT_PATH, EVENT_FILTERS_PATH, HOST_ISOLATION_EXCEPTIONS_PATH, @@ -39,8 +38,8 @@ import { RESPONSE_ACTIONS_HISTORY, TRUSTED_APPLICATIONS, ENTITY_ANALYTICS_RISK_SCORE, - ASSET_CRITICALITY, NOTES, + ENTITY_STORE, } from '../app/translations'; import { licenseService } from '../common/hooks/use_license'; import type { LinkItem } from '../common/links/types'; @@ -64,7 +63,7 @@ const categories = [ }), linkIds: [ SecurityPageName.entityAnalyticsManagement, - SecurityPageName.entityAnalyticsAssetClassification, + SecurityPageName.entityAnalyticsEntityStoreManagement, ], }, { @@ -196,20 +195,16 @@ export const links: LinkItem = { licenseType: 'platinum', }, { - id: SecurityPageName.entityAnalyticsAssetClassification, - title: ASSET_CRITICALITY, - description: i18n.translate( - 'xpack.securitySolution.appLinks.assetClassificationDescription', - { - defaultMessage: 'Represents the criticality of an asset to your business infrastructure.', - } - ), + id: SecurityPageName.entityAnalyticsEntityStoreManagement, + title: ENTITY_STORE, + description: i18n.translate('xpack.securitySolution.appLinks.entityStoreDescription', { + defaultMessage: "Allows comprehensive monitoring of your system's hosts and users.", + }), landingIcon: IconAssetCriticality, - path: ENTITY_ANALYTICS_ASSET_CRITICALITY_PATH, + path: ENTITY_ANALYTICS_ENTITY_STORE_MANAGEMENT_PATH, skipUrlState: true, hideTimeline: true, capabilities: [`${SERVER_APP_ID}.entity-analytics`], - uiSettingRequired: ENABLE_ASSET_CRITICALITY_SETTING, }, { id: SecurityPageName.responseActionsHistory, diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/constants.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/constants.ts index db0f48877a73c..addf432f20398 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/constants.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/constants.ts @@ -5,12 +5,7 @@ * 2.0. */ -import type { EngineStatus } from '../../../../common/api/entity_analytics/entity_store/common.gen'; - -/** - * Default index pattern for entity store - * This is the same as the default index pattern for the SIEM app but might diverge in the future - */ +import type { EngineStatus } from '../../../../common/api/entity_analytics'; export const DEFAULT_LOOKBACK_PERIOD = '24h'; @@ -21,6 +16,7 @@ export const ENGINE_STATUS: Record, EngineStatus> = { STARTED: 'started', STOPPED: 'stopped', UPDATING: 'updating', + ERROR: 'error', }; export const MAX_SEARCH_RESPONSE_SIZE = 10_000; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts index a71be61781e00..d2e21a1d10903 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts @@ -16,17 +16,15 @@ import type { SortOrder } from '@elastic/elasticsearch/lib/api/types'; import type { TaskManagerStartContract } from '@kbn/task-manager-plugin/server'; import type { DataViewsService } from '@kbn/data-views-plugin/common'; import { isEqual } from 'lodash/fp'; -import type { EngineDataviewUpdateResult } from '../../../../common/api/entity_analytics/entity_store/engine/apply_dataview_indices.gen'; import type { AppClient } from '../../..'; -import type { Entity } from '../../../../common/api/entity_analytics/entity_store/entities/common.gen'; import type { + Entity, + EngineDataviewUpdateResult, InitEntityEngineRequestBody, InitEntityEngineResponse, -} from '../../../../common/api/entity_analytics/entity_store/engine/init.gen'; -import type { EntityType, InspectQuery, -} from '../../../../common/api/entity_analytics/entity_store/common.gen'; +} from '../../../../common/api/entity_analytics'; import { EngineDescriptorClient } from './saved_object/engine_descriptor'; import { ENGINE_STATUS, MAX_SEARCH_RESPONSE_SIZE } from './constants'; import { AssetCriticalityEcsMigrationClient } from '../asset_criticality/asset_criticality_migration_client'; @@ -120,7 +118,7 @@ export class EntityStoreDataClient { throw new Error('Task Manager is not available'); } - const { logger, esClient, namespace, taskManager, appClient, dataViewsService } = this.options; + const { logger } = this.options; await this.riskScoreDataClient.createRiskScoreLatestIndex(); @@ -135,8 +133,6 @@ export class EntityStoreDataClient { logger.info( `In namespace ${this.options.namespace}: Initializing entity store for ${entityType}` ); - const debugLog = (message: string) => - logger.debug(`[Entity Engine] [${entityType}] ${message}`); const descriptor = await this.engineClient.init(entityType, { filter, @@ -144,9 +140,34 @@ export class EntityStoreDataClient { indexPattern, }); logger.debug(`Initialized engine for ${entityType}`); - const indexPatterns = await buildIndexPatterns(namespace, appClient, dataViewsService); // first create the entity definition without starting it // so that the index template is created which we can add a component template to + + this.asyncSetup( + entityType, + fieldHistoryLength, + this.options.taskManager, + indexPattern, + filter, + pipelineDebugMode + ).catch((error) => { + logger.error('There was an error during async setup of the Entity Store', error); + }); + + return descriptor; + } + + private async asyncSetup( + entityType: EntityType, + fieldHistoryLength: number, + taskManager: TaskManagerStartContract, + indexPattern: string, + filter: string, + pipelineDebugMode: boolean + ) { + const { esClient, logger, namespace, appClient, dataViewsService } = this.options; + const indexPatterns = await buildIndexPatterns(namespace, appClient, dataViewsService); + const unitedDefinition = getUnitedEntityDefinition({ indexPatterns, entityType, @@ -155,66 +176,84 @@ export class EntityStoreDataClient { }); const { entityManagerDefinition } = unitedDefinition; - await this.entityClient.createEntityDefinition({ - definition: { - ...entityManagerDefinition, - filter, - indexPatterns: indexPattern - ? [...entityManagerDefinition.indexPatterns, ...indexPattern.split(',')] - : entityManagerDefinition.indexPatterns, - }, - installOnly: true, - }); - debugLog(`Created entity definition`); + const debugLog = (message: string) => + logger.debug(`[Entity Engine] [${entityType}] ${message}`); - // the index must be in place with the correct mapping before the enrich policy is created - // this is because the enrich policy will fail if the index does not exist with the correct fields - await createEntityIndexComponentTemplate({ - unitedDefinition, - esClient, - }); - debugLog(`Created entity index component template`); - await createEntityIndex({ - entityType, - esClient, - namespace, - logger, - }); - debugLog(`Created entity index`); + try { + // clean up any existing entity store + await this.delete(entityType, taskManager, { deleteData: false, deleteEngine: false }); + + // set up the entity manager definition + await this.entityClient.createEntityDefinition({ + definition: { + ...entityManagerDefinition, + filter, + indexPatterns: indexPattern + ? [...entityManagerDefinition.indexPatterns, ...indexPattern.split(',')] + : entityManagerDefinition.indexPatterns, + }, + installOnly: true, + }); + debugLog(`Created entity definition`); - // we must create and execute the enrich policy before the pipeline is created - // this is because the pipeline will fail if the enrich index does not exist - await createFieldRetentionEnrichPolicy({ - unitedDefinition, - esClient, - }); - debugLog(`Created field retention enrich policy`); - await executeFieldRetentionEnrichPolicy({ - unitedDefinition, - esClient, - logger, - }); - debugLog(`Executed field retention enrich policy`); - await createPlatformPipeline({ - debugMode: pipelineDebugMode, - unitedDefinition, - logger, - esClient, - }); - debugLog(`Created @platform pipeline`); + // the index must be in place with the correct mapping before the enrich policy is created + // this is because the enrich policy will fail if the index does not exist with the correct fields + await createEntityIndexComponentTemplate({ + unitedDefinition, + esClient, + }); + debugLog(`Created entity index component template`); + await createEntityIndex({ + entityType, + esClient, + namespace, + logger, + }); + debugLog(`Created entity index`); + + // we must create and execute the enrich policy before the pipeline is created + // this is because the pipeline will fail if the enrich index does not exist + await createFieldRetentionEnrichPolicy({ + unitedDefinition, + esClient, + }); + debugLog(`Created field retention enrich policy`); + await executeFieldRetentionEnrichPolicy({ + unitedDefinition, + esClient, + logger, + }); + debugLog(`Executed field retention enrich policy`); + await createPlatformPipeline({ + debugMode: pipelineDebugMode, + unitedDefinition, + logger, + esClient, + }); + debugLog(`Created @platform pipeline`); - // finally start the entity definition now that everything is in place - const updated = await this.start(entityType, { force: true }); - debugLog(`Started entity definition`); + // finally start the entity definition now that everything is in place + const updated = await this.start(entityType, { force: true }); + debugLog(`Started entity definition`); - // the task will execute the enrich policy on a schedule - await startEntityStoreFieldRetentionEnrichTask({ - namespace, - logger, - taskManager, - }); - logger.info(`Entity store initialized`); - return { ...descriptor, ...updated }; + // the task will execute the enrich policy on a schedule + await startEntityStoreFieldRetentionEnrichTask({ + namespace, + logger, + taskManager, + }); + logger.info(`Entity store initialized`); + + return updated; + } catch (err) { + this.options.logger.error( + `Error initializing entity store for ${entityType}: ${err.message}` + ); + + await this.engineClient.update(entityType, ENGINE_STATUS.ERROR); + + await this.delete(entityType, taskManager, { deleteData: true, deleteEngine: false }); + } } public async getExistingEntityDefinition(entityType: EntityType) { @@ -284,9 +323,10 @@ export class EntityStoreDataClient { public async delete( entityType: EntityType, taskManager: TaskManagerStartContract, - deleteData: boolean + options = { deleteData: false, deleteEngine: true } ) { const { namespace, logger, esClient, appClient, dataViewsService } = this.options; + const { deleteData, deleteEngine } = options; const descriptor = await this.engineClient.maybeGet(entityType); const indexPatterns = await buildIndexPatterns(namespace, appClient, dataViewsService); const unitedDefinition = getUnitedEntityDefinition({ @@ -328,6 +368,10 @@ export class EntityStoreDataClient { logger, }); } + + if (descriptor && deleteEngine) { + await this.engineClient.delete(entityType); + } // if the last engine then stop the task const { engines } = await this.engineClient.list(); if (engines.length === 0) { @@ -338,10 +382,6 @@ export class EntityStoreDataClient { }); } - if (descriptor) { - await this.engineClient.delete(entityType); - } - return { deleted: true }; } catch (e) { logger.error(`Error deleting entity store for ${entityType}: ${e.message}`); diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/delete.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/delete.ts index 0828f94852cf2..e11c9d3fa7b9d 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/delete.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/delete.ts @@ -56,7 +56,10 @@ export const deleteEntityEngineRoute = ( const secSol = await context.securitySolution; const body = await secSol .getEntityStoreDataClient() - .delete(request.params.entityType, taskManager, !!request.query.data); + .delete(request.params.entityType, taskManager, { + deleteData: !!request.query.data, + deleteEngine: true, + }); return response.ok({ body }); } catch (e) { diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/engine_descriptor.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/engine_descriptor.ts index c3376fe0b3c67..af7b4ba80dde5 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/engine_descriptor.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/engine_descriptor.ts @@ -41,8 +41,28 @@ export class EngineDescriptorClient { ) { const engineDescriptor = await this.find(entityType); - if (engineDescriptor.total > 0) - throw new Error(`Entity engine for ${entityType} already exists`); + if (engineDescriptor.total > 1) { + throw new Error(`Found multiple engine descriptors for entity type ${entityType}`); + } + + if (engineDescriptor.total === 1) { + const old = engineDescriptor.saved_objects[0].attributes; + const update = { + ...old, + status: ENGINE_STATUS.INSTALLING, + filter, + fieldHistoryLength, + indexPattern, + }; + await this.deps.soClient.update( + entityEngineDescriptorTypeName, + this.getSavedObjectId(entityType), + update, + { refresh: 'wait_for' } + ); + + return update; + } const { attributes } = await this.deps.soClient.create( entityEngineDescriptorTypeName, diff --git a/x-pack/plugins/security_solution_ess/public/navigation/side_navigation.ts b/x-pack/plugins/security_solution_ess/public/navigation/side_navigation.ts index bab25b3966c6b..9e08345dac8f1 100644 --- a/x-pack/plugins/security_solution_ess/public/navigation/side_navigation.ts +++ b/x-pack/plugins/security_solution_ess/public/navigation/side_navigation.ts @@ -83,7 +83,7 @@ const stackManagementLinks: Array> { link: 'management:watcher' }, { link: 'management:maintenanceWindows' }, { link: `${SECURITY_UI_APP_ID}:${SecurityPageName.entityAnalyticsManagement}` }, - { link: `${SECURITY_UI_APP_ID}:${SecurityPageName.entityAnalyticsAssetClassification}` }, + { link: `${SECURITY_UI_APP_ID}:${SecurityPageName.entityAnalyticsEntityStoreManagement}` }, ], }, { diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/management_cards.ts b/x-pack/plugins/security_solution_serverless/public/navigation/management_cards.ts index 6306933d6a14b..cb39ae7c661e0 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/management_cards.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/management_cards.ts @@ -16,7 +16,7 @@ const SecurityManagementCards = new Map { diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index c9713d7d10c73..99e52c8d22234 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -35103,7 +35103,6 @@ "xpack.securitySolution.api.riskEngine.taskManagerUnavailable": "Le gestionnaire des tâches n'est pas disponible mais est requis par le moteur de risque. Veuillez autoriser le plug-in du gestionnaire des tâches et essayer à nouveau.", "xpack.securitySolution.appLinks.actionHistoryDescription": "Affichez l'historique des actions de réponse effectuées sur les hôtes.", "xpack.securitySolution.appLinks.alerts": "Alertes", - "xpack.securitySolution.appLinks.assetClassificationDescription": "Représente la criticité d'un actif pour l'infrastructure de votre entreprise.", "xpack.securitySolution.appLinks.attackDiscovery": "Attack discovery", "xpack.securitySolution.appLinks.blocklistDescription": "Excluez les applications non souhaitées de l'exécution sur vos hôtes.", "xpack.securitySolution.appLinks.category.cloudSecurity": "Sécurité du cloud", @@ -38361,7 +38360,6 @@ "xpack.securitySolution.entityAnalytics.assetCriticalityResultStep.uploadAnotherFile": "Charger un autre fichier", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.acceptedFileFormats": "Formats de fichiers : {formats}", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.advancedSettingDisabledMessage": "Veuillez autoriser \"{ENABLE_ASSET_CRITICALITY_SETTING}\" dans les paramètres avancés pour accéder à la page.", - "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.advancedSettingDisabledTitle": "Cette page est désactivée", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.assetCriticalityLabels": "Niveau de criticité : Spécifiez n'importe laquelle de ces {labels}", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.assetIdentifierDescription": "Identificateur : Spécifiez le {hostName} ou le {userName} de l'entité.", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.assetTypeDescription": "Type d'entité : Veuillez indiquer si l'entité est un {host} ou un {user}.", @@ -38381,7 +38379,6 @@ "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.resultsStepTitle": "Résultats", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.selectFileStepTitle": "Sélectionner un fichier", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.subTitle": "Importez vos données de criticité des ressources", - "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.title": "Criticité des ressources", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.unsupportedFileTypeError": "Format de fichier sélectionné non valide. Veuillez choisir un fichier {supportedFileExtensions} et réessayer", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.uploadFileSizeLimit": "La taille maximale de fichier est de : {maxFileSize}", "xpack.securitySolution.entityAnalytics.assetCriticalityValidationStep.assignButtonText": "Affecter", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d123d0edd8948..032f15409355c 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -34848,7 +34848,6 @@ "xpack.securitySolution.api.riskEngine.taskManagerUnavailable": "タスクマネージャーは使用できませんが、リスクエンジンには必要です。taskManagerプラグインを有効にして、再試行してください。", "xpack.securitySolution.appLinks.actionHistoryDescription": "ホストで実行された対応アクションの履歴を表示します。", "xpack.securitySolution.appLinks.alerts": "アラート", - "xpack.securitySolution.appLinks.assetClassificationDescription": "ビジネスインフラに対するアセットの重要度を表します。", "xpack.securitySolution.appLinks.attackDiscovery": "Attack discovery", "xpack.securitySolution.appLinks.blocklistDescription": "不要なアプリケーションがホストで実行されないようにします。", "xpack.securitySolution.appLinks.category.cloudSecurity": "クラウドセキュリティ", @@ -38103,7 +38102,6 @@ "xpack.securitySolution.entityAnalytics.assetCriticalityResultStep.uploadAnotherFile": "別のファイルをアップロード", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.acceptedFileFormats": "ファイル形式:{formats}", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.advancedSettingDisabledMessage": "ページにアクセスするには、詳細設定で\"{ENABLE_ASSET_CRITICALITY_SETTING}\"を有効化してください。", - "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.advancedSettingDisabledTitle": "このページは無効です", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.assetCriticalityLabels": "重要度レベル:{labels}のいずれかを指定", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.assetIdentifierDescription": "識別子:エンティティの{hostName}または{userName}を指定します。", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.assetTypeDescription": "エンティティタイプ:エンティティが{host}か{user}かを示します。", @@ -38123,7 +38121,6 @@ "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.resultsStepTitle": "結果", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.selectFileStepTitle": "ファイルを選択", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.subTitle": "アセット重要度データをインポート", - "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.title": "アセット重要度", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.unsupportedFileTypeError": "無効なファイル形式が選択されました。{supportedFileExtensions}ファイルを選択して再試行してください。", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.uploadFileSizeLimit": "最大ファイルサイズ:{maxFileSize}", "xpack.securitySolution.entityAnalytics.assetCriticalityValidationStep.assignButtonText": "割り当て", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 3e658947b010b..ea9606d1c6e00 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -34891,7 +34891,6 @@ "xpack.securitySolution.api.riskEngine.taskManagerUnavailable": "任务管理器不可用,但风险引擎需要该管理器。请启用任务管理器插件然后重试。", "xpack.securitySolution.appLinks.actionHistoryDescription": "查看在主机上执行的响应操作的历史记录。", "xpack.securitySolution.appLinks.alerts": "告警", - "xpack.securitySolution.appLinks.assetClassificationDescription": "表示资产对您的业务基础设施的关键度。", "xpack.securitySolution.appLinks.attackDiscovery": "Attack Discovery", "xpack.securitySolution.appLinks.blocklistDescription": "阻止不需要的应用程序在您的主机上运行。", "xpack.securitySolution.appLinks.category.cloudSecurity": "云安全", @@ -38149,7 +38148,6 @@ "xpack.securitySolution.entityAnalytics.assetCriticalityResultStep.uploadAnotherFile": "上传另一个文件", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.acceptedFileFormats": "文件格式:{formats}", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.advancedSettingDisabledMessage": "请在高级设置上启用“{ENABLE_ASSET_CRITICALITY_SETTING}”以访问此页面。", - "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.advancedSettingDisabledTitle": "已禁用此页面", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.assetCriticalityLabels": "关键度级别:指定任意 {labels}", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.assetIdentifierDescription": "标识符:指定实体的 {hostName} 或 {userName}。", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.assetTypeDescription": "实体类型:指示实体是 {host} 还是 {user}。", @@ -38169,7 +38167,6 @@ "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.resultsStepTitle": "结果", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.selectFileStepTitle": "选择文件", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.subTitle": "导入资产关键度数据", - "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.title": "资产关键度", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.unsupportedFileTypeError": "选定的文件格式无效。请选择 {supportedFileExtensions} 文件,然后重试", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.uploadFileSizeLimit": "最大文件大小:{maxFileSize}", "xpack.securitySolution.entityAnalytics.assetCriticalityValidationStep.assignButtonText": "分配", diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality_upload_page.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality_upload_page.cy.ts index 097e2541f57f9..1a48a7835f195 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality_upload_page.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality_upload_page.cy.ts @@ -31,7 +31,7 @@ describe( }); it('renders page as expected', () => { - cy.get(PAGE_TITLE).should('have.text', 'Asset criticality'); + cy.get(PAGE_TITLE).should('include.text', 'Entity Store'); }); it('uploads a file', () => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/dashboards/enable_risk_score_redirect.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/dashboards/enable_risk_score_redirect.cy.ts index ebff5204d4981..5ffffadc798f4 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/dashboards/enable_risk_score_redirect.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/dashboards/enable_risk_score_redirect.cy.ts @@ -18,25 +18,38 @@ import { RiskScoreEntity } from '../../../tasks/risk_scores/common'; import { ENTITY_ANALYTICS_URL } from '../../../urls/navigation'; import { PAGE_TITLE } from '../../../screens/entity_analytics_management'; -describe('Enable risk scores from dashboard', { tags: ['@ess', '@serverless'] }, () => { - beforeEach(() => { - login(); - visit(ENTITY_ANALYTICS_URL); - }); - - it('host risk enable button should redirect to entity management page', () => { - cy.get(ENABLE_HOST_RISK_SCORE_BUTTON).should('exist'); - - clickEnableRiskScore(RiskScoreEntity.host); - - cy.get(PAGE_TITLE).should('have.text', 'Entity Risk Score'); - }); - - it('user risk enable button should redirect to entity management page', () => { - cy.get(ENABLE_USER_RISK_SCORE_BUTTON).should('exist'); - - clickEnableRiskScore(RiskScoreEntity.user); - - cy.get(PAGE_TITLE).should('have.text', 'Entity Risk Score'); - }); -}); +describe( + 'Enable risk scores from dashboard', + { + tags: ['@ess', '@serverless'], + env: { + ftrConfig: { + kbnServerArgs: [ + `--xpack.securitySolution.enableExperimental=${JSON.stringify(['entityStoreDisabled'])}`, + ], + }, + }, + }, + () => { + beforeEach(() => { + login(); + visit(ENTITY_ANALYTICS_URL); + }); + + it('host risk enable button should redirect to entity management page', () => { + cy.get(ENABLE_HOST_RISK_SCORE_BUTTON).should('exist'); + + clickEnableRiskScore(RiskScoreEntity.host); + + cy.get(PAGE_TITLE).should('have.text', 'Entity Risk Score'); + }); + + it('user risk enable button should redirect to entity management page', () => { + cy.get(ENABLE_USER_RISK_SCORE_BUTTON).should('exist'); + + clickEnableRiskScore(RiskScoreEntity.user); + + cy.get(PAGE_TITLE).should('have.text', 'Entity Risk Score'); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/dashboards/upgrade_risk_score.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/dashboards/upgrade_risk_score.cy.ts index ee229539c8dbd..ef114aec912a6 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/dashboards/upgrade_risk_score.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/dashboards/upgrade_risk_score.cy.ts @@ -34,68 +34,81 @@ import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; const spaceId = 'default'; -describe('Upgrade risk scores', { tags: ['@ess'] }, () => { - beforeEach(() => { - login(); - deleteRiskEngineConfiguration(); - deleteAlertsAndRules(); - }); - - describe('show upgrade risk button', () => { +describe( + 'Upgrade risk scores', + { + tags: ['@ess'], + env: { + ftrConfig: { + kbnServerArgs: [ + `--xpack.securitySolution.enableExperimental=${JSON.stringify(['entityStoreDisabled'])}`, + ], + }, + }, + }, + () => { beforeEach(() => { - deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId }); - deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId }); - installLegacyRiskScoreModule(RiskScoreEntity.host, spaceId); - installLegacyRiskScoreModule(RiskScoreEntity.user, spaceId); - visitWithTimeRange(ENTITY_ANALYTICS_URL); - }); - - afterEach(() => { - deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId }); - deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId }); - cy.task('esArchiverUnload', { archiveName: 'risk_hosts' }); - cy.task('esArchiverUnload', { archiveName: 'risk_users' }); - }); - - it('shows upgrade panel', () => { - cy.get(UPGRADE_RISK_SCORE_BUTTON).should('be.visible'); - - clickUpgradeRiskScore(); - - cy.get(PAGE_TITLE).should('have.text', 'Entity Risk Score'); - }); - }); - - describe('upgrade risk engine', () => { - beforeEach(() => { - cy.task('esArchiverLoad', { archiveName: 'risk_hosts' }); - cy.task('esArchiverLoad', { archiveName: 'risk_users' }); login(); - installRiskScoreModule(); - visitWithTimeRange(ENTITY_ANALYTICS_URL); - }); - - afterEach(() => { - cy.task('esArchiverUnload', { archiveName: 'risk_hosts' }); - cy.task('esArchiverUnload', { archiveName: 'risk_users' }); - deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId }); - deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId }); deleteRiskEngineConfiguration(); + deleteAlertsAndRules(); }); - it('show old risk score data before upgrade, and hide after', () => { - cy.get(HOSTS_TABLE).should('be.visible'); - cy.get(HOSTS_TABLE_ROWS).should('have.length', 5); - - cy.get(USERS_TABLE).should('be.visible'); - cy.get(USERS_TABLE_ROWS).should('have.length', 5); - - upgradeRiskEngine(); - - visitWithTimeRange(ENTITY_ANALYTICS_URL); + describe('show upgrade risk button', () => { + beforeEach(() => { + deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId }); + deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId }); + installLegacyRiskScoreModule(RiskScoreEntity.host, spaceId); + installLegacyRiskScoreModule(RiskScoreEntity.user, spaceId); + visitWithTimeRange(ENTITY_ANALYTICS_URL); + }); + + afterEach(() => { + deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId }); + deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId }); + cy.task('esArchiverUnload', { archiveName: 'risk_hosts' }); + cy.task('esArchiverUnload', { archiveName: 'risk_users' }); + }); + + it('shows upgrade panel', () => { + cy.get(UPGRADE_RISK_SCORE_BUTTON).should('be.visible'); + + clickUpgradeRiskScore(); + + cy.get(PAGE_TITLE).should('have.text', 'Entity Risk Score'); + }); + }); - cy.get(HOST_RISK_SCORE_NO_DATA_DETECTED).should('be.visible'); - cy.get(USER_RISK_SCORE_NO_DATA_DETECTED).should('be.visible'); + describe('upgrade risk engine', () => { + beforeEach(() => { + cy.task('esArchiverLoad', { archiveName: 'risk_hosts' }); + cy.task('esArchiverLoad', { archiveName: 'risk_users' }); + login(); + installRiskScoreModule(); + visitWithTimeRange(ENTITY_ANALYTICS_URL); + }); + + afterEach(() => { + cy.task('esArchiverUnload', { archiveName: 'risk_hosts' }); + cy.task('esArchiverUnload', { archiveName: 'risk_users' }); + deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId }); + deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId }); + deleteRiskEngineConfiguration(); + }); + + it('show old risk score data before upgrade, and hide after', () => { + cy.get(HOSTS_TABLE).should('be.visible'); + cy.get(HOSTS_TABLE_ROWS).should('have.length', 5); + + cy.get(USERS_TABLE).should('be.visible'); + cy.get(USERS_TABLE_ROWS).should('have.length', 5); + + upgradeRiskEngine(); + + visitWithTimeRange(ENTITY_ANALYTICS_URL); + + cy.get(HOST_RISK_SCORE_NO_DATA_DETECTED).should('be.visible'); + cy.get(USER_RISK_SCORE_NO_DATA_DETECTED).should('be.visible'); + }); }); - }); -}); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/asset_criticality.ts b/x-pack/test/security_solution_cypress/cypress/screens/asset_criticality.ts index 6af6826227b6c..a397716d0c503 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/asset_criticality.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/asset_criticality.ts @@ -7,7 +7,7 @@ import { getDataTestSubjectSelector } from '../helpers/common'; -export const PAGE_TITLE = getDataTestSubjectSelector('assetCriticalityUploadPage'); +export const PAGE_TITLE = getDataTestSubjectSelector('entityStoreManagementPage'); export const FILE_PICKER = getDataTestSubjectSelector('asset-criticality-file-picker'); export const ASSIGN_BUTTON = getDataTestSubjectSelector('asset-criticality-assign-button'); export const RESULT_STEP = getDataTestSubjectSelector('asset-criticality-result-step-success');