Skip to content
Merged
Show file tree
Hide file tree
Changes from 73 commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
de04f1b
add boilerplate
kubasobon Feb 5, 2026
5b2b0c6
add crud delete
kubasobon Feb 5, 2026
37fb4d9
add upsert bulk
kubasobon Feb 6, 2026
653726f
add entity schema
kubasobon Feb 6, 2026
05f2d20
change schema to omit AC/Risk for now
kubasobon Feb 6, 2026
232573f
generate temp schema and fix types
kubasobon Feb 6, 2026
597fe1d
register routes
kubasobon Feb 6, 2026
a95d6e7
remove dependency on entityType
kubasobon Feb 6, 2026
b048751
delete files added by mistake
kubasobon Feb 6, 2026
fb77de0
Merge branch 'main' into entity-store-v2-crud
kubasobon Feb 11, 2026
b7ffde1
fix imports
kubasobon Feb 11, 2026
24dd5f1
update with extra validation
kubasobon Feb 11, 2026
d05f012
drop EUID method
kubasobon Feb 11, 2026
ed80d21
add hashed ids
kubasobon Feb 11, 2026
32184f5
update schema
kubasobon Feb 11, 2026
cf87382
rename to crud_client and reintroduce entityType
kubasobon Feb 11, 2026
ed7a859
update upsert & delete behavior
kubasobon Feb 13, 2026
e1c83e4
handle bulkUpsert entities
kubasobon Feb 13, 2026
da76347
handle request errors better
kubasobon Feb 13, 2026
d34f03e
add missing entity id field clause
kubasobon Feb 13, 2026
28f6175
Merge branch 'main' into entity-store-v2-crud
kubasobon Feb 13, 2026
94ebf32
Merge branch 'main' into entity-store-v2-crud
kubasobon Feb 13, 2026
82bb6af
start working on Scout tests
kubasobon Feb 16, 2026
2609597
add first scout test - working
kubasobon Feb 16, 2026
0728382
update tests to reflect new routes
kubasobon Feb 16, 2026
a0675ed
change v2 api to internal
kubasobon Feb 16, 2026
303d3a8
test force flag
kubasobon Feb 16, 2026
39b2291
add upsert test
kubasobon Feb 16, 2026
9c4011b
updates
kubasobon Feb 16, 2026
b923c65
fix bulk update test
kubasobon Feb 16, 2026
67e65aa
add working scout test suite
kubasobon Feb 16, 2026
b0d445f
fix naming issue
kubasobon Feb 16, 2026
567f0dd
delete entity id
kubasobon Feb 16, 2026
290f091
Merge branch 'main' into entity-store-v2-crud
kubasobon Feb 16, 2026
6cd8bf3
Changes from node scripts/lint_ts_projects --fix
kibanamachine Feb 16, 2026
334d814
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine Feb 16, 2026
25dec94
fix type errors
kubasobon Feb 16, 2026
b7b92ac
update last test
kubasobon Feb 16, 2026
d3459f3
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Feb 16, 2026
1f84f43
add eslint marker
kubasobon Feb 16, 2026
ed896c0
Merge branch 'entity-store-v2-crud' of github.com:elastic/kibana into…
kubasobon Feb 16, 2026
224ea5b
Merge branch 'main' into entity-store-v2-crud
kubasobon Feb 20, 2026
70660e9
lowercase the attributes/relationships/behaviors etc.
kubasobon Feb 20, 2026
f9cdbdc
fix hanging promise issue
kubasobon Feb 20, 2026
184b01c
fix ID/Hashed ID issues
kubasobon Feb 20, 2026
b93afbc
crud_client: use logger.debug
kubasobon Feb 20, 2026
a8fc7a4
crud_client: extract create/update methods, add explicit return values
kubasobon Feb 20, 2026
b505a38
crud_client: retry 3x on update conflict
kubasobon Feb 20, 2026
e3c3891
crud_api: remove redundant type casts
kubasobon Feb 20, 2026
640870e
crud_api: move endpoints to a common directory
kubasobon Feb 20, 2026
339a3d6
crud_api: extend async/bulk upsert test with forced extraction
kubasobon Feb 20, 2026
1d52156
crud_api: remove two update results that never occur
kubasobon Feb 20, 2026
9df544c
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Feb 20, 2026
d5f4add
crud_api: let upsert errors bubble up
kubasobon Feb 20, 2026
fdd4fbc
crud_api: update tests
kubasobon Feb 20, 2026
7647f20
Merge branch 'entity-store-v2-crud' of github.com:elastic/kibana into…
kubasobon Feb 20, 2026
71f5b72
update snapshots
kubasobon Feb 20, 2026
efc0f51
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Feb 20, 2026
e1499af
fix eslint issue
kubasobon Feb 23, 2026
6fb8227
Merge branch 'entity-store-v2-crud' of github.com:elastic/kibana into…
kubasobon Feb 23, 2026
2b4f0b0
crud_client: extract util funcs
kubasobon Feb 23, 2026
ffb61f0
crud_client: add utils.ts tests
kubasobon Feb 23, 2026
82e3bab
Merge branch 'main' into entity-store-v2-crud
kubasobon Feb 23, 2026
4cc1b08
crud_client: use update+doc_as_upsert
kubasobon Feb 23, 2026
690395b
crud_api: throw 503 if not started for bulk
kubasobon Feb 23, 2026
6114f34
crud_api: wait for shard sync on update
kubasobon Feb 23, 2026
6ed1c02
crud_api: handle 503 for bulk api
kubasobon Feb 23, 2026
86ddefa
crud_client: update transformDocForUpsert to be more readable and pre…
kubasobon Feb 23, 2026
bbe9e07
Merge branch 'main' into entity-store-v2-crud
kubasobon Feb 23, 2026
322a612
crud_client: document sync/async upsert methods
kubasobon Feb 23, 2026
0188a41
openapi: comment with script command
kubasobon Feb 23, 2026
99cc470
Merge branch 'main' into entity-store-v2-crud
kubasobon Feb 23, 2026
bbdbdc6
openapi: make the comment clearer
kubasobon Feb 23, 2026
bc40d88
crud_client: apply review remarks
kubasobon Feb 23, 2026
0d85afc
crud_client: make generic a const
kubasobon Feb 23, 2026
d6b2e4a
asset_manager: remove unused isInstalled function
kubasobon Feb 24, 2026
5e00170
crud_client: rename util func for clarity
kubasobon Feb 24, 2026
6edeb12
crud_client: filter bulk_upsert errors
kubasobon Feb 24, 2026
5beb7cb
crud_client: delete based on EUID rather than hash
kubasobon Feb 24, 2026
21e38e3
crud_client: update DELETE tests and check ids
kubasobon Feb 24, 2026
94b69f3
Merge branch 'main' into entity-store-v2-crud
kubasobon Feb 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import type { EntityType, EntityField } from './entity_schema';
import { oldestValue, newestValue } from './field_retention_operations';

export const ENTITY_ID_FIELD = 'entity.id';

// Copied from x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/common.ts

export const getCommonFieldDescriptions = (
Expand Down Expand Up @@ -53,57 +55,57 @@ export const getEntityFieldsDescriptions = (rootField?: EntityType) => {
newestValue({ source: `${prefix}.url`, destination: 'entity.url' }),

newestValue({
source: `${prefix}.attributes.Privileged`,
destination: 'entity.attributes.Privileged',
source: `${prefix}.attributes.privileged`,
destination: 'entity.attributes.privileged',
mapping: { type: 'boolean' },
allowAPIUpdate: true,
}),
newestValue({
source: `${prefix}.attributes.Asset`,
destination: 'entity.attributes.Asset',
source: `${prefix}.attributes.asset`,
destination: 'entity.attributes.asset',
mapping: { type: 'boolean' },
allowAPIUpdate: true,
}),
newestValue({
source: `${prefix}.attributes.Managed`,
destination: 'entity.attributes.Managed',
source: `${prefix}.attributes.managed`,
destination: 'entity.attributes.managed',
mapping: { type: 'boolean' },
allowAPIUpdate: true,
}),
newestValue({
source: `${prefix}.attributes.Mfa_enabled`,
destination: 'entity.attributes.Mfa_enabled',
source: `${prefix}.attributes.mfa_enabled`,
destination: 'entity.attributes.mfa_enabled',
mapping: { type: 'boolean' },
allowAPIUpdate: true,
}),

/* Lifecycle fields should not allow update via the API */
newestValue({
source: `${prefix}.lifecycle.First_seen`,
destination: 'entity.lifecycle.First_seen',
source: `${prefix}.lifecycle.first_seen`,
destination: 'entity.lifecycle.first_seen',
mapping: { type: 'date' },
}),
newestValue({
source: `${prefix}.lifecycle.Last_activity`,
destination: 'entity.lifecycle.Last_activity',
source: `${prefix}.lifecycle.last_activity`,
destination: 'entity.lifecycle.last_activity',
mapping: { type: 'date' },
}),

newestValue({
source: `${prefix}.behaviors.Brute_force_victim`,
destination: 'entity.behaviors.Brute_force_victim',
source: `${prefix}.behaviors.brute_force_victim`,
destination: 'entity.behaviors.brute_force_victim',
mapping: { type: 'boolean' },
allowAPIUpdate: true,
}),
newestValue({
source: `${prefix}.behaviors.New_country_login`,
destination: 'entity.behaviors.New_country_login',
source: `${prefix}.behaviors.new_country_login`,
destination: 'entity.behaviors.new_country_login',
mapping: { type: 'boolean' },
allowAPIUpdate: true,
}),
newestValue({
source: `${prefix}.behaviors.Used_usb_device`,
destination: 'entity.behaviors.Used_usb_device',
source: `${prefix}.behaviors.used_usb_device`,
destination: 'entity.behaviors.used_usb_device',
mapping: { type: 'boolean' },
allowAPIUpdate: true,
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
/*
Comment thread
kubasobon marked this conversation as resolved.
* 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.
*/

/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*
* info:
* title: Common Entities Schemas
* version: 1
*/

import { z } from '@kbn/zod';

export type EngineMetadata = z.infer<typeof EngineMetadata>;
export const EngineMetadata = z
.object({
Type: z.string(),
})
.strict();

export type EntityRiskLevels = z.infer<typeof EntityRiskLevels>;
export const EntityRiskLevels = z.enum(['Unknown', 'Low', 'Moderate', 'High', 'Critical']);
export type EntityRiskLevelsEnum = typeof EntityRiskLevels.enum;
export const EntityRiskLevelsEnum = EntityRiskLevels.enum;

export type EntityField = z.infer<typeof EntityField>;
export const EntityField = z
.object({
id: z.string(),
name: z.string().optional(),
type: z.string().optional(),
sub_type: z.string().optional(),
source: z.string().optional(),
EngineMetadata: EngineMetadata.optional(),
attributes: z
.object({
privileged: z.boolean().optional(),
asset: z.boolean().optional(),
managed: z.boolean().optional(),
mfa_enabled: z.boolean().optional(),
})
.strict()
.optional(),
behaviors: z
.object({
brute_force_victim: z.boolean().optional(),
new_country_login: z.boolean().optional(),
used_usb_device: z.boolean().optional(),
})
.strict()
.optional(),
lifecycle: z
.object({
first_seen: z.string().datetime().optional(),
last_activity: z.string().datetime().optional(),
})
.strict()
.optional(),
relationships: z
.object({
communicates_with: z.array(z.string()).optional(),
depends_on: z.array(z.string()).optional(),
dependent_of: z.array(z.string()).optional(),
owns: z.array(z.string()).optional(),
owned_by: z.array(z.string()).optional(),
accesses_frequently: z.array(z.string()).optional(),
accessed_frequently_by: z.array(z.string()).optional(),
supervises: z.array(z.string()).optional(),
supervised_by: z.array(z.string()).optional(),
})
.strict()
.optional(),
risk: z
.object({
/**
* Lexical description of the entity's risk.
*/
calculated_level: EntityRiskLevels.optional(),
/**
* The raw numeric value of the given entity's risk score.
*/
calculated_score: z.number().optional(),
/**
* The normalized numeric value of the given entity's risk score. Useful for comparing with other entities.
*/
calculated_score_norm: z.number().min(0).max(100).optional(),
})
.strict()
.optional(),
})
.strict();

/**
* The criticality level of the asset.
*/
export type AssetCriticalityLevel = z.infer<typeof AssetCriticalityLevel>;
export const AssetCriticalityLevel = z.enum([
'low_impact',
'medium_impact',
'high_impact',
'extreme_impact',
]);
export type AssetCriticalityLevelEnum = typeof AssetCriticalityLevel.enum;
export const AssetCriticalityLevelEnum = AssetCriticalityLevel.enum;

export type Asset = z.infer<typeof Asset>;
export const Asset = z
.object({
id: z.string().optional(),
name: z.string().optional(),
owner: z.string().optional(),
serial_number: z.string().optional(),
model: z.string().optional(),
vendor: z.string().optional(),
environment: z.string().optional(),
criticality: AssetCriticalityLevel.optional(),
business_unit: z.string().optional(),
})
.strict();

/**
* A generic representation of a document contributing to a Risk Score.
*/
export type RiskScoreInput = z.infer<typeof RiskScoreInput>;
export const RiskScoreInput = z.object({
/**
* The unique identifier (`_id`) of the original source document
*/
id: z.string(),
/**
* The unique index (`_index`) of the original source document
*/
index: z.string(),
/**
* The risk category of the risk input document.
*/
category: z.string(),
/**
* A human-readable description of the risk input document.
*/
description: z.string(),
/**
* The weighted risk score of the risk input document.
*/
risk_score: z.number().min(0).max(100).optional(),
/**
* The @timestamp of the risk input document.
*/
timestamp: z.string().optional(),
contribution_score: z.number().optional(),
});

export type EntityRiskScoreRecord = z.infer<typeof EntityRiskScoreRecord>;
export const EntityRiskScoreRecord = z.object({
/**
* The time at which the risk score was calculated.
*/
'@timestamp': z.string().datetime(),
/**
* The identifier field defining this risk score. Coupled with `id_value`, uniquely identifies the entity being scored.
*/
id_field: z.string(),
/**
* The identifier value defining this risk score. Coupled with `id_field`, uniquely identifies the entity being scored.
*/
id_value: z.string(),
/**
* Lexical description of the entity's risk.
*/
calculated_level: EntityRiskLevels,
/**
* The raw numeric value of the given entity's risk score.
*/
calculated_score: z.number(),
/**
* The normalized numeric value of the given entity's risk score. Useful for comparing with other entities.
*/
calculated_score_norm: z.number().min(0).max(100),
/**
* The contribution of Category 1 to the overall risk score (`calculated_score`). Category 1 contains Detection Engine Alerts.
*/
category_1_score: z.number(),
/**
* The number of risk input documents that contributed to the Category 1 score (`category_1_score`).
*/
category_1_count: z.number().int(),
/**
* A list of the highest-risk documents contributing to this risk score. Useful for investigative purposes.
*/
inputs: z.array(RiskScoreInput),
category_2_score: z.number().optional(),
category_2_count: z.number().int().optional(),
notes: z.array(z.string()),
criticality_modifier: z.number().optional(),
criticality_level: AssetCriticalityLevel.optional(),
/**
* A list of modifiers that were applied to the risk score calculation.
*/
modifiers: z
.array(
z.object({
type: z.string(),
subtype: z.string().optional(),
modifier_value: z.number().optional(),
contribution: z.number(),
metadata: z.object({}).catchall(z.unknown()).optional(),
})
)
.optional(),
});

export type UserEntity = z.infer<typeof UserEntity>;
export const UserEntity = z
.object({
'@timestamp': z.string().datetime().optional(),
entity: EntityField,
user: z
.object({
full_name: z.array(z.string()).optional(),
domain: z.array(z.string()).optional(),
roles: z.array(z.string()).optional(),
name: z.string(),
id: z.array(z.string()).optional(),
email: z.array(z.string()).optional(),
hash: z.array(z.string()).optional(),
risk: EntityRiskScoreRecord.optional(),
})
.strict()
.optional(),
asset: Asset.optional(),
event: z
.object({
ingested: z.string().datetime().optional(),
})
.strict()
.optional(),
})
.strict();

export type HostEntity = z.infer<typeof HostEntity>;
export const HostEntity = z
.object({
'@timestamp': z.string().datetime().optional(),
entity: EntityField,
host: z
.object({
hostname: z.array(z.string()).optional(),
domain: z.array(z.string()).optional(),
ip: z.array(z.string()).optional(),
name: z.string(),
id: z.array(z.string()).optional(),
type: z.array(z.string()).optional(),
mac: z.array(z.string()).optional(),
architecture: z.array(z.string()).optional(),
risk: EntityRiskScoreRecord.optional(),
entity: EntityField.optional(),
})
.strict()
.optional(),
asset: Asset.optional(),
event: z
.object({
ingested: z.string().datetime().optional(),
})
.strict()
.optional(),
})
.strict();

export type ServiceEntity = z.infer<typeof ServiceEntity>;
export const ServiceEntity = z
.object({
'@timestamp': z.string().datetime().optional(),
entity: EntityField,
service: z
.object({
name: z.string(),
risk: EntityRiskScoreRecord.optional(),
entity: EntityField.optional(),
})
.strict()
.optional(),
asset: Asset.optional(),
event: z
.object({
ingested: z.string().datetime().optional(),
})
.strict()
.optional(),
})
.strict();

export type GenericEntity = z.infer<typeof GenericEntity>;
export const GenericEntity = z
.object({
'@timestamp': z.string().datetime().optional(),
entity: EntityField,
asset: Asset.optional(),
})
.strict();

export const EntityInternal = z.union([UserEntity, HostEntity, ServiceEntity, GenericEntity]);

export type Entity = z.infer<typeof EntityInternal>;
export const Entity = EntityInternal as z.ZodType<Entity>;
Loading
Loading