mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
# Backport This will backport the following commits from `main` to `8.x`: - [[SecuritySolution] Update entity store source field (#197186)](https://github.com/elastic/kibana/pull/197186) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Pablo Machado","email":"pablo.nevesmachado@elastic.co"},"sourceCommit":{"committedDate":"2024-10-23T08:05:05Z","message":"[SecuritySolution] Update entity store source field (#197186)\n\n## Summary\r\n\r\nIn this PR the source field will only store the first identified index\r\nfor an entity.\r\nThe PR also updates the entities list panel to display a textual\r\ndescription of the source index and adds a new source field filter.\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/c7aad254-f871-4035-9dac-89decce31a55\r\n\r\n\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [x] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: Mark Hopkin <mark.hopkin@elastic.co>\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"0bafb9632c2e1b09dd56586f15dca83d8ad5b358","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:skip","v9.0.0","Team: SecuritySolution","Theme: entity_analytics","Feature:Entity Analytics","v8.9.0","Team:Entity Analytics","8.16 candidate","v8.16.0","backport:version","v8.17.0"],"title":"[SecuritySolution] Update entity store source field","number":197186,"url":"https://github.com/elastic/kibana/pull/197186","mergeCommit":{"message":"[SecuritySolution] Update entity store source field (#197186)\n\n## Summary\r\n\r\nIn this PR the source field will only store the first identified index\r\nfor an entity.\r\nThe PR also updates the entities list panel to display a textual\r\ndescription of the source index and adds a new source field filter.\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/c7aad254-f871-4035-9dac-89decce31a55\r\n\r\n\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [x] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: Mark Hopkin <mark.hopkin@elastic.co>\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"0bafb9632c2e1b09dd56586f15dca83d8ad5b358"}},"sourceBranch":"main","suggestedTargetBranches":["8.9","8.16","8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/197186","number":197186,"mergeCommit":{"message":"[SecuritySolution] Update entity store source field (#197186)\n\n## Summary\r\n\r\nIn this PR the source field will only store the first identified index\r\nfor an entity.\r\nThe PR also updates the entities list panel to display a textual\r\ndescription of the source index and adds a new source field filter.\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/c7aad254-f871-4035-9dac-89decce31a55\r\n\r\n\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [x] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: Mark Hopkin <mark.hopkin@elastic.co>\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"0bafb9632c2e1b09dd56586f15dca83d8ad5b358"}},{"branch":"8.9","label":"v8.9.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.16","label":"v8.16.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.x","label":"v8.17.0","branchLabelMappingKey":"^v8.17.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Pablo Machado <pablo.nevesmachado@elastic.co>
This commit is contained in:
parent
b663248f8a
commit
dc1eb11071
19 changed files with 246 additions and 126 deletions
|
@ -30472,9 +30472,7 @@ components:
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
source:
|
source:
|
||||||
items:
|
type: string
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
- source
|
- source
|
||||||
|
@ -30626,9 +30624,7 @@ components:
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
source:
|
source:
|
||||||
items:
|
type: string
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
- source
|
- source
|
||||||
|
|
|
@ -30472,9 +30472,7 @@ components:
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
source:
|
source:
|
||||||
items:
|
type: string
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
- source
|
- source
|
||||||
|
@ -30626,9 +30624,7 @@ components:
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
source:
|
source:
|
||||||
items:
|
type: string
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
- source
|
- source
|
||||||
|
|
|
@ -39302,9 +39302,7 @@ components:
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
source:
|
source:
|
||||||
items:
|
type: string
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
- source
|
- source
|
||||||
|
@ -39456,9 +39454,7 @@ components:
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
source:
|
source:
|
||||||
items:
|
type: string
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
- source
|
- source
|
||||||
|
|
|
@ -39302,9 +39302,7 @@ components:
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
source:
|
source:
|
||||||
items:
|
type: string
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
- source
|
- source
|
||||||
|
@ -39456,9 +39454,7 @@ components:
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
source:
|
source:
|
||||||
items:
|
type: string
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
- source
|
- source
|
||||||
|
|
|
@ -24,7 +24,7 @@ export const UserEntity = z.object({
|
||||||
'@timestamp': z.string().datetime(),
|
'@timestamp': z.string().datetime(),
|
||||||
entity: z.object({
|
entity: z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
source: z.array(z.string()),
|
source: z.string(),
|
||||||
}),
|
}),
|
||||||
user: z.object({
|
user: z.object({
|
||||||
full_name: z.array(z.string()).optional(),
|
full_name: z.array(z.string()).optional(),
|
||||||
|
@ -48,7 +48,7 @@ export const HostEntity = z.object({
|
||||||
'@timestamp': z.string().datetime(),
|
'@timestamp': z.string().datetime(),
|
||||||
entity: z.object({
|
entity: z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
source: z.array(z.string()),
|
source: z.string(),
|
||||||
}),
|
}),
|
||||||
host: z.object({
|
host: z.object({
|
||||||
hostname: z.array(z.string()).optional(),
|
hostname: z.array(z.string()).optional(),
|
||||||
|
|
|
@ -22,12 +22,10 @@ components:
|
||||||
- name
|
- name
|
||||||
- source
|
- source
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
source:
|
source:
|
||||||
type: array
|
type: string
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
user:
|
user:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -84,12 +82,10 @@ components:
|
||||||
- name
|
- name
|
||||||
- source
|
- source
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
source:
|
source:
|
||||||
type: array
|
type: string
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
host:
|
host:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
|
@ -910,9 +910,7 @@ components:
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
source:
|
source:
|
||||||
items:
|
type: string
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
- source
|
- source
|
||||||
|
@ -1062,9 +1060,7 @@ components:
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
source:
|
source:
|
||||||
items:
|
type: string
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
- source
|
- source
|
||||||
|
|
|
@ -910,9 +910,7 @@ components:
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
source:
|
source:
|
||||||
items:
|
type: string
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
- source
|
- source
|
||||||
|
@ -1062,9 +1060,7 @@ components:
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
source:
|
source:
|
||||||
items:
|
type: string
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
- source
|
- source
|
||||||
|
|
|
@ -8,30 +8,26 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { MultiselectFilter } from '../../../../common/components/multiselect_filter';
|
import { MultiselectFilter } from '../../../../common/components/multiselect_filter';
|
||||||
|
import { EntitySourceTag } from '../types';
|
||||||
|
|
||||||
interface SourceFilterProps {
|
interface SourceFilterProps {
|
||||||
selectedItems: EntitySource[];
|
selectedItems: EntitySourceTag[];
|
||||||
onChange: (selectedItems: EntitySource[]) => void;
|
onChange: (selectedItems: EntitySourceTag[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum EntitySource {
|
|
||||||
CSV_UPLOAD = 'CSV upload',
|
|
||||||
EVENTS = 'Events',
|
|
||||||
}
|
|
||||||
// TODO Fix the Entity Source field before using it
|
|
||||||
export const EntitySourceFilter: React.FC<SourceFilterProps> = ({ selectedItems, onChange }) => {
|
export const EntitySourceFilter: React.FC<SourceFilterProps> = ({ selectedItems, onChange }) => {
|
||||||
return (
|
return (
|
||||||
<MultiselectFilter<EntitySource>
|
<MultiselectFilter<EntitySourceTag>
|
||||||
title={i18n.translate(
|
title={i18n.translate(
|
||||||
'xpack.securitySolution.entityAnalytics.entityStore.entitySource.filterTitle',
|
'xpack.securitySolution.entityAnalytics.entityStore.entitySource.filterTitle',
|
||||||
{
|
{
|
||||||
defaultMessage: 'Source',
|
defaultMessage: 'Source',
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
items={Object.values(EntitySource)}
|
items={Object.values(EntitySourceTag)}
|
||||||
selectedItems={selectedItems}
|
selectedItems={selectedItems}
|
||||||
onSelectionChange={onChange}
|
onSelectionChange={onChange}
|
||||||
width={140}
|
width={190}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -33,7 +33,7 @@ const responseData: ListEntitiesResponse = {
|
||||||
user: { name: entityName },
|
user: { name: entityName },
|
||||||
entity: {
|
entity: {
|
||||||
name: entityName,
|
name: entityName,
|
||||||
source: ['source'],
|
source: 'test-index',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -21,12 +21,13 @@ import { EntityType } from '../../../../common/api/entity_analytics/entity_store
|
||||||
import type { Criteria } from '../../../explore/components/paginated_table';
|
import type { Criteria } from '../../../explore/components/paginated_table';
|
||||||
import { PaginatedTable } from '../../../explore/components/paginated_table';
|
import { PaginatedTable } from '../../../explore/components/paginated_table';
|
||||||
import { SeverityFilter } from '../severity/severity_filter';
|
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 { useEntitiesListFilters } from './hooks/use_entities_list_filters';
|
||||||
import { AssetCriticalityFilter } from '../asset_criticality/asset_criticality_filter';
|
import { AssetCriticalityFilter } from '../asset_criticality/asset_criticality_filter';
|
||||||
import { useEntitiesListQuery } from './hooks/use_entities_list_query';
|
import { useEntitiesListQuery } from './hooks/use_entities_list_query';
|
||||||
import { ENTITIES_LIST_TABLE_ID, rowItems } from './constants';
|
import { ENTITIES_LIST_TABLE_ID, rowItems } from './constants';
|
||||||
import { useEntitiesListColumns } from './hooks/use_entities_list_columns';
|
import { useEntitiesListColumns } from './hooks/use_entities_list_columns';
|
||||||
|
import type { EntitySourceTag } from './types';
|
||||||
|
|
||||||
export const EntitiesList: React.FC = () => {
|
export const EntitiesList: React.FC = () => {
|
||||||
const { deleteQuery, setQuery, isInitializing, from, to } = useGlobalTime();
|
const { deleteQuery, setQuery, isInitializing, from, to } = useGlobalTime();
|
||||||
|
@ -40,7 +41,7 @@ export const EntitiesList: React.FC = () => {
|
||||||
|
|
||||||
const [selectedSeverities, setSelectedSeverities] = useState<RiskSeverity[]>([]);
|
const [selectedSeverities, setSelectedSeverities] = useState<RiskSeverity[]>([]);
|
||||||
const [selectedCriticalities, setSelectedCriticalities] = useState<CriticalityLevels[]>([]);
|
const [selectedCriticalities, setSelectedCriticalities] = useState<CriticalityLevels[]>([]);
|
||||||
const [selectedSources, _] = useState<EntitySource[]>([]);
|
const [selectedSources, setSelectedSources] = useState<EntitySourceTag[]>([]);
|
||||||
|
|
||||||
const filter = useEntitiesListFilters({
|
const filter = useEntitiesListFilters({
|
||||||
selectedSeverities,
|
selectedSeverities,
|
||||||
|
@ -147,6 +148,7 @@ export const EntitiesList: React.FC = () => {
|
||||||
selectedItems={selectedCriticalities}
|
selectedItems={selectedCriticalities}
|
||||||
onChange={setSelectedCriticalities}
|
onChange={setSelectedCriticalities}
|
||||||
/>
|
/>
|
||||||
|
<EntitySourceFilter selectedItems={selectedSources} onChange={setSelectedSources} />
|
||||||
</EuiFilterGroup>
|
</EuiFilterGroup>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
|
|
|
@ -5,40 +5,70 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { isUserEntity } from './helpers';
|
import { isUserEntity, sourceFieldToText } from './helpers';
|
||||||
import type {
|
import type {
|
||||||
Entity,
|
Entity,
|
||||||
UserEntity,
|
UserEntity,
|
||||||
} from '../../../../common/api/entity_analytics/entity_store/entities/common.gen';
|
} from '../../../../common/api/entity_analytics/entity_store/entities/common.gen';
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import { TestProviders } from '@kbn/timelines-plugin/public/mock';
|
||||||
|
|
||||||
describe('isUserEntity', () => {
|
describe('helpers', () => {
|
||||||
it('should return true if the record is a UserEntity', () => {
|
describe('isUserEntity', () => {
|
||||||
const userEntity: UserEntity = {
|
it('should return true if the record is a UserEntity', () => {
|
||||||
'@timestamp': '2021-08-02T14:00:00.000Z',
|
const userEntity: UserEntity = {
|
||||||
user: {
|
'@timestamp': '2021-08-02T14:00:00.000Z',
|
||||||
name: 'test_user',
|
user: {
|
||||||
},
|
name: 'test_user',
|
||||||
entity: {
|
},
|
||||||
name: 'test_user',
|
entity: {
|
||||||
source: ['logs-test'],
|
name: 'test_user',
|
||||||
},
|
source: 'logs-test',
|
||||||
};
|
},
|
||||||
|
};
|
||||||
|
|
||||||
expect(isUserEntity(userEntity)).toBe(true);
|
expect(isUserEntity(userEntity)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false if the record is not a UserEntity', () => {
|
||||||
|
const nonUserEntity: Entity = {
|
||||||
|
'@timestamp': '2021-08-02T14:00:00.000Z',
|
||||||
|
host: {
|
||||||
|
name: 'test_host',
|
||||||
|
},
|
||||||
|
entity: {
|
||||||
|
name: 'test_host',
|
||||||
|
source: 'logs-test',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(isUserEntity(nonUserEntity)).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false if the record is not a UserEntity', () => {
|
describe('sourceFieldToText', () => {
|
||||||
const nonUserEntity: Entity = {
|
it("should return 'Events' if the value isn't risk or asset", () => {
|
||||||
'@timestamp': '2021-08-02T14:00:00.000Z',
|
const { container } = render(sourceFieldToText('anything'), {
|
||||||
host: {
|
wrapper: TestProviders,
|
||||||
name: 'test_host',
|
});
|
||||||
},
|
|
||||||
entity: {
|
|
||||||
name: 'test_host',
|
|
||||||
source: ['logs-test'],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(isUserEntity(nonUserEntity)).toBe(false);
|
expect(container).toHaveTextContent('Events');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return 'Risk' if the value is a risk index", () => {
|
||||||
|
const { container } = render(sourceFieldToText('risk-score.risk-score-default'), {
|
||||||
|
wrapper: TestProviders,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(container).toHaveTextContent('Risk');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return 'Asset Criticality' if the value is a asset criticality index", () => {
|
||||||
|
const { container } = render(sourceFieldToText('.asset-criticality.asset-criticality-*'), {
|
||||||
|
wrapper: TestProviders,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(container).toHaveTextContent('Asset Criticality');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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 from 'react';
|
||||||
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
|
import {
|
||||||
|
ASSET_CRITICALITY_INDEX_PATTERN,
|
||||||
|
RISK_SCORE_INDEX_PATTERN,
|
||||||
|
} from '../../../../common/constants';
|
||||||
|
import type {
|
||||||
|
Entity,
|
||||||
|
UserEntity,
|
||||||
|
} from '../../../../common/api/entity_analytics/entity_store/entities/common.gen';
|
||||||
|
|
||||||
|
export const isUserEntity = (record: Entity): record is UserEntity =>
|
||||||
|
!!(record as UserEntity)?.user;
|
||||||
|
|
||||||
|
export const sourceFieldToText = (source: string) => {
|
||||||
|
if (source.match(`^${RISK_SCORE_INDEX_PATTERN}`)) {
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id="xpack.securitySolution.entityAnalytics.entityStore.helpers.sourceField.riskDescription"
|
||||||
|
defaultMessage="Risk"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source.match(`^${ASSET_CRITICALITY_INDEX_PATTERN}`)) {
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id="xpack.securitySolution.entityAnalytics.entityStore.helpers.sourceField.criticalityDescription"
|
||||||
|
defaultMessage="Asset Criticality"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id="xpack.securitySolution.entityAnalytics.entityStore.helpers.sourceField.eventDescription"
|
||||||
|
defaultMessage="Events"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
|
@ -17,9 +17,9 @@ import { RiskScoreLevel } from '../../severity/common';
|
||||||
import { getEmptyTagValue } from '../../../../common/components/empty_value';
|
import { getEmptyTagValue } from '../../../../common/components/empty_value';
|
||||||
import type { Columns } from '../../../../explore/components/paginated_table';
|
import type { Columns } from '../../../../explore/components/paginated_table';
|
||||||
import type { Entity } from '../../../../../common/api/entity_analytics/entity_store/entities/common.gen';
|
import type { Entity } from '../../../../../common/api/entity_analytics/entity_store/entities/common.gen';
|
||||||
import type { CriticalityLevels } from '../../../../../common/constants';
|
import { type CriticalityLevels } from '../../../../../common/constants';
|
||||||
import { ENTITIES_LIST_TABLE_ID } from '../constants';
|
import { ENTITIES_LIST_TABLE_ID } from '../constants';
|
||||||
import { isUserEntity } from '../helpers';
|
import { isUserEntity, sourceFieldToText } from '../helpers';
|
||||||
import { CRITICALITY_LEVEL_TITLE } from '../../asset_criticality/translations';
|
import { CRITICALITY_LEVEL_TITLE } from '../../asset_criticality/translations';
|
||||||
|
|
||||||
export type EntitiesListColumns = [
|
export type EntitiesListColumns = [
|
||||||
|
@ -110,7 +110,7 @@ export const useEntitiesListColumns = (): EntitiesListColumns => {
|
||||||
truncateText: { lines: 2 },
|
truncateText: { lines: 2 },
|
||||||
render: (source: string | undefined) => {
|
render: (source: string | undefined) => {
|
||||||
if (source != null) {
|
if (source != null) {
|
||||||
return <span>{source}</span>;
|
return sourceFieldToText(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
return getEmptyTagValue();
|
return getEmptyTagValue();
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { useGlobalFilterQuery } from '../../../../common/hooks/use_global_filter
|
||||||
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||||
import { CriticalityLevels } from '../../../../../common/constants';
|
import { CriticalityLevels } from '../../../../../common/constants';
|
||||||
import { RiskSeverity } from '../../../../../common/search_strategy';
|
import { RiskSeverity } from '../../../../../common/search_strategy';
|
||||||
import { EntitySource } from '../components/entity_source_filter';
|
import { EntitySourceTag } from '../types';
|
||||||
|
|
||||||
jest.mock('../../../../common/hooks/use_global_filter_query');
|
jest.mock('../../../../common/hooks/use_global_filter_query');
|
||||||
|
|
||||||
|
@ -52,7 +52,6 @@ describe('useEntitiesListFilters', () => {
|
||||||
{ term: { 'host.risk.calculated_level': RiskSeverity.High } },
|
{ term: { 'host.risk.calculated_level': RiskSeverity.High } },
|
||||||
{ term: { 'user.risk.calculated_level': RiskSeverity.High } },
|
{ term: { 'user.risk.calculated_level': RiskSeverity.High } },
|
||||||
],
|
],
|
||||||
minimum_should_match: 1,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -72,7 +71,6 @@ describe('useEntitiesListFilters', () => {
|
||||||
const expectedFilters: QueryDslQueryContainer[] = [
|
const expectedFilters: QueryDslQueryContainer[] = [
|
||||||
{
|
{
|
||||||
bool: {
|
bool: {
|
||||||
minimum_should_match: 1,
|
|
||||||
should: [
|
should: [
|
||||||
{
|
{
|
||||||
term: {
|
term: {
|
||||||
|
@ -97,13 +95,48 @@ describe('useEntitiesListFilters', () => {
|
||||||
useEntitiesListFilters({
|
useEntitiesListFilters({
|
||||||
selectedSeverities: [],
|
selectedSeverities: [],
|
||||||
selectedCriticalities: [],
|
selectedCriticalities: [],
|
||||||
selectedSources: [EntitySource.CSV_UPLOAD, EntitySource.EVENTS],
|
selectedSources: [EntitySourceTag.criticality, EntitySourceTag.risk],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const expectedFilters: QueryDslQueryContainer[] = [
|
const expectedFilters: QueryDslQueryContainer[] = [
|
||||||
{ term: { 'entity.source': EntitySource.CSV_UPLOAD } },
|
{
|
||||||
{ term: { 'entity.source': EntitySource.EVENTS } },
|
bool: {
|
||||||
|
should: [
|
||||||
|
{ wildcard: { 'entity.source': '.asset-criticality.asset-criticality-*' } },
|
||||||
|
{ wildcard: { 'entity.source': 'risk-score.risk-score-*' } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(result.current).toEqual(expectedFilters);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return source events filters when events is selected', () => {
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useEntitiesListFilters({
|
||||||
|
selectedSeverities: [],
|
||||||
|
selectedCriticalities: [],
|
||||||
|
selectedSources: [EntitySourceTag.events],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const expectedFilters: QueryDslQueryContainer[] = [
|
||||||
|
{
|
||||||
|
bool: {
|
||||||
|
should: [
|
||||||
|
{
|
||||||
|
bool: {
|
||||||
|
must_not: [
|
||||||
|
{ wildcard: { 'entity.source': '.asset-criticality.asset-criticality-*' } },
|
||||||
|
{ wildcard: { 'entity.source': 'risk-score.risk-score-*' } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
expect(result.current).toEqual(expectedFilters);
|
expect(result.current).toEqual(expectedFilters);
|
||||||
|
@ -132,7 +165,7 @@ describe('useEntitiesListFilters', () => {
|
||||||
useEntitiesListFilters({
|
useEntitiesListFilters({
|
||||||
selectedSeverities: [RiskSeverity.Low],
|
selectedSeverities: [RiskSeverity.Low],
|
||||||
selectedCriticalities: [CriticalityLevels.HIGH_IMPACT],
|
selectedCriticalities: [CriticalityLevels.HIGH_IMPACT],
|
||||||
selectedSources: [EntitySource.CSV_UPLOAD],
|
selectedSources: [EntitySourceTag.risk],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -143,16 +176,18 @@ describe('useEntitiesListFilters', () => {
|
||||||
{ term: { 'host.risk.calculated_level': RiskSeverity.Low } },
|
{ term: { 'host.risk.calculated_level': RiskSeverity.Low } },
|
||||||
{ term: { 'user.risk.calculated_level': RiskSeverity.Low } },
|
{ term: { 'user.risk.calculated_level': RiskSeverity.Low } },
|
||||||
],
|
],
|
||||||
minimum_should_match: 1,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
bool: {
|
bool: {
|
||||||
should: [{ term: { 'asset.criticality': CriticalityLevels.HIGH_IMPACT } }],
|
should: [{ term: { 'asset.criticality': CriticalityLevels.HIGH_IMPACT } }],
|
||||||
minimum_should_match: 1,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ term: { 'entity.source': EntitySource.CSV_UPLOAD } },
|
{
|
||||||
|
bool: {
|
||||||
|
should: [{ wildcard: { 'entity.source': 'risk-score.risk-score-*' } }],
|
||||||
|
},
|
||||||
|
},
|
||||||
globalQuery,
|
globalQuery,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -7,15 +7,19 @@
|
||||||
|
|
||||||
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import type { CriticalityLevels } from '../../../../../common/constants';
|
import {
|
||||||
|
ASSET_CRITICALITY_INDEX_PATTERN,
|
||||||
|
RISK_SCORE_INDEX_PATTERN,
|
||||||
|
type CriticalityLevels,
|
||||||
|
} from '../../../../../common/constants';
|
||||||
import type { RiskSeverity } from '../../../../../common/search_strategy';
|
import type { RiskSeverity } from '../../../../../common/search_strategy';
|
||||||
import { useGlobalFilterQuery } from '../../../../common/hooks/use_global_filter_query';
|
import { useGlobalFilterQuery } from '../../../../common/hooks/use_global_filter_query';
|
||||||
import type { EntitySource } from '../components/entity_source_filter';
|
import { EntitySourceTag } from '../types';
|
||||||
|
|
||||||
interface UseEntitiesListFiltersParams {
|
interface UseEntitiesListFiltersParams {
|
||||||
selectedSeverities: RiskSeverity[];
|
selectedSeverities: RiskSeverity[];
|
||||||
selectedCriticalities: CriticalityLevels[];
|
selectedCriticalities: CriticalityLevels[];
|
||||||
selectedSources: EntitySource[];
|
selectedSources: EntitySourceTag[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useEntitiesListFilters = ({
|
export const useEntitiesListFilters = ({
|
||||||
|
@ -35,17 +39,20 @@ export const useEntitiesListFilters = ({
|
||||||
'asset.criticality': value,
|
'asset.criticality': value,
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
minimum_should_match: 1,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const sourceFilter: QueryDslQueryContainer[] = selectedSources.map((value) => ({
|
const sourceFilter: QueryDslQueryContainer[] = selectedSources.length
|
||||||
term: {
|
? [
|
||||||
'entity.source': value,
|
{
|
||||||
},
|
bool: {
|
||||||
}));
|
should: selectedSources.map((tag) => getSourceTagFilterQuery(tag)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
|
||||||
const severityFilter: QueryDslQueryContainer[] = selectedSeverities.length
|
const severityFilter: QueryDslQueryContainer[] = selectedSeverities.length
|
||||||
? [
|
? [
|
||||||
|
@ -63,7 +70,6 @@ export const useEntitiesListFilters = ({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
minimum_should_match: 1,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -80,3 +86,37 @@ export const useEntitiesListFilters = ({
|
||||||
return filterList;
|
return filterList;
|
||||||
}, [globalQuery, selectedCriticalities, selectedSeverities, selectedSources]);
|
}, [globalQuery, selectedCriticalities, selectedSeverities, selectedSources]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getSourceTagFilterQuery = (tag: EntitySourceTag): QueryDslQueryContainer => {
|
||||||
|
if (tag === EntitySourceTag.risk) {
|
||||||
|
return {
|
||||||
|
wildcard: {
|
||||||
|
'entity.source': RISK_SCORE_INDEX_PATTERN,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (tag === EntitySourceTag.criticality) {
|
||||||
|
return {
|
||||||
|
wildcard: {
|
||||||
|
'entity.source': ASSET_CRITICALITY_INDEX_PATTERN,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
bool: {
|
||||||
|
must_not: [
|
||||||
|
{
|
||||||
|
wildcard: {
|
||||||
|
'entity.source': ASSET_CRITICALITY_INDEX_PATTERN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
wildcard: {
|
||||||
|
'entity.source': RISK_SCORE_INDEX_PATTERN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
@ -5,10 +5,8 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {
|
export enum EntitySourceTag {
|
||||||
Entity,
|
'risk' = 'Risk',
|
||||||
UserEntity,
|
'criticality' = 'Asset Criticality',
|
||||||
} from '../../../../common/api/entity_analytics/entity_store/entities/common.gen';
|
'events' = 'Events',
|
||||||
|
}
|
||||||
export const isUserEntity = (record: Entity): record is UserEntity =>
|
|
||||||
!!(record as UserEntity)?.user;
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import type { EntityType } from '../../../../../../common/api/entity_analytics/entity_store';
|
import type { EntityType } from '../../../../../../common/api/entity_analytics/entity_store';
|
||||||
import { getIdentityFieldForEntityType } from '../../utils';
|
import { getIdentityFieldForEntityType } from '../../utils';
|
||||||
import { collectValues, newestValue } from '../definition_utils';
|
import { oldestValue, newestValue } from '../definition_utils';
|
||||||
import type { UnitedDefinitionField } from '../types';
|
import type { UnitedDefinitionField } from '../types';
|
||||||
|
|
||||||
export const getCommonUnitedFieldDefinitions = ({
|
export const getCommonUnitedFieldDefinitions = ({
|
||||||
|
@ -19,10 +19,9 @@ export const getCommonUnitedFieldDefinitions = ({
|
||||||
}): UnitedDefinitionField[] => {
|
}): UnitedDefinitionField[] => {
|
||||||
const identityField = getIdentityFieldForEntityType(entityType);
|
const identityField = getIdentityFieldForEntityType(entityType);
|
||||||
return [
|
return [
|
||||||
collectValues({
|
oldestValue({
|
||||||
sourceField: '_index',
|
sourceField: '_index',
|
||||||
field: 'entity.source',
|
field: 'entity.source',
|
||||||
fieldHistoryLength,
|
|
||||||
}),
|
}),
|
||||||
newestValue({ field: 'asset.criticality' }),
|
newestValue({ field: 'asset.criticality' }),
|
||||||
newestValue({
|
newestValue({
|
||||||
|
|
|
@ -117,8 +117,7 @@ describe('getUnitedEntityDefinition', () => {
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"field": "entity.source",
|
"field": "entity.source",
|
||||||
"maxLength": 10,
|
"operation": "prefer_oldest_value",
|
||||||
"operation": "collect_values",
|
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"field": "asset.criticality",
|
"field": "asset.criticality",
|
||||||
|
@ -219,8 +218,10 @@ describe('getUnitedEntityDefinition', () => {
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"aggregation": Object {
|
"aggregation": Object {
|
||||||
"limit": 10,
|
"sort": Object {
|
||||||
"type": "terms",
|
"@timestamp": "asc",
|
||||||
|
},
|
||||||
|
"type": "top_value",
|
||||||
},
|
},
|
||||||
"destination": "entity.source",
|
"destination": "entity.source",
|
||||||
"source": "_index",
|
"source": "_index",
|
||||||
|
@ -373,8 +374,7 @@ describe('getUnitedEntityDefinition', () => {
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"field": "entity.source",
|
"field": "entity.source",
|
||||||
"maxLength": 10,
|
"operation": "prefer_oldest_value",
|
||||||
"operation": "collect_values",
|
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"field": "asset.criticality",
|
"field": "asset.criticality",
|
||||||
|
@ -467,8 +467,10 @@ describe('getUnitedEntityDefinition', () => {
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"aggregation": Object {
|
"aggregation": Object {
|
||||||
"limit": 10,
|
"sort": Object {
|
||||||
"type": "terms",
|
"@timestamp": "asc",
|
||||||
|
},
|
||||||
|
"type": "top_value",
|
||||||
},
|
},
|
||||||
"destination": "entity.source",
|
"destination": "entity.source",
|
||||||
"source": "_index",
|
"source": "_index",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue