Add 'Generic' Entity Engine Definition (#211232)

## Summary

Introduce a new Entity Engine Definition called Generic. The larger
context on why we are introducing a new entity definition is described
on this [private github
issue](https://github.com/elastic/security-team/issues/11857).

The tldr; is that we would like to have an entity store with all the
entities described by the [entity ecs
field](https://github.com/elastic/security-team/issues/11857). The
decision to call `generic` entity definition comes from the fact that
any entity can be described with the `entity` field - user, host,
service, database, queue, subscription and so on. Therefore it makes
sense to have the concept called `generic` entity, meanwhile the
existent entity definitions will be called concrete entities, because
they describe a very concrete type of entity (currently user, host,
service).

Other changes included on this PR:

- Don't override `entity.name` with `entity.id`, only set if no value is
found
- Migrate the usage of `entity.type` as the entity definition type to
`entity.EngineMetadata.Type`
- Changes touching Entity Analytics code around
`getRiskEngineEntityTypes` and `getAssetCriticalityEntityTypes`. There
was a somewhat unnecessary and duplicated logic in these functions which
essentially described the concrete entity definitions to be used by
entity analytics flows. A new function called
`getEntityAnalyticsEntityTypes` was introduced which unifies this logic
and returns the entity types that Entity Analytics care about.


Video of a scroll through the entities processed by the generic entity
store, source of the data is cloudbeat asset management integration.



https://github.com/user-attachments/assets/450afd05-dee0-4449-aaec-2cd69645d6ec

#### How to test:

- In Advanced Settings (`/app/management/kibana/settings`), enable
`securitySolution:enableAssetInventory`

<img width="883" alt="image"
src="https://github.com/user-attachments/assets/c342abb2-efb3-40a8-b945-d9558f085f34"
/>

- In Entity Store management (`/security/entity_analytics_entity_store`)
enable entity store
<img width="1251" alt="image"
src="https://github.com/user-attachments/assets/41f709e1-0aea-47dc-9c98-ffaebf18fdb1"
/>

- Verify Generic Engine Status
<img width="1203" alt="image"
src="https://github.com/user-attachments/assets/d26b764a-4695-436e-85f7-e3ed7df5a3be"
/>

- Ingest documents with `entity.id` and `entity.*` fields. Personally I
run `cloudbeat` asset discovery locally

- Verify ingested documents in
`.entities.v1.latest.security_generic_default`

<img width="1496" alt="image"
src="https://github.com/user-attachments/assets/88286cb9-38c1-4f9d-83a7-57ba33811c60"
/>

--

**OBS: Also test enabling the store without the uiSetting enabled, so
you can make sure that it doesn't enable**

### Checklist

Check the PR satisfies following conditions. 

Reviewers should verify this PR satisfies this list as well.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)
- [x]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [x] This was checked for breaking HTTP API changes, and any breaking
changes have been approved by the breaking-change committee. The
`release_note:breaking` label should be applied in these situations.
- [x] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

### Identify risks

Does this PR introduce any risks? For example, consider risks like hard
to test bugs, performance regression, potential of data loss.

Describe the risk, its severity, and mitigation for each identified
risk. Invite stakeholders and evaluate how to proceed before merging.

- [x] I see risk on performance, given the amount of aggregations the
generated transform does
- tested, although we see a higher spike in CPU than before, it's behind
a feature flag and it's going to be used in controlled data sets (entity
centric logs that contain `entity.id` field) we decided it's good enough
to go.
- [ ] Enablement/disablement of entity store in a different uiSetting
configuration.
- [ ] Enable entity store with `securitySolution:enableAssetInventory`
disabled. Then enable `securitySolution:enableAssetInventory` ==> No
generic entity definition installed. You can manually install it in the
EntityStore status page
- [ ] Enable entity store with `securitySolution:enableAssetInventory`
enabled. Then disable `securitySolution:enableAssetInventory` definition
==> hanging assets of generic entity store that can be deleted manually

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Rômulo Farias 2025-04-15 11:50:15 +02:00 committed by GitHub
parent 89d6dabfc2
commit b1ffcf3060
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
60 changed files with 808 additions and 198 deletions

View file

@ -62494,6 +62494,13 @@ components:
- indexPattern
- status
- fieldHistoryLength
Security_Entity_Analytics_API_EngineMetadata:
type: object
properties:
Type:
type: string
required:
- Type
Security_Entity_Analytics_API_EngineStatus:
enum:
- installing
@ -62507,6 +62514,7 @@ components:
- $ref: '#/components/schemas/Security_Entity_Analytics_API_UserEntity'
- $ref: '#/components/schemas/Security_Entity_Analytics_API_HostEntity'
- $ref: '#/components/schemas/Security_Entity_Analytics_API_ServiceEntity'
- $ref: '#/components/schemas/Security_Entity_Analytics_API_GenericEntity'
Security_Entity_Analytics_API_EntityRiskLevels:
enum:
- Unknown
@ -62589,7 +62597,42 @@ components:
- user
- host
- service
- generic
type: string
Security_Entity_Analytics_API_GenericEntity:
type: object
properties:
'@timestamp':
format: date-time
type: string
asset:
type: object
properties:
criticality:
$ref: '#/components/schemas/Security_Entity_Analytics_API_AssetCriticalityLevel'
required:
- criticality
entity:
type: object
properties:
category:
type: string
EngineMetadata:
$ref: '#/components/schemas/Security_Entity_Analytics_API_EngineMetadata'
id:
type: string
name:
type: string
source:
type: string
type:
type: string
required:
- id
- name
- type
required:
- entity
Security_Entity_Analytics_API_HostEntity:
type: object
properties:
@ -62606,13 +62649,18 @@ components:
entity:
type: object
properties:
EngineMetadata:
$ref: '#/components/schemas/Security_Entity_Analytics_API_EngineMetadata'
name:
type: string
source:
type: string
type:
type: string
required:
- name
- source
- type
event:
type: object
properties:
@ -62664,6 +62712,7 @@ components:
- host.name
- user.name
- service.name
- entity.id
type: string
Security_Entity_Analytics_API_IndexPattern:
type: string
@ -62765,13 +62814,18 @@ components:
entity:
type: object
properties:
EngineMetadata:
$ref: '#/components/schemas/Security_Entity_Analytics_API_EngineMetadata'
name:
type: string
source:
type: string
type:
type: string
required:
- name
- source
- type
event:
type: object
properties:
@ -62879,13 +62933,18 @@ components:
entity:
type: object
properties:
EngineMetadata:
$ref: '#/components/schemas/Security_Entity_Analytics_API_EngineMetadata'
name:
type: string
source:
type: string
type:
type: string
required:
- name
- source
- type
event:
type: object
properties:

View file

@ -71979,6 +71979,13 @@ components:
- indexPattern
- status
- fieldHistoryLength
Security_Entity_Analytics_API_EngineMetadata:
type: object
properties:
Type:
type: string
required:
- Type
Security_Entity_Analytics_API_EngineStatus:
enum:
- installing
@ -71992,6 +71999,7 @@ components:
- $ref: '#/components/schemas/Security_Entity_Analytics_API_UserEntity'
- $ref: '#/components/schemas/Security_Entity_Analytics_API_HostEntity'
- $ref: '#/components/schemas/Security_Entity_Analytics_API_ServiceEntity'
- $ref: '#/components/schemas/Security_Entity_Analytics_API_GenericEntity'
Security_Entity_Analytics_API_EntityRiskLevels:
enum:
- Unknown
@ -72074,7 +72082,42 @@ components:
- user
- host
- service
- generic
type: string
Security_Entity_Analytics_API_GenericEntity:
type: object
properties:
'@timestamp':
format: date-time
type: string
asset:
type: object
properties:
criticality:
$ref: '#/components/schemas/Security_Entity_Analytics_API_AssetCriticalityLevel'
required:
- criticality
entity:
type: object
properties:
category:
type: string
EngineMetadata:
$ref: '#/components/schemas/Security_Entity_Analytics_API_EngineMetadata'
id:
type: string
name:
type: string
source:
type: string
type:
type: string
required:
- id
- name
- type
required:
- entity
Security_Entity_Analytics_API_HostEntity:
type: object
properties:
@ -72091,13 +72134,18 @@ components:
entity:
type: object
properties:
EngineMetadata:
$ref: '#/components/schemas/Security_Entity_Analytics_API_EngineMetadata'
name:
type: string
source:
type: string
type:
type: string
required:
- name
- source
- type
event:
type: object
properties:
@ -72149,6 +72197,7 @@ components:
- host.name
- user.name
- service.name
- entity.id
type: string
Security_Entity_Analytics_API_IndexPattern:
type: string
@ -72250,13 +72299,18 @@ components:
entity:
type: object
properties:
EngineMetadata:
$ref: '#/components/schemas/Security_Entity_Analytics_API_EngineMetadata'
name:
type: string
source:
type: string
type:
type: string
required:
- name
- source
- type
event:
type: object
properties:
@ -72364,13 +72418,18 @@ components:
entity:
type: object
properties:
EngineMetadata:
$ref: '#/components/schemas/Security_Entity_Analytics_API_EngineMetadata'
name:
type: string
source:
type: string
type:
type: string
required:
- name
- source
- type
event:
type: object
properties:

View file

@ -21,6 +21,22 @@ const entity = {
},
};
const entityWithEngineMetadata = {
entity: {
last_seen_timestamp: '2024-08-06T17:03:50.722Z',
schema_version: 'v1',
definition_version: '999.999.999',
display_name: 'message_processor',
identity_fields: ['log.logger', 'event.category'],
id: '6UHVPiduEC2qk6rMjs1Jzg==',
metrics: {},
definition_id: 'admin-console-services',
EngineMetadata: {
Type: 'service',
},
},
};
const metadata = {
host: {
os: {
@ -68,5 +84,15 @@ describe('Entity Schemas', () => {
const result = entityLatestSchema.safeParse(doc);
expect(result).toHaveProperty('success', true);
});
it('should parse an entity with metadata and engine metadata', () => {
const doc = {
...entityWithEngineMetadata,
...metadata,
};
const result = entityLatestSchema.safeParse(doc);
expect(result).toHaveProperty('success', true);
});
});
});

View file

@ -10,13 +10,18 @@ import { arrayOfStringsSchema } from './common';
export const entityBaseSchema = z.object({
id: z.string(),
type: z.string(),
type: z.optional(z.string()),
identity_fields: z.union([arrayOfStringsSchema, z.string()]),
display_name: z.string(),
metrics: z.optional(z.record(z.string(), z.number())),
definition_version: z.string(),
schema_version: z.string(),
definition_id: z.string(),
EngineMetadata: z.optional(
z.object({
Type: z.string(),
})
),
});
export interface MetadataRecord {

View file

@ -10,7 +10,7 @@ Array [
},
Object {
"set": Object {
"field": "entity.type",
"field": "entity.EngineMetadata.Type",
"value": "service",
},
},
@ -115,7 +115,7 @@ Array [
},
Object {
"set": Object {
"field": "entity.type",
"field": "entity.EngineMetadata.Type",
"value": "service",
},
},

View file

@ -111,7 +111,7 @@ export function generateLatestProcessors(definition: EntityDefinition) {
},
{
set: {
field: 'entity.type',
field: 'entity.EngineMetadata.Type',
value: definition.type,
},
},

View file

@ -17,7 +17,7 @@
import { z } from '@kbn/zod';
export type IdField = z.infer<typeof IdField>;
export const IdField = z.enum(['host.name', 'user.name', 'service.name']);
export const IdField = z.enum(['host.name', 'user.name', 'service.name', 'entity.id']);
export type IdFieldEnum = typeof IdField.enum;
export const IdFieldEnum = IdField.enum;

View file

@ -29,6 +29,7 @@ components:
- 'host.name'
- 'user.name'
- 'service.name'
- 'entity.id'
AssetCriticalityRecordIdParts:
type: object
properties:

View file

@ -40,7 +40,7 @@ export const AfterKeys = z.object({
host: EntityAfterKey.optional(),
user: EntityAfterKey.optional(),
service: EntityAfterKey.optional(),
universal: EntityAfterKey.optional(),
generic: EntityAfterKey.optional(),
});
/**
@ -74,7 +74,7 @@ export const DateRange = z.object({
});
export type IdentifierType = z.infer<typeof IdentifierType>;
export const IdentifierType = z.enum(['host', 'user', 'service', 'universal']);
export const IdentifierType = z.enum(['host', 'user', 'service', 'generic']);
export type IdentifierTypeEnum = typeof IdentifierType.enum;
export const IdentifierTypeEnum = IdentifierType.enum;
@ -177,7 +177,7 @@ export const RiskScoreWeightInternal = z.union([
host: RiskScoreEntityIdentifierWeights,
user: RiskScoreEntityIdentifierWeights.optional(),
service: RiskScoreEntityIdentifierWeights.optional(),
universal: RiskScoreEntityIdentifierWeights.optional(),
generic: RiskScoreEntityIdentifierWeights.optional(),
})
),
RiskScoreWeightGlobalShared.merge(
@ -185,7 +185,7 @@ export const RiskScoreWeightInternal = z.union([
host: RiskScoreEntityIdentifierWeights.optional(),
user: RiskScoreEntityIdentifierWeights,
service: RiskScoreEntityIdentifierWeights.optional(),
universal: RiskScoreEntityIdentifierWeights.optional(),
generic: RiskScoreEntityIdentifierWeights.optional(),
})
),
RiskScoreWeightGlobalShared.merge(
@ -193,7 +193,7 @@ export const RiskScoreWeightInternal = z.union([
host: RiskScoreEntityIdentifierWeights.optional(),
user: RiskScoreEntityIdentifierWeights.optional(),
service: RiskScoreEntityIdentifierWeights,
universal: RiskScoreEntityIdentifierWeights.optional(),
generic: RiskScoreEntityIdentifierWeights.optional(),
})
),
]);

View file

@ -54,7 +54,7 @@ components:
$ref: '#/components/schemas/EntityAfterKey'
service:
$ref: '#/components/schemas/EntityAfterKey'
universal:
generic:
$ref: '#/components/schemas/EntityAfterKey'
example:
host:
@ -102,7 +102,7 @@ components:
- host
- user
- service
- universal
- generic
RiskScoreInput:
description: A generic representation of a document contributing to a Risk Score.
@ -258,7 +258,7 @@ components:
$ref: '#/components/schemas/RiskScoreEntityIdentifierWeights'
service:
$ref: '#/components/schemas/RiskScoreEntityIdentifierWeights'
universal:
generic:
$ref: '#/components/schemas/RiskScoreEntityIdentifierWeights'
- allOf:
@ -273,7 +273,7 @@ components:
$ref: '#/components/schemas/RiskScoreEntityIdentifierWeights'
service:
$ref: '#/components/schemas/RiskScoreEntityIdentifierWeights'
universal:
generic:
$ref: '#/components/schemas/RiskScoreEntityIdentifierWeights'
- allOf:
- $ref: '#/components/schemas/RiskScoreWeightGlobalShared'
@ -287,7 +287,7 @@ components:
$ref: '#/components/schemas/RiskScoreEntityIdentifierWeights'
service:
$ref: '#/components/schemas/RiskScoreEntityIdentifierWeights'
universal:
generic:
$ref: '#/components/schemas/RiskScoreEntityIdentifierWeights'
RiskScoreWeights:
description: 'A list of weights to be applied to the scoring calculation.'

View file

@ -17,7 +17,7 @@
import { z } from '@kbn/zod';
export type EntityType = z.infer<typeof EntityType>;
export const EntityType = z.enum(['user', 'host', 'service']);
export const EntityType = z.enum(['user', 'host', 'service', 'generic']);
export type EntityTypeEnum = typeof EntityType.enum;
export const EntityTypeEnum = EntityType.enum;

View file

@ -12,6 +12,7 @@ components:
- user
- host
- service
- generic
EngineDescriptor:
type: object

View file

@ -19,12 +19,19 @@ import { z } from '@kbn/zod';
import { EntityRiskScoreRecord } from '../../common/common.gen';
import { AssetCriticalityLevel } from '../../asset_criticality/common.gen';
export type EngineMetadata = z.infer<typeof EngineMetadata>;
export const EngineMetadata = z.object({
Type: z.string(),
});
export type UserEntity = z.infer<typeof UserEntity>;
export const UserEntity = z.object({
'@timestamp': z.string().datetime().optional(),
entity: z.object({
EngineMetadata: EngineMetadata.optional(),
name: z.string(),
source: z.string(),
type: z.string(),
}),
user: z.object({
full_name: z.array(z.string()).optional(),
@ -52,8 +59,10 @@ export type HostEntity = z.infer<typeof HostEntity>;
export const HostEntity = z.object({
'@timestamp': z.string().datetime().optional(),
entity: z.object({
EngineMetadata: EngineMetadata.optional(),
name: z.string(),
source: z.string(),
type: z.string(),
}),
host: z.object({
hostname: z.array(z.string()).optional(),
@ -82,8 +91,10 @@ export type ServiceEntity = z.infer<typeof ServiceEntity>;
export const ServiceEntity = z.object({
'@timestamp': z.string().datetime().optional(),
entity: z.object({
EngineMetadata: EngineMetadata.optional(),
name: z.string(),
source: z.string(),
type: z.string(),
}),
service: z.object({
name: z.string(),
@ -101,7 +112,25 @@ export const ServiceEntity = z.object({
.optional(),
});
export const EntityInternal = z.union([UserEntity, HostEntity, ServiceEntity]);
export type GenericEntity = z.infer<typeof GenericEntity>;
export const GenericEntity = z.object({
'@timestamp': z.string().datetime().optional(),
entity: z.object({
EngineMetadata: EngineMetadata.optional(),
id: z.string(),
name: z.string(),
type: z.string(),
category: z.string().optional(),
source: z.string().optional(),
}),
asset: z
.object({
criticality: AssetCriticalityLevel,
})
.optional(),
});
export const EntityInternal = z.union([UserEntity, HostEntity, ServiceEntity, GenericEntity]);
export type Entity = z.infer<typeof EntityInternal>;
export const Entity = EntityInternal as z.ZodType<Entity>;

View file

@ -6,6 +6,13 @@ info:
paths: {}
components:
schemas:
EngineMetadata:
type: object
required:
- Type
properties:
Type:
type: string
UserEntity:
type: object
required:
@ -20,11 +27,16 @@ components:
required:
- name
- source
- type
properties:
"EngineMetadata":
$ref: '#/components/schemas/EngineMetadata'
name:
type: string
source:
type: string
type:
type: string
user:
type: object
properties:
@ -85,11 +97,16 @@ components:
required:
- name
- source
- type
properties:
"EngineMetadata":
$ref: '#/components/schemas/EngineMetadata'
name:
type: string
source:
type: string
type:
type: string
host:
type: object
properties:
@ -154,11 +171,16 @@ components:
required:
- name
- source
- type
properties:
"EngineMetadata":
$ref: '#/components/schemas/EngineMetadata'
name:
type: string
source:
type: string
type:
type: string
service:
type: object
properties:
@ -181,8 +203,50 @@ components:
ingested:
type: string
format: date-time
# The Generic Entity definition maps more than just entity.
# however I don't see a reason to duplicate the definition
# of all the fields just for the sake of doing.
# Thus the current mapping maps entity and asset only
# (used in code). If you end up needing the fields mapped
# in the schema just add it.
GenericEntity:
type: object
required:
- entity
properties:
"@timestamp":
type: string
format: date-time
entity:
type: object
required:
- id
- name
- type
properties:
"EngineMetadata":
$ref: '#/components/schemas/EngineMetadata'
id:
type: string
name:
type: string
type:
type: string
category:
type: string
source:
type: string
asset:
type: object
properties:
criticality:
$ref: '../../asset_criticality/common.schema.yaml#/components/schemas/AssetCriticalityLevel'
required:
- criticality
Entity:
oneOf:
- $ref: '#/components/schemas/UserEntity'
- $ref: '#/components/schemas/HostEntity'
- $ref: '#/components/schemas/ServiceEntity'
- $ref: '#/components/schemas/GenericEntity'

View file

@ -47,9 +47,9 @@ export const RiskScoresCalculationResponse = z.object({
*/
service: z.array(EntityRiskScoreRecord).optional(),
/**
* A list of universal risk scores
* A list of generic risk scores
*/
universal: z.array(EntityRiskScoreRecord).optional(),
generic: z.array(EntityRiskScoreRecord).optional(),
/**
* If 'wait_for' the request will wait for the index refresh.
*/

View file

@ -45,11 +45,11 @@ components:
items:
$ref: '../common/common.schema.yaml#/components/schemas/EntityRiskScoreRecord'
description: A list of service risk scores
universal:
generic:
type: array
items:
$ref: '../common/common.schema.yaml#/components/schemas/EntityRiskScoreRecord'
description: A list of universal risk scores
description: A list of generic risk scores
refresh:
type: string
enum: [wait_for]

View file

@ -94,9 +94,9 @@ export const RiskScoresPreviewResponse = z.object({
*/
service: z.array(EntityRiskScoreRecord).optional(),
/**
* A list of universal risk scores
* A list of generic entities risk scores
*/
universal: z.array(EntityRiskScoreRecord).optional(),
generic: z.array(EntityRiskScoreRecord).optional(),
}),
});

View file

@ -106,8 +106,8 @@ components:
items:
$ref: '../common/common.schema.yaml#/components/schemas/EntityRiskScoreRecord'
description: A list of service risk scores
universal:
generic:
type: array
items:
$ref: '../common/common.schema.yaml#/components/schemas/EntityRiskScoreRecord'
description: A list of universal risk scores
description: A list of generic entities risk scores

View file

@ -5,13 +5,11 @@
* 2.0.
*/
import { mockGlobalState } from '../../../public/common/mock';
import { parseAssetCriticalityCsvRow } from './parse_asset_criticality_csv_row';
const experimentalFeatures = mockGlobalState.app.enableExperimental;
describe('parseAssetCriticalityCsvRow', () => {
it('should return valid false if the row has no columns', () => {
const result = parseAssetCriticalityCsvRow([], experimentalFeatures);
const result = parseAssetCriticalityCsvRow([]);
expect(result.valid).toBe(false);
// @ts-ignore result can now only be InvalidRecord
@ -19,7 +17,7 @@ describe('parseAssetCriticalityCsvRow', () => {
});
it('should return valid false if the row has 2 columns', () => {
const result = parseAssetCriticalityCsvRow(['host', 'host-1'], experimentalFeatures);
const result = parseAssetCriticalityCsvRow(['host', 'host-1']);
expect(result.valid).toBe(false);
// @ts-ignore result can now only be InvalidRecord
@ -27,10 +25,7 @@ describe('parseAssetCriticalityCsvRow', () => {
});
it('should return valid false if the row has 4 columns', () => {
const result = parseAssetCriticalityCsvRow(
['host', 'host-1', 'low_impact', 'extra'],
experimentalFeatures
);
const result = parseAssetCriticalityCsvRow(['host', 'host-1', 'low_impact', 'extra']);
expect(result.valid).toBe(false);
// @ts-ignore result can now only be InvalidRecord
@ -38,7 +33,7 @@ describe('parseAssetCriticalityCsvRow', () => {
});
it('should return valid false if the entity type is missing', () => {
const result = parseAssetCriticalityCsvRow(['', 'host-1', 'low_impact'], experimentalFeatures);
const result = parseAssetCriticalityCsvRow(['', 'host-1', 'low_impact']);
expect(result.valid).toBe(false);
// @ts-ignore result can now only be InvalidRecord
@ -46,10 +41,7 @@ describe('parseAssetCriticalityCsvRow', () => {
});
it('should return valid false if the entity type is invalid', () => {
const result = parseAssetCriticalityCsvRow(
['invalid', 'host-1', 'low_impact'],
experimentalFeatures
);
const result = parseAssetCriticalityCsvRow(['invalid', 'host-1', 'low_impact']);
expect(result.valid).toBe(false);
// @ts-ignore result can now only be InvalidRecord
@ -60,10 +52,7 @@ describe('parseAssetCriticalityCsvRow', () => {
it('should return valid false if the entity type is invalid and only log 1000 characters', () => {
const invalidEntityType = 'x'.repeat(1001);
const result = parseAssetCriticalityCsvRow(
[invalidEntityType, 'host-1', 'low_impact'],
experimentalFeatures
);
const result = parseAssetCriticalityCsvRow([invalidEntityType, 'host-1', 'low_impact']);
expect(result.valid).toBe(false);
// @ts-ignore result can now only be InvalidRecord
@ -73,7 +62,7 @@ describe('parseAssetCriticalityCsvRow', () => {
});
it('should return valid false if the ID is missing', () => {
const result = parseAssetCriticalityCsvRow(['host', '', 'low_impact'], experimentalFeatures);
const result = parseAssetCriticalityCsvRow(['host', '', 'low_impact']);
expect(result.valid).toBe(false);
// @ts-ignore result can now only be InvalidRecord
@ -81,7 +70,7 @@ describe('parseAssetCriticalityCsvRow', () => {
});
it('should return valid false if the criticality level is missing', () => {
const result = parseAssetCriticalityCsvRow(['host', 'host-1', ''], experimentalFeatures);
const result = parseAssetCriticalityCsvRow(['host', 'host-1', '']);
expect(result.valid).toBe(false);
// @ts-ignore result can now only be InvalidRecord
@ -89,10 +78,7 @@ describe('parseAssetCriticalityCsvRow', () => {
});
it('should return valid false if the criticality level is invalid', () => {
const result = parseAssetCriticalityCsvRow(
['host', 'host-1', 'unassigned_impact'],
experimentalFeatures
);
const result = parseAssetCriticalityCsvRow(['host', 'host-1', 'unassigned_impact']);
expect(result.valid).toBe(false);
// @ts-ignore result can now only be InvalidRecord
@ -103,10 +89,7 @@ describe('parseAssetCriticalityCsvRow', () => {
it('should return valid false if the criticality level is invalid and only log 1000 characters', () => {
const invalidCriticalityLevel = 'x'.repeat(1001);
const result = parseAssetCriticalityCsvRow(
['host', 'host-1', invalidCriticalityLevel],
experimentalFeatures
);
const result = parseAssetCriticalityCsvRow(['host', 'host-1', invalidCriticalityLevel]);
expect(result.valid).toBe(false);
// @ts-ignore result can now only be InvalidRecord
@ -117,10 +100,7 @@ describe('parseAssetCriticalityCsvRow', () => {
it('should return valid false if the ID is too long', () => {
const idValue = 'x'.repeat(1001);
const result = parseAssetCriticalityCsvRow(
['host', idValue, 'low_impact'],
experimentalFeatures
);
const result = parseAssetCriticalityCsvRow(['host', idValue, 'low_impact']);
expect(result.valid).toBe(false);
// @ts-ignore result can now only be InvalidRecord
@ -130,9 +110,7 @@ describe('parseAssetCriticalityCsvRow', () => {
});
it('should return the parsed row', () => {
expect(
parseAssetCriticalityCsvRow(['host', 'host-1', 'low_impact'], experimentalFeatures)
).toEqual({
expect(parseAssetCriticalityCsvRow(['host', 'host-1', 'low_impact'])).toEqual({
valid: true,
record: {
idField: 'host.name',
@ -143,9 +121,7 @@ describe('parseAssetCriticalityCsvRow', () => {
});
it('should return the parsed row if criticality level is the wrong case', () => {
expect(
parseAssetCriticalityCsvRow(['host', 'host-1', 'LOW_IMPACT'], experimentalFeatures)
).toEqual({
expect(parseAssetCriticalityCsvRow(['host', 'host-1', 'LOW_IMPACT'])).toEqual({
valid: true,
record: {
idField: 'host.name',
@ -155,9 +131,7 @@ describe('parseAssetCriticalityCsvRow', () => {
});
});
it('should return the parsed row if criticality level is UNASSIGNED', () => {
expect(
parseAssetCriticalityCsvRow(['host', 'host-1', 'UNASSIGNED'], experimentalFeatures)
).toEqual({
expect(parseAssetCriticalityCsvRow(['host', 'host-1', 'UNASSIGNED'])).toEqual({
valid: true,
record: {
idField: 'host.name',

View file

@ -9,8 +9,7 @@ import type { CriticalityLevels } from './constants';
import { ValidCriticalityLevels } from './constants';
import { type AssetCriticalityUpsertForBulkUpload, type CriticalityLevel } from './types';
import { EntityTypeToIdentifierField, type EntityType } from '../types';
import { getAssetCriticalityEntityTypes } from './utils';
import type { ExperimentalFeatures } from '../../experimental_features';
import { getEntityAnalyticsEntityTypes } from '../utils';
const MAX_COLUMN_CHARS = 1000;
@ -40,10 +39,7 @@ const trimColumn = (column: string): string => {
return column.length > MAX_COLUMN_CHARS ? `${column.substring(0, MAX_COLUMN_CHARS)}...` : column;
};
export const parseAssetCriticalityCsvRow = (
row: string[],
experimentalFeatures: ExperimentalFeatures
): ReturnType => {
export const parseAssetCriticalityCsvRow = (row: string[]): ReturnType => {
if (row.length !== 3) {
return validationErrorWithMessage(
i18n.translate('xpack.securitySolution.assetCriticality.csvUpload.expectedColumnsError', {
@ -104,7 +100,7 @@ export const parseAssetCriticalityCsvRow = (
);
}
const enabledEntityTypes = getAssetCriticalityEntityTypes(experimentalFeatures);
const enabledEntityTypes = getEntityAnalyticsEntityTypes();
if (!enabledEntityTypes.includes(entityType as EntityType)) {
return validationErrorWithMessage(
i18n.translate('xpack.securitySolution.assetCriticality.csvUpload.invalidEntityTypeError', {

View file

@ -1,17 +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 type { ExperimentalFeatures } from '../../experimental_features';
import { getAllEntityTypes, getDisabledEntityTypes } from '../utils';
// TODO delete this function when the universal entity support is added
export const getAssetCriticalityEntityTypes = (experimentalFeatures: ExperimentalFeatures) => {
const allEntityTypes = getAllEntityTypes();
const disabledEntityTypes = getDisabledEntityTypes(experimentalFeatures);
return allEntityTypes.filter((value) => !disabledEntityTypes.includes(value));
};

View file

@ -1,17 +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 type { ExperimentalFeatures } from '../../experimental_features';
import { getAllEntityTypes, getDisabledEntityTypes } from '../utils';
export const getEnabledStoreEntityTypes = (experimentalFeatures: ExperimentalFeatures) => {
const allEntityTypes = getAllEntityTypes();
const disabledEntityTypes = getDisabledEntityTypes(experimentalFeatures);
return allEntityTypes.filter((value) => !disabledEntityTypes.includes(value));
};

View file

@ -6,8 +6,6 @@
*/
import * as t from 'io-ts';
import { getAllEntityTypes, getDisabledEntityTypes } from '../utils';
import type { ExperimentalFeatures } from '../../experimental_features';
/*
* This utility function can be used to turn a TypeScript enum into a io-ts codec.
@ -26,11 +24,3 @@ export function fromEnum<EnumType extends string>(
t.identity
);
}
// TODO delete this function when the universal entity support is added
export const getRiskEngineEntityTypes = (experimentalFeatures: ExperimentalFeatures) => {
const allEntityTypes = getAllEntityTypes();
const disabledEntityTypes = getDisabledEntityTypes(experimentalFeatures);
return allEntityTypes.filter((value) => !disabledEntityTypes.includes(value));
};

View file

@ -15,16 +15,19 @@ export enum EntityType {
user = 'user',
host = 'host',
service = 'service',
generic = 'generic',
}
export enum EntityIdentifierFields {
hostName = 'host.name',
userName = 'user.name',
serviceName = 'service.name',
generic = 'entity.id',
}
export const EntityTypeToIdentifierField: Record<EntityType, EntityIdentifierFields> = {
[EntityType.host]: EntityIdentifierFields.hostName,
[EntityType.user]: EntityIdentifierFields.userName,
[EntityType.service]: EntityIdentifierFields.serviceName,
[EntityType.generic]: EntityIdentifierFields.generic,
};

View file

@ -4,14 +4,26 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { getAllEntityTypes } from './utils';
import { getEnabledEntityTypes, getEntityAnalyticsEntityTypes } from './utils';
import { EntityType } from './types';
describe('utils', () => {
describe('getAllEntityTypes', () => {
describe('getEntityAnalyticsEntityTypes', () => {
it('should return all Entity Analytics entity types', () => {
const entityTypes = getEntityAnalyticsEntityTypes();
expect(entityTypes).toEqual([EntityType.user, EntityType.host, EntityType.service]);
});
});
describe('getEnabledEntityTypes', () => {
it('should return all entity types', () => {
const entityTypes = getAllEntityTypes();
const entityTypes = getEnabledEntityTypes(true);
expect(entityTypes).toEqual(Object.values(EntityType));
});
it('should not return generic', () => {
const entityTypes = getEnabledEntityTypes(false);
expect(entityTypes).toEqual([EntityType.user, EntityType.host, EntityType.service]);
});
});
});

View file

@ -4,15 +4,22 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { ExperimentalFeatures } from '../experimental_features';
import { EntityType } from './types';
export const getAllEntityTypes = (): EntityType[] => Object.values(EntityType);
const ENTITY_ANALYTICS_ENTITY_TYPES = [EntityType.user, EntityType.host, EntityType.service];
export const getDisabledEntityTypes = (
experimentalFeatures: ExperimentalFeatures
): EntityType[] => {
const disabledEntityTypes: EntityType[] = [];
export const getEntityAnalyticsEntityTypes = (): EntityType[] => ENTITY_ANALYTICS_ENTITY_TYPES;
return disabledEntityTypes;
export const getEnabledEntityTypes = (genericDefinitionEnabled: boolean): EntityType[] => {
const entities = Object.values(EntityType);
if (genericDefinitionEnabled) {
return entities;
}
// Remove the index of generic
entities.splice(entities.indexOf(EntityType.generic), 1);
return entities;
};

View file

@ -93,10 +93,12 @@ export const EntityTypeToLevelField: Record<EntityType, RiskScoreFields> = {
[EntityType.host]: RiskScoreFields.hostRisk,
[EntityType.user]: RiskScoreFields.userRisk,
[EntityType.service]: RiskScoreFields.serviceRisk,
[EntityType.generic]: RiskScoreFields.unsupported, // We don't calculate risk for the generic entity
};
export const EntityTypeToScoreField: Record<EntityType, RiskScoreFields> = {
[EntityType.host]: RiskScoreFields.hostRiskScore,
[EntityType.user]: RiskScoreFields.userRiskScore,
[EntityType.service]: RiskScoreFields.serviceRiskScore,
[EntityType.generic]: RiskScoreFields.unsupported, // We don't calculate risk for the generic entity
};

View file

@ -1162,6 +1162,13 @@ components:
- indexPattern
- status
- fieldHistoryLength
EngineMetadata:
type: object
properties:
Type:
type: string
required:
- Type
EngineStatus:
enum:
- installing
@ -1175,6 +1182,7 @@ components:
- $ref: '#/components/schemas/UserEntity'
- $ref: '#/components/schemas/HostEntity'
- $ref: '#/components/schemas/ServiceEntity'
- $ref: '#/components/schemas/GenericEntity'
EntityRiskLevels:
enum:
- Unknown
@ -1269,7 +1277,42 @@ components:
- user
- host
- service
- generic
type: string
GenericEntity:
type: object
properties:
'@timestamp':
format: date-time
type: string
asset:
type: object
properties:
criticality:
$ref: '#/components/schemas/AssetCriticalityLevel'
required:
- criticality
entity:
type: object
properties:
category:
type: string
EngineMetadata:
$ref: '#/components/schemas/EngineMetadata'
id:
type: string
name:
type: string
source:
type: string
type:
type: string
required:
- id
- name
- type
required:
- entity
HostEntity:
type: object
properties:
@ -1286,13 +1329,18 @@ components:
entity:
type: object
properties:
EngineMetadata:
$ref: '#/components/schemas/EngineMetadata'
name:
type: string
source:
type: string
type:
type: string
required:
- name
- source
- type
event:
type: object
properties:
@ -1344,6 +1392,7 @@ components:
- host.name
- user.name
- service.name
- entity.id
type: string
IndexPattern:
type: string
@ -1448,13 +1497,18 @@ components:
entity:
type: object
properties:
EngineMetadata:
$ref: '#/components/schemas/EngineMetadata'
name:
type: string
source:
type: string
type:
type: string
required:
- name
- source
- type
event:
type: object
properties:
@ -1562,13 +1616,18 @@ components:
entity:
type: object
properties:
EngineMetadata:
$ref: '#/components/schemas/EngineMetadata'
name:
type: string
source:
type: string
type:
type: string
required:
- name
- source
- type
event:
type: object
properties:

View file

@ -1162,6 +1162,13 @@ components:
- indexPattern
- status
- fieldHistoryLength
EngineMetadata:
type: object
properties:
Type:
type: string
required:
- Type
EngineStatus:
enum:
- installing
@ -1175,6 +1182,7 @@ components:
- $ref: '#/components/schemas/UserEntity'
- $ref: '#/components/schemas/HostEntity'
- $ref: '#/components/schemas/ServiceEntity'
- $ref: '#/components/schemas/GenericEntity'
EntityRiskLevels:
enum:
- Unknown
@ -1269,7 +1277,42 @@ components:
- user
- host
- service
- generic
type: string
GenericEntity:
type: object
properties:
'@timestamp':
format: date-time
type: string
asset:
type: object
properties:
criticality:
$ref: '#/components/schemas/AssetCriticalityLevel'
required:
- criticality
entity:
type: object
properties:
category:
type: string
EngineMetadata:
$ref: '#/components/schemas/EngineMetadata'
id:
type: string
name:
type: string
source:
type: string
type:
type: string
required:
- id
- name
- type
required:
- entity
HostEntity:
type: object
properties:
@ -1286,13 +1329,18 @@ components:
entity:
type: object
properties:
EngineMetadata:
$ref: '#/components/schemas/EngineMetadata'
name:
type: string
source:
type: string
type:
type: string
required:
- name
- source
- type
event:
type: object
properties:
@ -1344,6 +1392,7 @@ components:
- host.name
- user.name
- service.name
- entity.id
type: string
IndexPattern:
type: string
@ -1448,13 +1497,18 @@ components:
entity:
type: object
properties:
EngineMetadata:
$ref: '#/components/schemas/EngineMetadata'
name:
type: string
source:
type: string
type:
type: string
required:
- name
- source
- type
event:
type: object
properties:
@ -1562,13 +1616,18 @@ components:
entity:
type: object
properties:
EngineMetadata:
$ref: '#/components/schemas/EngineMetadata'
name:
type: string
source:
type: string
type:
type: string
required:
- name
- source
- type
event:
type: object
properties:

View file

@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage, useI18n } from '@kbn/i18n-react';
import type { EntityType } from '../../../../../common/entity_analytics/types';
import { useAssetCriticalityEntityTypes } from '../../../hooks/use_enabled_entity_types';
import { useEntityAnalyticsTypes } from '../../../hooks/use_enabled_entity_types';
import { EntityTypeToIdentifierField } from '../../../../../common/entity_analytics/types';
import {
@ -61,7 +61,7 @@ export const AssetCriticalityFilePickerStep: React.FC<AssetCriticalityFilePicker
line-height: ${useEuiFontSize('s').lineHeight};
`;
const entityTypes = useAssetCriticalityEntityTypes();
const entityTypes = useEntityAnalyticsTypes();
const sampleCSVContent = entityTypes
.filter((entity): entity is SupportedEntityType => entity in entityCSVMap)

View file

@ -34,7 +34,7 @@ export const validateParsedContent = (
errors: RowValidationErrors[];
}>(
(acc, row) => {
const parsedRow = parseAssetCriticalityCsvRow(row, experimentalFeatures);
const parsedRow = parseAssetCriticalityCsvRow(row);
if (parsedRow.valid) {
acc.valid.push(row);
} else {

View file

@ -38,7 +38,7 @@ import {
import type { Enablements } from './enablement_modal';
import { EntityStoreEnablementModal } from './enablement_modal';
import dashboardEnableImg from '../../../images/entity_store_dashboard.png';
import { useStoreEntityTypes } from '../../../hooks/use_enabled_entity_types';
import { useEntityStoreTypes } from '../../../hooks/use_enabled_entity_types';
interface EnableEntityStorePanelProps {
state: {
@ -51,7 +51,7 @@ export const EnablementPanel: React.FC<EnableEntityStorePanelProps> = ({ state }
const riskEngineStatus = state.riskEngine.data?.risk_engine_status;
const entityStoreStatus = state.entityStore.data?.status;
const engines = state.entityStore.data?.engines;
const enabledEntityTypes = useStoreEntityTypes();
const enabledEntityTypes = useEntityStoreTypes();
const [modal, setModalState] = useState({ visible: false });
const [riskEngineInitializing, setRiskEngineInitializing] = useState(false);

View file

@ -12,7 +12,7 @@ import {
EuiFlexGroup,
EuiPanel,
} from '@elastic/eui';
import { useStoreEntityTypes } from '../../../hooks/use_enabled_entity_types';
import { useEntityAnalyticsTypes } from '../../../hooks/use_enabled_entity_types';
import { RiskEngineStatusEnum } from '../../../../../common/api/entity_analytics';
import { EntitiesList } from '../entities_list';
import { useEntityStoreStatus } from '../hooks/use_entity_store';
@ -25,7 +25,7 @@ import { EntityStoreErrorCallout } from './entity_store_error_callout';
const EntityStoreDashboardPanelsComponent = () => {
const riskEngineStatus = useRiskEngineStatus();
const storeStatusQuery = useEntityStoreStatus({});
const entityTypes = useStoreEntityTypes();
const entityTypes = useEntityAnalyticsTypes();
const callouts = (storeStatusQuery.data?.engines ?? [])
.filter((engine) => engine.status === 'error')

View file

@ -17,7 +17,7 @@ import {
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { useStoreEntityTypes } from '../../../../hooks/use_enabled_entity_types';
import { useEntityStoreTypes } from '../../../../hooks/use_enabled_entity_types';
import { useErrorToast } from '../../../../../common/hooks/use_error_toast';
import { downloadBlob } from '../../../../../common/utils/download_blob';
import { EngineComponentsStatusTable } from './components/engine_components_status';
@ -42,7 +42,7 @@ export const EngineStatus: React.FC = () => {
isLoading: isStatusAPILoading,
error,
} = useEntityStoreStatus({ withComponents: true });
const enabledEntityTypes = useStoreEntityTypes();
const enabledEntityTypes = useEntityStoreTypes();
const downloadJson = () => {
downloadBlob(new Blob([JSON.stringify(data)]), FILE_NAME);

View file

@ -36,6 +36,7 @@ const responseData: ListEntitiesResponse = {
entity: {
name: `Entity Name ${index}`,
source: 'test-index',
type: 'user',
},
}),
10

View file

@ -26,7 +26,7 @@ import { useEntitiesListQuery } from './hooks/use_entities_list_query';
import { ENTITIES_LIST_TABLE_ID, rowItems } from './constants';
import { useEntitiesListColumns } from './hooks/use_entities_list_columns';
import type { EntitySourceTag } from './types';
import { useStoreEntityTypes } from '../../hooks/use_enabled_entity_types';
import { useEntityStoreTypes } from '../../hooks/use_enabled_entity_types';
export const EntitiesList: React.FC = () => {
const { deleteQuery, setQuery, isInitializing, from, to } = useGlobalTime();
@ -37,7 +37,7 @@ export const EntitiesList: React.FC = () => {
field: '@timestamp',
direction: Direction.desc,
});
const entityTypes = useStoreEntityTypes();
const entityTypes = useEntityStoreTypes();
const [selectedSeverities, setSelectedSeverities] = useState<RiskSeverity[]>([]);
const [selectedCriticalities, setSelectedCriticalities] = useState<CriticalityLevels[]>([]);
const [selectedSources, setSelectedSources] = useState<EntitySourceTag[]>([]);

View file

@ -13,6 +13,7 @@ import type {
HostEntity,
ServiceEntity,
UserEntity,
GenericEntity,
} from '../../../../common/api/entity_analytics';
describe('helpers', () => {
@ -26,6 +27,26 @@ describe('helpers', () => {
entity: {
name: 'test_user',
source: 'logs-test',
type: 'AWS IAM User',
EngineMetadata: {
Type: 'user',
},
},
};
expect(getEntityType(userEntity)).toBe('user');
});
it('should return "user" if the record is a UserEntity (with user.entity)', () => {
const userEntity: UserEntity = {
'@timestamp': '2021-08-02T14:00:00.000Z',
user: {
name: 'test_user',
},
entity: {
name: 'test_user',
source: 'logs-test',
type: 'user',
},
};
@ -41,11 +62,32 @@ describe('helpers', () => {
entity: {
name: 'test_host',
source: 'logs-test',
type: 'EC2 Instance',
EngineMetadata: {
Type: 'host',
},
},
};
expect(getEntityType(hostEntity)).toBe('host');
});
it('should return "host" if the record is a HostEntity (with entity.type)', () => {
const hostEntity: HostEntity = {
'@timestamp': '2021-08-02T14:00:00.000Z',
host: {
name: 'test_host',
},
entity: {
name: 'test_host',
source: 'logs-test',
type: 'host',
},
};
expect(getEntityType(hostEntity)).toBe('host');
});
it('should return "service" if the record is a ServiceEntity', () => {
const serviceEntity: ServiceEntity = {
'@timestamp': '2021-08-02T14:00:00.000Z',
@ -55,12 +97,63 @@ describe('helpers', () => {
entity: {
name: 'test_service',
source: 'logs-test',
type: 'SaaS',
EngineMetadata: {
Type: 'service',
},
},
};
expect(getEntityType(serviceEntity)).toBe('service');
});
it('should return "service" if the record is a ServiceEntity (with entity.type)', () => {
const serviceEntity: ServiceEntity = {
'@timestamp': '2021-08-02T14:00:00.000Z',
service: {
name: 'test_service',
},
entity: {
name: 'test_service',
source: 'logs-test',
type: 'service',
},
};
expect(getEntityType(serviceEntity)).toBe('service');
});
it('should return "generic" if the record is a ServiceEntity', () => {
const genericEntity: GenericEntity = {
'@timestamp': '2021-08-02T14:00:00.000Z',
entity: {
id: 'arn',
name: 'test_generic',
source: 'logs-test',
type: 'PostgreSQL Database',
EngineMetadata: {
Type: 'generic',
},
},
};
expect(getEntityType(genericEntity)).toBe('generic');
});
it('should return "generic" if the record is a ServiceEntity (with entity.type)', () => {
const genericEntity: GenericEntity = {
'@timestamp': '2021-08-02T14:00:00.000Z',
entity: {
id: 'arn',
name: 'test_generic',
source: 'logs-test',
type: 'generic',
},
};
expect(getEntityType(genericEntity)).toBe('generic');
});
it('should throw an error if the record does not match any entity type', () => {
const unknownEntity = {
'@timestamp': '2021-08-02T14:00:00.000Z',

View file

@ -7,8 +7,6 @@
import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import type { IconType } from '@elastic/eui';
import { get } from 'lodash/fp';
import { getAllEntityTypes } from '../../../../common/entity_analytics/utils';
import { EntityType } from '../../../../common/entity_analytics/types';
import {
@ -18,23 +16,21 @@ import {
import type { Entity } from '../../../../common/api/entity_analytics/entity_store/entities/common.gen';
export const getEntityType = (record: Entity): EntityType => {
const allEntityTypes = getAllEntityTypes();
// Looking at `entity.type` to keep backward compatibility
const entityType = record.entity.EngineMetadata?.Type || record.entity.type;
const entityType = allEntityTypes.find((type) => {
return get(type, record) !== undefined;
});
if (!entityType) {
if (Object.keys(EntityType).indexOf(entityType) < 0) {
throw new Error(`Unexpected entity: ${JSON.stringify(record)}`);
}
return entityType;
return EntityType[entityType as keyof typeof EntityType];
};
export const EntityIconByType: Record<EntityType, IconType> = {
[EntityType.user]: 'user',
[EntityType.host]: 'storage',
[EntityType.service]: 'node',
[EntityType.generic]: 'globe',
};
export const sourceFieldToText = (source: string) => {

View file

@ -18,6 +18,17 @@ const mockedExperimentalFeatures = mockGlobalState.app.enableExperimental;
jest.mock('../../../../common/hooks/use_experimental_features', () => ({
useEnableExperimental: () => ({ ...mockedExperimentalFeatures }),
}));
const mockUseUiSettings = jest.fn().mockReturnValue([true]);
jest.mock('@kbn/kibana-react-plugin/public', () => {
const original = jest.requireActual('@kbn/kibana-react-plugin/public');
return {
...original,
useUiSetting$: () => mockUseUiSettings(),
};
});
jest.mock('../../../../common/hooks/use_global_filter_query');
describe('useEntitiesListFilters', () => {

View file

@ -12,10 +12,14 @@ import {
RISK_SCORE_INDEX_PATTERN,
type CriticalityLevels,
} from '../../../../../common/constants';
import { EntityTypeToLevelField, type RiskSeverity } from '../../../../../common/search_strategy';
import {
EntityTypeToLevelField,
RiskScoreFields,
type RiskSeverity,
} from '../../../../../common/search_strategy';
import { useGlobalFilterQuery } from '../../../../common/hooks/use_global_filter_query';
import { EntitySourceTag } from '../types';
import { useStoreEntityTypes } from '../../../hooks/use_enabled_entity_types';
import { useEntityStoreTypes } from '../../../hooks/use_enabled_entity_types';
interface UseEntitiesListFiltersParams {
selectedSeverities: RiskSeverity[];
@ -29,7 +33,7 @@ export const useEntitiesListFilters = ({
selectedSources,
}: UseEntitiesListFiltersParams) => {
const { filterQuery: globalQuery } = useGlobalFilterQuery();
const enabledEntityTypes = useStoreEntityTypes();
const enabledEntityTypes = useEntityStoreTypes();
return useMemo(() => {
const criticalityFilter: QueryDslQueryContainer[] = selectedCriticalities.length
@ -60,11 +64,15 @@ export const useEntitiesListFilters = ({
? [
{
bool: {
should: enabledEntityTypes.map((type) => ({
terms: {
[EntityTypeToLevelField[type]]: selectedSeverities,
},
})),
should: enabledEntityTypes
.filter((type) => {
return EntityTypeToLevelField[type] !== RiskScoreFields.unsupported;
})
.map((type) => ({
terms: {
[EntityTypeToLevelField[type]]: selectedSeverities,
},
})),
},
},
]

View file

@ -34,7 +34,7 @@ import { useSourcererDataView } from '../../sourcerer/containers';
import type { RiskEngineMissingPrivilegesResponse } from '../hooks/use_missing_risk_engine_privileges';
import { userHasRiskEngineReadPermissions } from '../common';
import { EntityIconByType } from './entity_store/helpers';
import { useRiskEngineEntityTypes } from '../hooks/use_enabled_entity_types';
import { useEntityAnalyticsTypes } from '../hooks/use_enabled_entity_types';
interface IRiskScorePreviewPanel {
showMessage: React.ReactNode;
hideMessage: React.ReactNode;
@ -142,7 +142,7 @@ const RiskEnginePreview: React.FC<{ includeClosedAlerts: boolean; from: string;
from,
to,
}) => {
const entityTypes = useRiskEngineEntityTypes();
const entityTypes = useEntityAnalyticsTypes();
const [filters] = useState<{ bool: BoolQuery }>({
bool: { must: [], filter: [], should: [], must_not: [] },

View file

@ -6,28 +6,24 @@
*/
import { useMemo } from 'react';
import { getAssetCriticalityEntityTypes } from '../../../common/entity_analytics/asset_criticality/utils';
import { getRiskEngineEntityTypes } from '../../../common/entity_analytics/risk_engine/utils';
import { getEnabledStoreEntityTypes } from '../../../common/entity_analytics/entity_store/utils';
import { useEnableExperimental } from '../../common/hooks/use_experimental_features';
import { useUiSetting$ } from '@kbn/kibana-react-plugin/public';
import { SECURITY_SOLUTION_ENABLE_ASSET_INVENTORY_SETTING } from '@kbn/management-settings-ids';
import {
getEntityAnalyticsEntityTypes,
getEnabledEntityTypes,
} from '../../../common/entity_analytics/utils';
export const useStoreEntityTypes = () => {
const experimentalFeatures = useEnableExperimental();
return useMemo(() => getEnabledStoreEntityTypes(experimentalFeatures), [experimentalFeatures]);
};
export const useRiskEngineEntityTypes = () => {
const experimentalFeatures = useEnableExperimental();
return useMemo(() => getRiskEngineEntityTypes(experimentalFeatures), [experimentalFeatures]);
};
export const useAssetCriticalityEntityTypes = () => {
const experimentalFeatures = useEnableExperimental();
export const useEntityStoreTypes = () => {
const [genericEntityStoreEnabled] = useUiSetting$<boolean>(
SECURITY_SOLUTION_ENABLE_ASSET_INVENTORY_SETTING
);
return useMemo(
() => getAssetCriticalityEntityTypes(experimentalFeatures),
[experimentalFeatures]
() => getEnabledEntityTypes(genericEntityStoreEnabled),
[genericEntityStoreEnabled]
);
};
export const useEntityAnalyticsTypes = () => {
return useMemo(() => getEntityAnalyticsEntityTypes(), []);
};

View file

@ -22,7 +22,7 @@ import { EntityAnalyticsAnomalies } from '../components/entity_analytics_anomali
import { EntityStoreDashboardPanels } from '../components/entity_store/components/dashboard_entity_store_panels';
import { EntityAnalyticsRiskScores } from '../components/entity_analytics_risk_score';
import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features';
import { useStoreEntityTypes } from '../hooks/use_enabled_entity_types';
import { useEntityAnalyticsTypes } from '../hooks/use_enabled_entity_types';
const EntityAnalyticsComponent = () => {
const [skipEmptyPrompt, setSkipEmptyPrompt] = React.useState(false);
@ -30,7 +30,7 @@ const EntityAnalyticsComponent = () => {
const { indicesExist, loading: isSourcererLoading, sourcererDataView } = useSourcererDataView();
const isEntityStoreFeatureFlagDisabled = useIsExperimentalFeatureEnabled('entityStoreDisabled');
const showEmptyPrompt = !indicesExist && !skipEmptyPrompt;
const entityTypes = useStoreEntityTypes();
const entityTypes = useEntityAnalyticsTypes();
return (
<>

View file

@ -49,7 +49,7 @@ import {
import { useEntityEnginePrivileges } from '../components/entity_store/hooks/use_entity_engine_privileges';
import { MissingPrivilegesCallout } from '../components/entity_store/components/missing_privileges_callout';
import { EngineStatus } from '../components/entity_store/components/engines_status';
import { useStoreEntityTypes } from '../hooks/use_enabled_entity_types';
import { useEntityStoreTypes } from '../hooks/use_enabled_entity_types';
import { EntityStoreErrorCallout } from '../components/entity_store/components/entity_store_error_callout';
enum TabId {
@ -82,7 +82,7 @@ export const EntityStoreManagementPage = () => {
const hasAssetCriticalityWritePermissions = assetCriticalityPrivileges?.has_write_permissions;
const [selectedTabId, setSelectedTabId] = useState(TabId.Import);
const entityStoreStatus = useEntityStoreStatus({});
const entityTypes = useStoreEntityTypes();
const entityTypes = useEntityStoreTypes();
const enableStoreMutation = useEnableEntityStoreMutation();
const stopEntityEngineMutation = useStopEntityEngineMutation(entityTypes);
const deleteEntityEngineMutation = useDeleteEntityEngineMutation({

View file

@ -35,6 +35,7 @@ export const EntityPanelKeyByType: Record<EntityType, string | undefined> = {
[EntityType.host]: HostPanelKey,
[EntityType.user]: UserPanelKey,
[EntityType.service]: ServicePanelKey,
[EntityType.generic]: undefined, // TODO create generic flyout?
};
// TODO rename all params and merged them as 'entityName'
@ -42,4 +43,5 @@ export const EntityPanelParamByType: Record<EntityType, string | undefined> = {
[EntityType.host]: 'hostName',
[EntityType.user]: 'userName',
[EntityType.service]: 'serviceName',
[EntityType.generic]: undefined, // TODO create generic flyout?
};

View file

@ -82,6 +82,7 @@ const entityTypeByIdField = {
'host.name': 'host',
'user.name': 'user',
'service.name': 'service',
'entity.id': 'generic',
} as const;
export const getImplicitEntityFields = (record: AssetCriticalityUpsertWithDeleted) => {

View file

@ -28,7 +28,7 @@ class TransformCSVToUpsertRecords extends Transform {
callback: (error: Error | null, data?: AssetCriticalityUpsertForBulkUpload | Error) => void
) {
try {
const parseResult = parseAssetCriticalityCsvRow(chunk, this.experimentalFeatures);
const parseResult = parseAssetCriticalityCsvRow(chunk);
if (isErrorResult(parseResult)) {
return callback(null, new Error(parseResult.error));
} else {

View file

@ -66,6 +66,7 @@ const buildIngestPipeline = ({
{
set: {
field: 'entity.name',
override: false,
value: `{{${description.identityField}}}`,
},
},

View file

@ -10,12 +10,14 @@ import {
HOST_DEFINITION_VERSION,
USER_DEFINITION_VERSION,
SERVICE_DEFINITION_VERSION,
GENERIC_DEFINITION_VERSION,
} from './entity_descriptions';
export const VERSIONS_BY_ENTITY_TYPE: Record<EntityType, string> = {
host: HOST_DEFINITION_VERSION,
user: USER_DEFINITION_VERSION,
service: SERVICE_DEFINITION_VERSION,
generic: GENERIC_DEFINITION_VERSION,
};
export const DEFAULT_FIELD_HISTORY_LENGTH = 10;

View file

@ -0,0 +1,111 @@
/*
* 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 { newestValue } from './field_utils';
import type { EntityDescription } from '../types';
import { getCommonFieldDescriptions } from './common';
export const GENERIC_DEFINITION_VERSION = '1.0.0';
export const GENERIC_IDENTITY_FIELD = 'entity.id';
export const genericEntityEngineDescription: EntityDescription = {
entityType: 'generic',
version: GENERIC_DEFINITION_VERSION,
identityField: GENERIC_IDENTITY_FIELD,
settings: {
timestampField: '@timestamp',
},
fields: [
newestValue({ source: 'entity.name' }),
newestValue({ source: 'entity.source' }),
newestValue({ source: 'entity.type' }),
newestValue({ source: 'entity.sub_type' }),
newestValue({ source: 'entity.url' }),
newestValue({ source: 'cloud.account.id' }),
newestValue({ source: 'cloud.account.name' }),
newestValue({ source: 'cloud.availability_zone' }),
newestValue({ source: 'cloud.instance.id' }),
newestValue({ source: 'cloud.instance.name' }),
newestValue({ source: 'cloud.machine.type' }),
newestValue({ source: 'cloud.project.id' }),
newestValue({ source: 'cloud.project.name' }),
newestValue({ source: 'cloud.provider' }),
newestValue({ source: 'cloud.region' }),
newestValue({ source: 'cloud.service.name' }),
newestValue({ source: 'host.architecture' }),
newestValue({ source: 'host.boot.id' }),
newestValue({ source: 'host.cpu.usage' }),
newestValue({ source: 'host.disk.read.bytes' }),
newestValue({ source: 'host.disk.write.bytes' }),
newestValue({ source: 'host.domain' }),
newestValue({ source: 'host.hostname' }),
newestValue({ source: 'host.id' }),
newestValue({ source: 'host.mac' }),
newestValue({ source: 'host.name' }),
newestValue({ source: 'host.network.egress.bytes' }),
newestValue({ source: 'host.network.egress.packets' }),
newestValue({ source: 'host.network.ingress.bytes' }),
newestValue({ source: 'host.network.ingress.packets' }),
newestValue({ source: 'host.pid_ns_ino' }),
newestValue({ source: 'host.type' }),
newestValue({ source: 'host.uptime' }),
newestValue({
source: 'host.ip',
mapping: {
type: 'ip',
},
}),
newestValue({ source: 'user.domain' }),
newestValue({ source: 'user.email' }),
newestValue({ source: 'user.full_name' }),
newestValue({ source: 'user.roles' }),
newestValue({ source: 'user.hash' }),
newestValue({ source: 'user.id' }),
newestValue({
source: 'user.name',
mapping: {
type: 'keyword',
fields: {
text: {
type: 'match_only_text',
},
},
},
}),
newestValue({
source: 'user.full_name',
mapping: {
type: 'keyword',
fields: {
text: {
type: 'match_only_text',
},
},
},
}),
newestValue({ source: 'orchestrator.api_version' }),
newestValue({ source: 'orchestrator.cluster.id' }),
newestValue({ source: 'orchestrator.cluster.name' }),
newestValue({ source: 'orchestrator.cluster.url' }),
newestValue({ source: 'orchestrator.cluster.version' }),
newestValue({ source: 'orchestrator.namespace' }),
newestValue({ source: 'orchestrator.organization' }),
newestValue({ source: 'orchestrator.resource.annotation' }),
newestValue({ source: 'orchestrator.resource.id' }),
newestValue({ source: 'orchestrator.resource.ip' }),
newestValue({ source: 'orchestrator.resource.label' }),
newestValue({ source: 'orchestrator.resource.name' }),
newestValue({ source: 'orchestrator.resource.parent.type' }),
newestValue({ source: 'orchestrator.resource.type' }),
newestValue({ source: 'orchestrator.type' }),
...getCommonFieldDescriptions('generic'),
],
};

View file

@ -8,4 +8,6 @@
export * from './host';
export * from './user';
export * from './service';
export * from './generic';
export { getCommonFieldDescriptions } from './common';

View file

@ -26,7 +26,7 @@ import type {
import type { TaskManagerStartContract } from '@kbn/task-manager-plugin/server';
import { defaultOptions } from './constants';
import type { SecurityPluginStart } from '@kbn/security-plugin/server';
import type { KibanaRequest } from '@kbn/core/server';
import type { IUiSettingsClient, KibanaRequest } from '@kbn/core/server';
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
import { createStubDataView } from '@kbn/data-views-plugin/common/mocks';
@ -117,6 +117,11 @@ describe('EntityStoreDataClient', () => {
const clusterClientMock = elasticsearchServiceMock.createScopedClusterClient();
const esClientMock = clusterClientMock.asCurrentUser;
const loggerMock = loggingSystemMock.createLogger();
const uiSettingsClientGetMock = jest.fn();
const uiSettingsClientMock = {
get: uiSettingsClientGetMock,
} as unknown as IUiSettingsClient;
const dataClient = new EntityStoreDataClient({
clusterClient: clusterClientMock,
logger: loggerMock,
@ -137,6 +142,7 @@ describe('EntityStoreDataClient', () => {
},
} as unknown as SecurityPluginStart,
request: {} as KibanaRequest,
uiSettingsClient: uiSettingsClientMock,
});
const defaultSearchParams = {
@ -438,6 +444,47 @@ describe('EntityStoreDataClient', () => {
expect(spyInit).toHaveBeenCalledWith(EntityType.host, expect.anything(), expect.anything());
});
it('enable all', async () => {
uiSettingsClientGetMock.mockReturnValue(true);
await dataClient.enable({
...defaultOptions,
});
expect(spyInit).toHaveBeenCalledWith(EntityType.host, expect.anything(), expect.anything());
expect(spyInit).toHaveBeenCalledWith(EntityType.user, expect.anything(), expect.anything());
expect(spyInit).toHaveBeenCalledWith(
EntityType.service,
expect.anything(),
expect.anything()
);
expect(spyInit).toHaveBeenCalledWith(
EntityType.generic,
expect.anything(),
expect.anything()
);
});
it('enable all without generic', async () => {
uiSettingsClientGetMock.mockReturnValue(false);
await dataClient.enable({
...defaultOptions,
});
expect(spyInit).toHaveBeenCalledWith(EntityType.host, expect.anything(), expect.anything());
expect(spyInit).toHaveBeenCalledWith(EntityType.user, expect.anything(), expect.anything());
expect(spyInit).toHaveBeenCalledWith(
EntityType.service,
expect.anything(),
expect.anything()
);
expect(spyInit).not.toHaveBeenCalledWith(
EntityType.generic,
expect.anything(),
expect.anything()
);
});
});
describe('applyDataViewIndices', () => {

View file

@ -12,6 +12,7 @@ import type {
AuditLogger,
IScopedClusterClient,
AuditEvent,
IUiSettingsClient,
AnalyticsServiceSetup,
KibanaRequest,
} from '@kbn/core/server';
@ -26,18 +27,19 @@ import type { EntityDefinitionWithState } from '@kbn/entityManager-plugin/server
import type { EntityDefinition } from '@kbn/entities-schema';
import type { estypes } from '@elastic/elasticsearch';
import { SO_ENTITY_DEFINITION_TYPE } from '@kbn/entityManager-plugin/server/saved_objects';
import { SECURITY_SOLUTION_ENABLE_ASSET_INVENTORY_SETTING } from '@kbn/management-settings-ids';
import { RISK_SCORE_INDEX_PATTERN } from '../../../../common/constants';
import {
ENTITY_STORE_INDEX_PATTERN,
ENTITY_STORE_REQUIRED_ES_CLUSTER_PRIVILEGES,
ENTITY_STORE_SOURCE_REQUIRED_ES_INDEX_PRIVILEGES,
} from '../../../../common/entity_analytics/entity_store/constants';
import { getEnabledEntityTypes } from '../../../../common/entity_analytics/utils';
import {
getAllMissingPrivileges,
getMissingPrivilegesErrorMessage,
} from '../../../../common/entity_analytics/privileges';
import { merge } from '../../../../common/utils/objects/merge';
import { getEnabledStoreEntityTypes } from '../../../../common/entity_analytics/entity_store/utils';
import { EntityType } from '../../../../common/entity_analytics/types';
import type { ExperimentalFeatures } from '../../../../common';
import type {
@ -143,6 +145,7 @@ interface EntityStoreClientOpts {
apiKeyManager?: ApiKeyManager;
security: SecurityPluginStart;
request: KibanaRequest;
uiSettingsClient: IUiSettingsClient;
}
interface SearchEntitiesParams {
@ -161,6 +164,7 @@ export class EntityStoreDataClient {
private riskScoreDataClient: RiskScoreDataClient;
private esClient: ElasticsearchClient;
private apiKeyGenerator?: ApiKeyManager;
private uiSettingsClient: IUiSettingsClient;
constructor(private readonly options: EntityStoreClientOpts) {
const {
@ -171,9 +175,11 @@ export class EntityStoreDataClient {
kibanaVersion,
namespace,
apiKeyManager,
uiSettingsClient,
} = options;
this.esClient = clusterClient.asCurrentUser;
this.apiKeyGenerator = apiKeyManager;
this.uiSettingsClient = uiSettingsClient;
this.entityClient = new EntityClient({
clusterClient,
@ -252,8 +258,7 @@ export class EntityStoreDataClient {
const run = <T>(fn: () => Promise<T>) =>
new Promise<T>((resolve) => setTimeout(() => fn().then(resolve), 0));
const { experimentalFeatures } = this.options;
const enabledEntityTypes = getEnabledStoreEntityTypes(experimentalFeatures);
const enabledEntityTypes = await this.getEnabledEntityTypes();
// When entityTypes param is defined it only enables the engines that are provided
const enginesTypes = requestBodyOverrides.entityTypes
@ -270,26 +275,39 @@ export class EntityStoreDataClient {
return { engines, succeeded: true };
}
private async getEnabledEntityTypes(): Promise<EntityType[]> {
const genericEntityStoreEnabled = await this.uiSettingsClient.get<boolean>(
SECURITY_SOLUTION_ENABLE_ASSET_INVENTORY_SETTING
);
return getEnabledEntityTypes(genericEntityStoreEnabled);
}
public async status({
include_components: withComponents = false,
}: GetEntityStoreStatusRequestQuery): Promise<GetEntityStoreStatusResponse> {
const { namespace } = this.options;
const { engines, count } = await this.engineClient.list();
const { engines } = await this.engineClient.list();
const enabledEntityTypes = await this.getEnabledEntityTypes();
const enabledEngines = engines.filter((engine) => {
return enabledEntityTypes.indexOf(EntityType[engine.type]) > -1;
});
let status = ENTITY_STORE_STATUS.RUNNING;
if (count === 0) {
if (enabledEngines.length === 0) {
status = ENTITY_STORE_STATUS.NOT_INSTALLED;
} else if (engines.some((engine) => engine.status === ENGINE_STATUS.ERROR)) {
} else if (enabledEngines.some((engine) => engine.status === ENGINE_STATUS.ERROR)) {
status = ENTITY_STORE_STATUS.ERROR;
} else if (engines.every((engine) => engine.status === ENGINE_STATUS.STOPPED)) {
} else if (enabledEngines.every((engine) => engine.status === ENGINE_STATUS.STOPPED)) {
status = ENTITY_STORE_STATUS.STOPPED;
} else if (engines.some((engine) => engine.status === ENGINE_STATUS.INSTALLING)) {
} else if (enabledEngines.some((engine) => engine.status === ENGINE_STATUS.INSTALLING)) {
status = ENTITY_STORE_STATUS.INSTALLING;
}
if (withComponents) {
const enginesWithComponents = await Promise.all(
engines.map(async (engine) => {
enabledEngines.map(async (engine) => {
const id = buildEntityDefinitionId(engine.type, namespace);
const {
definitions: [definition],
@ -314,7 +332,7 @@ export class EntityStoreDataClient {
return { engines: enginesWithComponents, status };
} else {
return { engines, status };
return { engines: enabledEngines, status };
}
}

View file

@ -15,6 +15,7 @@ import {
hostEntityEngineDescription,
userEntityEngineDescription,
serviceEntityEngineDescription,
genericEntityEngineDescription,
} from '../entity_definitions/entity_descriptions';
import type { EntityStoreConfig } from '../types';
import { buildEntityDefinitionId, mergeEntityStoreIndices } from '../utils';
@ -27,6 +28,7 @@ const engineDescriptionRegistry: Record<EntityType, EntityDescription> = {
host: hostEntityEngineDescription,
user: userEntityEngineDescription,
service: serviceEntityEngineDescription,
generic: genericEntityEngineDescription,
};
interface EngineDescriptionParams {

View file

@ -108,6 +108,7 @@ export const registerEntityStoreDataViewRefreshTask = ({
config: entityStoreConfig,
security,
request,
uiSettingsClient: core.uiSettings.asScopedToClient(soClient),
});
const { errors } = await entityStoreClient.applyDataViewIndices();

View file

@ -14,7 +14,7 @@ import type {
TaskManagerStartContract,
} from '@kbn/task-manager-plugin/server';
import type { ExperimentalFeatures } from '../../../../../../common';
import { getEnabledStoreEntityTypes } from '../../../../../../common/entity_analytics/entity_store/utils';
import { getEntityAnalyticsEntityTypes } from '../../../../../../common/entity_analytics/utils';
import {
EngineComponentResourceEnum,
type EntityType,
@ -201,7 +201,7 @@ export const runEntityStoreFieldRetentionEnrichTask = async ({
return { state: updatedState };
}
const entityTypes = getEnabledStoreEntityTypes(experimentalFeatures);
const entityTypes = getEntityAnalyticsEntityTypes();
for (const entityType of entityTypes) {
const start = Date.now();

View file

@ -16,7 +16,7 @@ import {
ALERT_WORKFLOW_STATUS,
ALERT_WORKFLOW_TAGS,
} from '@kbn/rule-registry-plugin/common/technical_rule_data_field_names';
import { getRiskEngineEntityTypes } from '../../../../common/entity_analytics/risk_engine/utils';
import { getEntityAnalyticsEntityTypes } from '../../../../common/entity_analytics/utils';
import type { EntityType } from '../../../../common/search_strategy';
import type { ExperimentalFeatures } from '../../../../common';
import type {
@ -249,7 +249,7 @@ export const calculateRiskScores = async ({
}
const identifierTypes: EntityType[] = identifierType
? [identifierType]
: getRiskEngineEntityTypes(experimentalFeatures);
: getEntityAnalyticsEntityTypes();
const request = {
size: 0,

View file

@ -16,7 +16,7 @@ import type {
} from '@kbn/task-manager-plugin/server';
import type { AnalyticsServiceSetup } from '@kbn/core-analytics-server';
import type { AuditLogger } from '@kbn/security-plugin-types-server';
import { getRiskEngineEntityTypes } from '../../../../../common/entity_analytics/risk_engine/utils';
import { getEntityAnalyticsEntityTypes } from '../../../../../common/entity_analytics/utils';
import type { EntityType } from '../../../../../common/search_strategy';
import type { ExperimentalFeatures } from '../../../../../common';
import type { AfterKeys } from '../../../../../common/api/entity_analytics/common';
@ -291,7 +291,7 @@ export const runTask = async ({
const identifierTypes: EntityType[] = configuredIdentifierType
? [configuredIdentifierType]
: getRiskEngineEntityTypes(experimentalFeatures);
: getEntityAnalyticsEntityTypes();
const runs: Array<{
identifierType: EntityType;

View file

@ -289,6 +289,7 @@ export class RequestContextFactory implements IRequestContextFactory {
apiKeyManager: getEntityStoreApiKeyManager(),
security: startPlugins.security,
request,
uiSettingsClient: coreContext.uiSettings.client,
});
}),
getAssetInventoryClient: memoize(

View file

@ -45,6 +45,11 @@ export default ({ getService }: FtrProviderContext) => {
await utils.initEntityEngineForEntityTypesAndWait(['host']);
await utils.expectEngineAssetsExist('host');
});
it('should have installed the expected generic resources', async () => {
await utils.initEntityEngineForEntityTypesAndWait(['generic']);
await utils.expectEngineAssetsExist('generic');
});
});
describe('init error handling', () => {