Skip to content

[Entity Store] Adding list endpoint with query filter#258320

Merged
tiansivive merged 7 commits intoelastic:mainfrom
tiansivive:siem/entity-store/crud-api-get-filter
Mar 24, 2026
Merged

[Entity Store] Adding list endpoint with query filter#258320
tiansivive merged 7 commits intoelastic:mainfrom
tiansivive:siem/entity-store/crud-api-get-filter

Conversation

@tiansivive
Copy link
Copy Markdown
Contributor

Summary

This PR introduces a LIST endpoint in the entity store, filterable via query param

How to test

  1. Seed some data into the store. The easiest way is via security-document-generator.

  2. Test the endpoint with curl

List all entities (no filter)

curl -s -u elastic:changeme \
  -H "kbn-xsrf: true" \
  -H "x-elastic-internal-origin: kibana" \
  -H "elastic-api-version: 2" \
  "http://localhost:5601/internal/security/entity_store/entities" | jq

Expected: { "entities": [ ... ] } containing seeded entities.

List with a DSL filter

curl -s -u elastic:changeme \
  -H "kbn-xsrf: true" \
  -H "x-elastic-internal-origin: kibana" \
  -H "elastic-api-version: 2" \
  --get \
  --data-urlencode 'filter={"term":{"entity.type":"host"}}' \
  "http://localhost:5601/internal/security/entity_store/entities" | jq

Expected: only hosts entities are returned.


Invalid filter (should return 400)

curl -s -u elastic:changeme \
  -H "kbn-xsrf: true" \
  -H "x-elastic-internal-origin: kibana" \
  -H "elastic-api-version: 2" \
  "http://localhost:5601/internal/security/entity_store/entities?filter=not-json" | jq

Expected: 400 Bad Request with "Invalid filter: must be a valid JSON-encoded DSL query".

@tiansivive tiansivive added Entity Store (Next) release_note:skip Skip the PR/issue when compiling release notes backport:skip This PR does not require backporting labels Mar 18, 2026
@tiansivive tiansivive marked this pull request as ready for review March 18, 2026 11:06
@tiansivive tiansivive requested a review from a team as a code owner March 18, 2026 11:06
Copy link
Copy Markdown
Contributor

@chennn1990 chennn1990 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good,
Please see my comments

import { wrapMiddlewares } from '../../middleware';

const querySchema = z.object({
filter: z.string().optional(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add a validation for the filter

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, shouldnt we be using OAS?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets keep it internal for Now, once we will convert the CRUD API's to be public this will be addressed
@uri-weisman WDYT?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean using the superRefine method in order to validate the filter string., it should be a valid DSL filter.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

superRefine is apparently deprecated?
I also didnt really see any other place doing this kind of validation, so isntead I jsut used the typical KQL to query DSL utility fns. They already throw on incorrect strings, so jus tuse that to validate.

}
}

const entities = await crudClient.listEntities(parsedFilter);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use this one inside the try

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// An optional DSL filter can be provided and is applied as an additional
// filter clause on the search query, e.g. to scope results by additional
// field conditions.
public async listEntities(filter?: QueryDslQueryContainer): Promise<Entity[]> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would change the terminology to getEntities.
I see list as a quite confusing inside CRUD operations

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the problem with GET is that it implies a single entity? We typically use LIST for GET'ing multiple entries. I can change though, if that's the way we should look at API's in this domain?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have a convention yet, lets stick to the Kibana conventions.
If list is more common, lets keep it

STOP: `${ENTITY_STORE_BASE_ROUTE}/stop`,
FORCE_LOG_EXTRACTION: `${ENTITY_STORE_BASE_ROUTE}/{entityType}/force_log_extraction`,
FORCE_HISTORY_SNAPSHOT: `${ENTITY_STORE_BASE_ROUTE}/force_history_snapshot`,
CRUD_LIST: `${ENTITY_STORE_BASE_ROUTE}/entities`,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRUD_GET: ENTITY_STORE_BASE_ROUTE
IMO A simple get to the base route is more meaningful

Copy link
Copy Markdown
Contributor

@uri-weisman uri-weisman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tiansivive thanks for putting this PR 🙏
Can you please add unit tests and a Scout test (At least one happy path)?

? { bool: { filter: [filter] } }
: { match_all: {} };

const resp = await this.esClient.search<Entity>({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So today you get at most one page of 10 results (ES default), with no way to request more or skip ahead.
I suggest to extend the params to support size and searchAfter to make this endpoint more usable.

export interface ListEntitiesParams {
  filter?: QueryDslQueryContainer;
  size?: number;
  searchAfter?: Array<string | number>;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good shout

Copy link
Copy Markdown
Contributor

@uri-weisman uri-weisman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good Job @tiansivive!

@@ -74,6 +74,37 @@ apiTest.describe.skip('Entity Store CRUD API tests', { tag: ENTITY_STORE_TAGS },
expect(check.found).toBe(true);
});

apiTest('Should list entities', async ({ apiClient, esClient }) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test feels a bit limited, I would expect to see multiple tests that better cover the different API capabilities, especially around ListEntitiesParams (valid scenarios & invalid scenarios)

import { wrapMiddlewares } from '../../middleware';

const querySchema = z.object({
filter: z.string().optional(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean using the superRefine method in order to validate the filter string., it should be a valid DSL filter.

@tiansivive tiansivive force-pushed the siem/entity-store/crud-api-get-filter branch from e01f3f3 to 12a6c34 Compare March 23, 2026 13:30
@tiansivive tiansivive enabled auto-merge (squash) March 24, 2026 12:23
@tiansivive tiansivive merged commit 91ae003 into elastic:main Mar 24, 2026
18 checks passed
@elasticmachine
Copy link
Copy Markdown
Contributor

💛 Build succeeded, but was flaky

Failed CI Steps

Test Failures

  • [job] [logs] FTR Configs #160 / Actions and Triggers app Connectors Tines connector page should edit the connector
  • [job] [logs] FTR Configs #26 / InfraOps App Metrics UI Hosts View #With data #Single Host Flyout Tabs Dashboards Tab should render dashboards tab splash screen with option to add dashboard

Metrics [docs]

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
entityStore 24.5KB 24.5KB +26.0B

History

abhishekbhatia1710 added a commit to abhishekbhatia1710/kibana that referenced this pull request Mar 24, 2026
Steps 2b + 2e from refactor prompt:
- Replace entityStoreDataClient param with listEntities: () => Promise<LeadEntity[]>
  in RunPipelineParams (dependency injection pattern)
- Wire startPlugins.entityStore.createCRUDClient() + fetchAllLeadEntities()
  in the task runner, removing the TODO skip
- Add fetchAllLeadEntities paginated helper to entity_conversion.ts
- Rewrite run_pipeline.test.ts to mock listEntities function

Depends on PR elastic#258320 for CRUDClient.listEntities() method.
abhishekbhatia1710 added a commit to abhishekbhatia1710/kibana that referenced this pull request Mar 24, 2026
Steps 2a, 2c, 2d from refactor prompt:
- Add entityStore: EntityStoreStartContract to SecuritySolutionPluginStartDependencies
- Create fetchAllLeadEntities() paginated helper in entity_conversion.ts
  that pages through all V2 entities via searchAfter cursors
- Use startPlugins.entityStore.createCRUDClient().listEntities() in the
  generate_leads route instead of EntityStoreDataClient.searchEntities()
- Restore getStartServices parameter to generateLeadsRoute (needed for
  entityStore plugin access, not for inference/chatModel)

Depends on PR elastic#258320 for CRUDClient.listEntities() method.
mbondyra added a commit to mbondyra/kibana that referenced this pull request Mar 24, 2026
…ra/kibana into dashboard_align_attachment_to_api

* 'dashboard_align_attachment_to_api' of github.com:mbondyra/kibana: (45 commits)
  [OTel Tracing] HTTP instrumentation (elastic#258663)
  Replace deprecated EUI icons in files owned by @elastic/ml-ui (elastic#255624)
  [Codeowners] add missing codeowners for security_solution_api_integration tests (elastic#259223)
  [CI] fix bad imports that came from a merge-race (elastic#259383)
  Add `.claude/worktrees/` to `.gitignore` (elastic#259192)
  Improve unknown-key validation error message in @kbn/config-schema (elastic#258633)
  [ML] Update Security ML jobs to use entity analytics fields for host and user fields (elastic#255339)
  [Table sweep] Update table columns responsiveness in Index Management and Dashboards (elastic#259340)
  skip failing test suite (elastic#258790)
  skip failing test suite (elastic#259261)
  chore: util to clean cached images (elastic#259335)
  [Entity Store] Use last_seen for automated resolution watermark (elastic#258574)
  [One Workflow] Fix flaky alert trigger Scout test by removing order-dependent assertions (elastic#259299)
  Skip serverless Discover request counts tests for MKI (elastic#259333)
  [Security Solution] render header title in new document flyout in Security Solution and Discover (elastic#258166)
  [Agent Builder] register inference endpoint feature (elastic#259259)
  [Agent Builder] Skills Command Menu - Add descriptions and scope options to agent (elastic#258964)
  [Streams][Streamlang][API] Fully use meta({id}) to reuse schema partials in OAS output (elastic#259275)
  fix(files_example): add tableCaption to EuiInMemoryTable for a11y (elastic#258289)
  [Entity Store] Adding list endpoint with query filter (elastic#258320)
  ...
abhishekbhatia1710 added a commit to abhishekbhatia1710/kibana that referenced this pull request Mar 25, 2026
Steps 2b + 2e from refactor prompt:
- Replace entityStoreDataClient param with listEntities: () => Promise<LeadEntity[]>
  in RunPipelineParams (dependency injection pattern)
- Wire startPlugins.entityStore.createCRUDClient() + fetchAllLeadEntities()
  in the task runner, removing the TODO skip
- Add fetchAllLeadEntities paginated helper to entity_conversion.ts
- Rewrite run_pipeline.test.ts to mock listEntities function

Depends on PR elastic#258320 for CRUDClient.listEntities() method.
jeramysoucy pushed a commit to jeramysoucy/kibana that referenced this pull request Mar 26, 2026
# Summary

This PR introduces a LIST endpoint in the entity store, filterable via
query param

# How to test

1. Seed some data into the store. The easiest way is via
[security-document-generator](https://github.com/elastic/security-documents-generator).


2. Test the endpoint with curl

### List all entities (no filter)
```bash
curl -s -u elastic:changeme \
  -H "kbn-xsrf: true" \
  -H "x-elastic-internal-origin: kibana" \
  -H "elastic-api-version: 2" \
  "http://localhost:5601/internal/security/entity_store/entities" | jq
```

Expected: `{ "entities": [ ... ] }` containing seeded entities.

### List with a DSL filter

```bash
curl -s -u elastic:changeme \
  -H "kbn-xsrf: true" \
  -H "x-elastic-internal-origin: kibana" \
  -H "elastic-api-version: 2" \
  --get \
  --data-urlencode 'filter={"term":{"entity.type":"host"}}' \
  "http://localhost:5601/internal/security/entity_store/entities" | jq
```

Expected: only hosts entities are returned.

---

### Invalid filter (should return 400)

```bash
curl -s -u elastic:changeme \
  -H "kbn-xsrf: true" \
  -H "x-elastic-internal-origin: kibana" \
  -H "elastic-api-version: 2" \
  "http://localhost:5601/internal/security/entity_store/entities?filter=not-json" | jq
```

Expected: `400 Bad Request` with `"Invalid filter: must be a valid
JSON-encoded DSL query"`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport:skip This PR does not require backporting Entity Store (Next) release_note:skip Skip the PR/issue when compiling release notes v9.4.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants