[9.0] Added more request validation to entity store enablement (#212657) (#215257)

# Backport

This will backport the following commits from `main` to `9.0`:
- [Added more request validation to entity store enablement
(#212657)](https://github.com/elastic/kibana/pull/212657)

<!--- Backport version: 9.6.6 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT [{"author":{"name":"Jared
Burgett","email":"147995946+jaredburgettelastic@users.noreply.github.com"},"sourceCommit":{"committedDate":"2025-03-19T23:31:31Z","message":"Added
more request validation to entity store enablement (#212657)\n\n#
Purpose\n\nThis change introduces new validations that ensure no loss of
data is\npossible if a user accidentally sets the Security Entity Store
enrich\npolicy execution interval to a value that “doesn’t play nicely”
with the\nlookback period value.\n\nThe specific logic (greater than or
equal to half the value) was chosen\nto not only ensure no loss of data,
but also provide extra resiliency in\ncase of a failed enrich policy
execution.\n\n(Note that this is not considered a breaking change, as
the parameters\nare not yet available on any version of Elastic,
including Serverless.)\n\n# How to test\n\n1. Load appropriate entity
log data to your Kibana instance (for\nexample, using
the\n[security-documents-generator](https://github.com/elastic/security-documents-generator))\n2.
Navigate to the Developer console\n3. Attempt to enable the Entity Store
via the /enable or /init routes\n(examples below), and pass in values
that are expected to error. For\nexample, “lookbackPeriod”: “24h” and
“enrichPolicyExecutionInterval”:\n“24h” should fail, because of the
validation logic\n4. Expect results similar to those shown below,
specifically a 400\nerror, or else a success message\n\n<img
width=\"1902\" alt=\"Screenshot 2025-02-27 at 12 57
45 AM\"\nsrc=\"https://github.com/user-attachments/assets/a7f4b0fb-9899-4e00-a0ae-d172245bd506\"\n/>\n<img
width=\"1909\" alt=\"Screenshot 2025-02-27 at 12 58
06 AM\"\nsrc=\"https://github.com/user-attachments/assets/372acde2-9d7b-4c75-8596-af8374088f79\"\n/>\n\n---------\n\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by:
Elastic Machine
<elasticmachine@users.noreply.github.com>","sha":"64743b3a820e3af4478d78bb643fa3531b302aa6","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","backport:version","v8.18.0","v9.1.0","v8.19.0"],"title":"Added
more request validation to entity store
enablement","number":212657,"url":"https://github.com/elastic/kibana/pull/212657","mergeCommit":{"message":"Added
more request validation to entity store enablement (#212657)\n\n#
Purpose\n\nThis change introduces new validations that ensure no loss of
data is\npossible if a user accidentally sets the Security Entity Store
enrich\npolicy execution interval to a value that “doesn’t play nicely”
with the\nlookback period value.\n\nThe specific logic (greater than or
equal to half the value) was chosen\nto not only ensure no loss of data,
but also provide extra resiliency in\ncase of a failed enrich policy
execution.\n\n(Note that this is not considered a breaking change, as
the parameters\nare not yet available on any version of Elastic,
including Serverless.)\n\n# How to test\n\n1. Load appropriate entity
log data to your Kibana instance (for\nexample, using
the\n[security-documents-generator](https://github.com/elastic/security-documents-generator))\n2.
Navigate to the Developer console\n3. Attempt to enable the Entity Store
via the /enable or /init routes\n(examples below), and pass in values
that are expected to error. For\nexample, “lookbackPeriod”: “24h” and
“enrichPolicyExecutionInterval”:\n“24h” should fail, because of the
validation logic\n4. Expect results similar to those shown below,
specifically a 400\nerror, or else a success message\n\n<img
width=\"1902\" alt=\"Screenshot 2025-02-27 at 12 57
45 AM\"\nsrc=\"https://github.com/user-attachments/assets/a7f4b0fb-9899-4e00-a0ae-d172245bd506\"\n/>\n<img
width=\"1909\" alt=\"Screenshot 2025-02-27 at 12 58
06 AM\"\nsrc=\"https://github.com/user-attachments/assets/372acde2-9d7b-4c75-8596-af8374088f79\"\n/>\n\n---------\n\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by:
Elastic Machine
<elasticmachine@users.noreply.github.com>","sha":"64743b3a820e3af4478d78bb643fa3531b302aa6"}},"sourceBranch":"main","suggestedTargetBranches":["9.0","8.18","8.x"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.18","label":"v8.18.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/212657","number":212657,"mergeCommit":{"message":"Added
more request validation to entity store enablement (#212657)\n\n#
Purpose\n\nThis change introduces new validations that ensure no loss of
data is\npossible if a user accidentally sets the Security Entity Store
enrich\npolicy execution interval to a value that “doesn’t play nicely”
with the\nlookback period value.\n\nThe specific logic (greater than or
equal to half the value) was chosen\nto not only ensure no loss of data,
but also provide extra resiliency in\ncase of a failed enrich policy
execution.\n\n(Note that this is not considered a breaking change, as
the parameters\nare not yet available on any version of Elastic,
including Serverless.)\n\n# How to test\n\n1. Load appropriate entity
log data to your Kibana instance (for\nexample, using
the\n[security-documents-generator](https://github.com/elastic/security-documents-generator))\n2.
Navigate to the Developer console\n3. Attempt to enable the Entity Store
via the /enable or /init routes\n(examples below), and pass in values
that are expected to error. For\nexample, “lookbackPeriod”: “24h” and
“enrichPolicyExecutionInterval”:\n“24h” should fail, because of the
validation logic\n4. Expect results similar to those shown below,
specifically a 400\nerror, or else a success message\n\n<img
width=\"1902\" alt=\"Screenshot 2025-02-27 at 12 57
45 AM\"\nsrc=\"https://github.com/user-attachments/assets/a7f4b0fb-9899-4e00-a0ae-d172245bd506\"\n/>\n<img
width=\"1909\" alt=\"Screenshot 2025-02-27 at 12 58
06 AM\"\nsrc=\"https://github.com/user-attachments/assets/372acde2-9d7b-4c75-8596-af8374088f79\"\n/>\n\n---------\n\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by:
Elastic Machine
<elasticmachine@users.noreply.github.com>","sha":"64743b3a820e3af4478d78bb643fa3531b302aa6"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Jared Burgett <147995946+jaredburgettelastic@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2025-03-20 06:54:32 +01:00 committed by GitHub
parent f6876a4349
commit 12a8f3062c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 183 additions and 34 deletions

View file

@ -10265,6 +10265,8 @@ paths:
succeeded:
type: boolean
description: Successful response
'400':
description: Invalid request
summary: Initialize the Entity Store
tags:
- Security Entity Analytics API
@ -10402,6 +10404,8 @@ paths:
schema:
$ref: '#/components/schemas/Security_Entity_Analytics_API_EngineDescriptor'
description: Successful response
'400':
description: Invalid request
summary: Initialize an Entity Engine
tags:
- Security Entity Analytics API
@ -53017,7 +53021,7 @@ components:
- dsl
- response
Security_Entity_Analytics_API_Interval:
description: Interval in which enrich policy runs. For example, `"1h"` means the rule runs every hour.
description: Interval in which enrich policy runs. For example, `"1h"` means the rule runs every hour. Must be less than or equal to half the duration of the lookback period,
example: 1h
pattern: ^[1-9]\d*[smh]$
type: string

View file

@ -12284,6 +12284,8 @@ paths:
succeeded:
type: boolean
description: Successful response
'400':
description: Invalid request
summary: Initialize the Entity Store
tags:
- Security Entity Analytics API
@ -12417,6 +12419,8 @@ paths:
schema:
$ref: '#/components/schemas/Security_Entity_Analytics_API_EngineDescriptor'
description: Successful response
'400':
description: Invalid request
summary: Initialize an Entity Engine
tags:
- Security Entity Analytics API
@ -60619,7 +60623,7 @@ components:
- dsl
- response
Security_Entity_Analytics_API_Interval:
description: Interval in which enrich policy runs. For example, `"1h"` means the rule runs every hour.
description: Interval in which enrich policy runs. For example, `"1h"` means the rule runs every hour. Must be less than or equal to half the duration of the lookback period,
example: 1h
pattern: ^[1-9]\d*[smh]$
type: string

View file

@ -104,7 +104,7 @@ export const InspectQuery = z.object({
});
/**
* Interval in which enrich policy runs. For example, `"1h"` means the rule runs every hour.
* Interval in which enrich policy runs. For example, `"1h"` means the rule runs every hour. Must be less than or equal to half the duration of the lookback period,
*/
export type Interval = z.infer<typeof Interval>;
export const Interval = z.string().regex(/^[1-9]\d*[smh]$/);

View file

@ -31,7 +31,7 @@ components:
type: string
fieldHistoryLength:
type: integer
lookbackPeriod:
lookbackPeriod:
type: string
default: 24h
pattern: '[smdh]$'
@ -42,15 +42,15 @@ components:
default: 180s
pattern: '[smdh]$'
frequency:
type: string
type: string
default: 1m
pattern: '[smdh]$'
delay:
type: string
default: 1m
pattern: '[smdh]$'
default: 1m
pattern: '[smdh]$'
docsPerSecond:
type: integer
type: integer
error:
type: object
@ -62,7 +62,7 @@ components:
- stopped
- updating
- error
EngineComponentStatus:
type: object
required:
@ -70,13 +70,13 @@ components:
- installed
- resource
properties:
id:
id:
type: string
installed:
installed:
type: boolean
resource:
$ref: '#/components/schemas/EngineComponentResource'
health:
health:
type: string
enum:
- green
@ -134,6 +134,6 @@ components:
- response
Interval:
type: string
description: Interval in which enrich policy runs. For example, `"1h"` means the rule runs every hour.
description: Interval in which enrich policy runs. For example, `"1h"` means the rule runs every hour. Must be less than or equal to half the duration of the lookback period,
pattern: '^[1-9]\d*[smh]$' # any number except zero followed by one of the suffixes 's', 'm', 'h'
example: '1h'

View file

@ -37,7 +37,7 @@ paths:
type: string
description: The field to use as the timestamp.
default: '@timestamp'
lookbackPeriod:
lookbackPeriod:
type: string
default: 24h
pattern: '[smdh]$'
@ -48,18 +48,18 @@ paths:
pattern: '[smdh]$'
description: The timeout for initializing the aggregating transform.
frequency:
type: string
type: string
default: 1m
pattern: '[smdh]$'
description: The frequency at which the transform will run.
delay:
type: string
default: 1m
pattern: '[smdh]$'
default: 1m
pattern: '[smdh]$'
description: The delay before the transform will run.
docsPerSecond:
type: integer
description: The number of documents per second to process.
type: integer
description: The number of documents per second to process.
responses:
'200':
description: Successful response
@ -74,3 +74,5 @@ paths:
type: array
items:
$ref: './common.schema.yaml#/components/schemas/EngineDescriptor'
'400':
description: Invalid request

View file

@ -39,7 +39,7 @@ paths:
type: string
description: The field to use as the timestamp for the entity type.
default: '@timestamp'
lookbackPeriod:
lookbackPeriod:
type: string
default: 24h
pattern: '[smdh]$'
@ -50,20 +50,18 @@ paths:
pattern: '[smdh]$'
description: The timeout for initializing the aggregating transform.
frequency:
type: string
type: string
default: 1m
pattern: '[smdh]$'
description: The frequency at which the transform will run.
delay:
type: string
default: 1m
pattern: '[smdh]$'
default: 1m
pattern: '[smdh]$'
description: The delay before the transform will run.
docsPerSecond:
type: integer
description: The number of documents per second to process.
type: integer
description: The number of documents per second to process.
responses:
'200':
description: Successful response
@ -71,4 +69,5 @@ paths:
application/json:
schema:
$ref: '../common.schema.yaml#/components/schemas/EngineDescriptor'
'400':
description: Invalid request

View file

@ -366,6 +366,8 @@ paths:
succeeded:
type: boolean
description: Successful response
'400':
description: Invalid request
summary: Initialize the Entity Store
tags:
- Security Entity Analytics API
@ -501,6 +503,8 @@ paths:
schema:
$ref: '#/components/schemas/EngineDescriptor'
description: Successful response
'400':
description: Invalid request
summary: Initialize an Entity Engine
tags:
- Security Entity Analytics API
@ -1299,7 +1303,8 @@ components:
Interval:
description: >-
Interval in which enrich policy runs. For example, `"1h"` means the rule
runs every hour.
runs every hour. Must be less than or equal to half the duration of the
lookback period,
example: 1h
pattern: ^[1-9]\d*[smh]$
type: string

View file

@ -366,6 +366,8 @@ paths:
succeeded:
type: boolean
description: Successful response
'400':
description: Invalid request
summary: Initialize the Entity Store
tags:
- Security Entity Analytics API
@ -501,6 +503,8 @@ paths:
schema:
$ref: '#/components/schemas/EngineDescriptor'
description: Successful response
'400':
description: Invalid request
summary: Initialize an Entity Engine
tags:
- Security Entity Analytics API
@ -1299,7 +1303,8 @@ components:
Interval:
description: >-
Interval in which enrich policy runs. For example, `"1h"` means the rule
runs every hour.
runs every hour. Must be less than or equal to half the duration of the
lookback period,
example: 1h
pattern: ^[1-9]\d*[smh]$
type: string

View file

@ -8,13 +8,13 @@
import type { IKibanaResponse, Logger } from '@kbn/core/server';
import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
import { transformError } from '@kbn/securitysolution-es-utils';
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
import { buildInitRequestBodyValidation } from './validation';
import type { InitEntityStoreResponse } from '../../../../../common/api/entity_analytics/entity_store/enable.gen';
import { InitEntityStoreRequestBody } from '../../../../../common/api/entity_analytics/entity_store/enable.gen';
import { API_VERSIONS, APP_ID } from '../../../../../common/constants';
import type { EntityAnalyticsRoutesDeps } from '../../types';
import { checkAndInitAssetCriticalityResources } from '../../asset_criticality/check_and_init_asset_criticality_resources';
import { InitEntityStoreRequestBody } from '../../../../../common/api/entity_analytics/entity_store/enable.gen';
export const enableEntityStoreRoute = (
router: EntityAnalyticsRoutesDeps['router'],
@ -36,7 +36,7 @@ export const enableEntityStoreRoute = (
version: API_VERSIONS.public.v1,
validate: {
request: {
body: buildRouteValidationWithZod(InitEntityStoreRequestBody),
body: buildInitRequestBodyValidation(InitEntityStoreRequestBody),
},
},
},

View file

@ -19,6 +19,7 @@ import {
import { API_VERSIONS, APP_ID } from '../../../../../common/constants';
import type { EntityAnalyticsRoutesDeps } from '../../types';
import { checkAndInitAssetCriticalityResources } from '../../asset_criticality/check_and_init_asset_criticality_resources';
import { buildInitRequestBodyValidation } from './validation';
export const initEntityEngineRoute = (
router: EntityAnalyticsRoutesDeps['router'],
@ -41,7 +42,7 @@ export const initEntityEngineRoute = (
validate: {
request: {
params: buildRouteValidationWithZod(InitEntityEngineRequestParams),
body: buildRouteValidationWithZod(InitEntityEngineRequestBody),
body: buildInitRequestBodyValidation(InitEntityEngineRequestBody),
},
},
},

View file

@ -0,0 +1,82 @@
/*
* 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 { validateInitializationRequestBody } from './validation';
import type { InitEntityEngineRequestBody } from '../../../../../common/api/entity_analytics';
import { BadRequestError } from '@kbn/securitysolution-es-utils';
describe('entity store initialization request validation', () => {
const defaultRequestBody: InitEntityEngineRequestBody = {
fieldHistoryLength: 10,
timestampField: '@timestamp',
lookbackPeriod: '24h',
timeout: '180s',
frequency: '1m',
delay: '1m',
enrichPolicyExecutionInterval: '1h',
};
it('should allow the default values (24 hour lookback period, 1 hour enrich policy interval)', () => {
expect(validateInitializationRequestBody(defaultRequestBody)).toBeUndefined();
});
it('should allow the enrich policy interval to be exactly half the lookback period', () => {
expect(
validateInitializationRequestBody({
...defaultRequestBody,
lookbackPeriod: '24h',
enrichPolicyExecutionInterval: '12h',
})
).toBeUndefined();
});
it('should allow the enrich policy interval to be barely less than half the lookback period', () => {
expect(
validateInitializationRequestBody({
...defaultRequestBody,
lookbackPeriod: '24h',
enrichPolicyExecutionInterval: '11h',
})
).toBeUndefined();
});
it('should not allow the lookback period and enrich policy interval to be the same', () => {
expect(
validateInitializationRequestBody({
...defaultRequestBody,
lookbackPeriod: '1h',
enrichPolicyExecutionInterval: '1h',
})
).toEqual(
new BadRequestError(
'The enrich policy execution interval must be less than or equal to half the duration of the lookback period.'
)
);
});
it('should not allow the enrich policy interval to be greater than the lookback period', () => {
expect(
validateInitializationRequestBody({
...defaultRequestBody,
lookbackPeriod: '1h',
enrichPolicyExecutionInterval: '2h',
})
).toEqual(
new BadRequestError(
'The enrich policy execution interval must be less than or equal to half the duration of the lookback period.'
)
);
});
it('should not allow the enrich policy interval to be more than half the lookback period', () => {
expect(
validateInitializationRequestBody({
...defaultRequestBody,
lookbackPeriod: '24h',
enrichPolicyExecutionInterval: '13h',
})
).toEqual(
new BadRequestError(
'The enrich policy execution interval must be less than or equal to half the duration of the lookback period.'
)
);
});
});

View file

@ -0,0 +1,47 @@
/*
* 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 { parseDuration } from '@kbn/alerting-plugin/common';
import { BadRequestError } from '@kbn/securitysolution-es-utils';
import type { RouteValidationFunction, RouteValidationResultFactory } from '@kbn/core-http-server';
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
import type { TypeOf, ZodType } from '@kbn/zod';
import type { InitEntityEngineRequestBody } from '../../../../../common/api/entity_analytics';
export const buildInitRequestBodyValidation =
<ZodSchema extends ZodType, Type = TypeOf<ZodSchema>>(
schema: ZodSchema
): RouteValidationFunction<Type> =>
(inputValue: unknown, validationResultFactory: RouteValidationResultFactory) => {
const zodValidationResult = buildRouteValidationWithZod(schema)(
inputValue,
validationResultFactory
);
if (zodValidationResult.error) return zodValidationResult;
const additionalValidationResult = validateInitializationRequestBody(zodValidationResult.value);
if (additionalValidationResult)
return validationResultFactory.badRequest(additionalValidationResult);
return zodValidationResult;
};
/**
* Validations performed:
* - Ensures that the enrich policy execution interval is less than or equal to half the duration of the lookback period,
* as the execution policy must run successfully at least once within the lookback period in order to ensure no loss of
* data
*/
export const validateInitializationRequestBody = (requestBody: InitEntityEngineRequestBody) => {
const { lookbackPeriod, enrichPolicyExecutionInterval } = requestBody;
if (!lookbackPeriod || !enrichPolicyExecutionInterval) return;
const lookbackPeriodMillis = parseDuration(lookbackPeriod);
const enrichPolicyExecutionIntervalMillis = parseDuration(enrichPolicyExecutionInterval);
if (enrichPolicyExecutionIntervalMillis > lookbackPeriodMillis / 2) {
return new BadRequestError(
'The enrich policy execution interval must be less than or equal to half the duration of the lookback period.'
);
}
};