[Security Solution] Migrate rules management endpoints to OpenAPI and code generation (#165091)

**Part of: https://github.com/elastic/security-team/issues/7491**

## Summary

Migrated Detection Engine APIs to OpenAPI schema and code generation:

- [x] `PUT /api/detection_engine/rules/prepackaged`
- [x] `POST /api/detection_engine/rules/_export`
- [x] `POST /api/detection_engine/rules/_import`
- [x] `GET /api/detection_engine/tags`
- [x] `GET /internal/detection_engine/rules/{ruleId}/execution/results`
This commit is contained in:
Dmitrii Shevchenko 2023-10-02 16:10:15 +02:00 committed by GitHub
parent b05397366e
commit 06502b9cdc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
80 changed files with 2100 additions and 992 deletions

View file

@ -43,9 +43,17 @@
{{~/if~}}
{{~#if (eq type "integer")}}
{{> zod_schema_item}}
z.coerce.number().int()
{{~#if minimum includeZero=true}}.min({{minimum}}){{/if~}}
{{~#if maximum includeZero=true}}.max({{maximum}}){{/if~}}
{{~#if (eq requiredBool false)}}.optional(){{/if~}}
{{~#if (defined default)}}.default({{{toJSON default}}}){{/if~}}
{{~/if~}}
{{~#if (eq type "number")}}
{{> zod_schema_item}}
z.coerce.number()
{{~#if minimum includeZero=true}}.min({{minimum}}){{/if~}}
{{~#if maximum includeZero=true}}.max({{maximum}}){{/if~}}
{{~#if (eq requiredBool false)}}.optional(){{/if~}}
{{~#if (defined default)}}.default({{{toJSON default}}}){{/if~}}
{{~/if~}}

View file

@ -0,0 +1,29 @@
/*
* 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 { z } from 'zod';
/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*/
import { RuleSignatureId } from './rule_schema/common_attributes.gen';
export type ErrorSchema = z.infer<typeof ErrorSchema>;
export const ErrorSchema = z
.object({
id: z.string().optional(),
rule_id: RuleSignatureId.optional(),
list_id: z.string().min(1).optional(),
item_id: z.string().min(1).optional(),
error: z.object({
status_code: z.number().int().min(400),
message: z.string(),
}),
})
.strict();

View file

@ -4,12 +4,13 @@ info:
version: 'not applicable'
paths: {}
components:
x-codegen-enabled: false
x-codegen-enabled: true
schemas:
ErrorSchema:
type: object
required:
- error
additionalProperties: false
properties:
id:
type: string

View file

@ -8,14 +8,13 @@
import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils';
import { left } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
import type { ErrorSchema } from './error_schema';
import { errorSchema } from './error_schema';
import { ErrorSchema } from './error_schema';
import { getErrorSchemaMock } from './error_schema.mock';
describe('error_schema', () => {
test('it should validate an error with a UUID given for id', () => {
const error = getErrorSchemaMock();
const decoded = errorSchema.decode(getErrorSchemaMock());
const decoded = ErrorSchema.decode(getErrorSchemaMock());
const checked = exactCheck(error, decoded);
const message = pipe(checked, foldLeftRight);
@ -25,7 +24,7 @@ describe('error_schema', () => {
test('it should validate an error with a plain string given for id since sometimes we echo the user id which might not be a UUID back out to them', () => {
const error = getErrorSchemaMock('fake id');
const decoded = errorSchema.decode(error);
const decoded = ErrorSchema.decode(error);
const checked = exactCheck(error, decoded);
const message = pipe(checked, foldLeftRight);
@ -37,7 +36,7 @@ describe('error_schema', () => {
type InvalidError = ErrorSchema & { invalid_extra_data?: string };
const error: InvalidError = getErrorSchemaMock();
error.invalid_extra_data = 'invalid_extra_data';
const decoded = errorSchema.decode(error);
const decoded = ErrorSchema.decode(error);
const checked = exactCheck(error, decoded);
const message = pipe(checked, foldLeftRight);
@ -49,7 +48,7 @@ describe('error_schema', () => {
const error = getErrorSchemaMock();
// @ts-expect-error
delete error.error;
const decoded = errorSchema.decode(error);
const decoded = ErrorSchema.decode(error);
const checked = exactCheck(error, decoded);
const message = pipe(checked, foldLeftRight);

View file

@ -31,5 +31,5 @@ const required = t.exact(
})
);
export const errorSchema = t.intersection([partial, required]);
export type ErrorSchema = t.TypeOf<typeof errorSchema>;
export const ErrorSchema = t.intersection([partial, required]);
export type ErrorSchema = t.TypeOf<typeof ErrorSchema>;

View file

@ -0,0 +1,207 @@
/*
* 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 { z } from 'zod';
/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*/
/**
* A universally unique identifier
*/
export type UUID = z.infer<typeof UUID>;
export const UUID = z.string();
export type RuleObjectId = z.infer<typeof RuleObjectId>;
export const RuleObjectId = z.string();
/**
* Could be any string, not necessarily a UUID
*/
export type RuleSignatureId = z.infer<typeof RuleSignatureId>;
export const RuleSignatureId = z.string();
export type RuleName = z.infer<typeof RuleName>;
export const RuleName = z.string().min(1);
export type RuleDescription = z.infer<typeof RuleDescription>;
export const RuleDescription = z.string().min(1);
export type RuleVersion = z.infer<typeof RuleVersion>;
export const RuleVersion = z.string();
export type IsRuleImmutable = z.infer<typeof IsRuleImmutable>;
export const IsRuleImmutable = z.boolean();
export type IsRuleEnabled = z.infer<typeof IsRuleEnabled>;
export const IsRuleEnabled = z.boolean();
export type RuleTagArray = z.infer<typeof RuleTagArray>;
export const RuleTagArray = z.array(z.string());
export type RuleMetadata = z.infer<typeof RuleMetadata>;
export const RuleMetadata = z.object({});
export type RuleLicense = z.infer<typeof RuleLicense>;
export const RuleLicense = z.string();
export type RuleAuthorArray = z.infer<typeof RuleAuthorArray>;
export const RuleAuthorArray = z.array(z.string());
export type RuleFalsePositiveArray = z.infer<typeof RuleFalsePositiveArray>;
export const RuleFalsePositiveArray = z.array(z.string());
export type RuleReferenceArray = z.infer<typeof RuleReferenceArray>;
export const RuleReferenceArray = z.array(z.string());
export type InvestigationGuide = z.infer<typeof InvestigationGuide>;
export const InvestigationGuide = z.string();
export type SetupGuide = z.infer<typeof SetupGuide>;
export const SetupGuide = z.string();
export type BuildingBlockType = z.infer<typeof BuildingBlockType>;
export const BuildingBlockType = z.string();
export type AlertsIndex = z.infer<typeof AlertsIndex>;
export const AlertsIndex = z.string();
export type AlertsIndexNamespace = z.infer<typeof AlertsIndexNamespace>;
export const AlertsIndexNamespace = z.string();
export type MaxSignals = z.infer<typeof MaxSignals>;
export const MaxSignals = z.number().int().min(1);
export type Subtechnique = z.infer<typeof Subtechnique>;
export const Subtechnique = z.object({
/**
* Subtechnique ID
*/
id: z.string(),
/**
* Subtechnique name
*/
name: z.string(),
/**
* Subtechnique reference
*/
reference: z.string(),
});
export type Technique = z.infer<typeof Technique>;
export const Technique = z.object({
/**
* Technique ID
*/
id: z.string(),
/**
* Technique name
*/
name: z.string(),
/**
* Technique reference
*/
reference: z.string(),
/**
* Array containing more specific information on the attack technique
*/
subtechnique: z.array(Subtechnique).optional(),
});
export type Threat = z.infer<typeof Threat>;
export const Threat = z.object({
/**
* Relevant attack framework
*/
framework: z.string(),
tactic: z.object({
/**
* Tactic ID
*/
id: z.string(),
/**
* Tactic name
*/
name: z.string(),
/**
* Tactic reference
*/
reference: z.string(),
}),
/**
* Array containing information on the attack techniques (optional)
*/
technique: z.array(Technique).optional(),
});
export type ThreatArray = z.infer<typeof ThreatArray>;
export const ThreatArray = z.array(Threat);
export type IndexPatternArray = z.infer<typeof IndexPatternArray>;
export const IndexPatternArray = z.array(z.string());
export type DataViewId = z.infer<typeof DataViewId>;
export const DataViewId = z.string();
export type RuleQuery = z.infer<typeof RuleQuery>;
export const RuleQuery = z.string();
export type RuleFilterArray = z.infer<typeof RuleFilterArray>;
export const RuleFilterArray = z.array(z.object({}));
export type RuleNameOverride = z.infer<typeof RuleNameOverride>;
export const RuleNameOverride = z.string();
export type TimestampOverride = z.infer<typeof TimestampOverride>;
export const TimestampOverride = z.string();
export type TimestampOverrideFallbackDisabled = z.infer<typeof TimestampOverrideFallbackDisabled>;
export const TimestampOverrideFallbackDisabled = z.boolean();
export type RequiredField = z.infer<typeof RequiredField>;
export const RequiredField = z.object({
name: z.string().min(1).optional(),
type: z.string().min(1).optional(),
ecs: z.boolean().optional(),
});
export type RequiredFieldArray = z.infer<typeof RequiredFieldArray>;
export const RequiredFieldArray = z.array(RequiredField);
export type TimelineTemplateId = z.infer<typeof TimelineTemplateId>;
export const TimelineTemplateId = z.string();
export type TimelineTemplateTitle = z.infer<typeof TimelineTemplateTitle>;
export const TimelineTemplateTitle = z.string();
export type SavedObjectResolveOutcome = z.infer<typeof SavedObjectResolveOutcome>;
export const SavedObjectResolveOutcome = z.enum(['exactMatch', 'aliasMatch', 'conflict']);
export const SavedObjectResolveOutcomeEnum = SavedObjectResolveOutcome.enum;
export type SavedObjectResolveOutcomeEnum = typeof SavedObjectResolveOutcome.enum;
export type SavedObjectResolveAliasTargetId = z.infer<typeof SavedObjectResolveAliasTargetId>;
export const SavedObjectResolveAliasTargetId = z.string();
export type SavedObjectResolveAliasPurpose = z.infer<typeof SavedObjectResolveAliasPurpose>;
export const SavedObjectResolveAliasPurpose = z.enum([
'savedObjectConversion',
'savedObjectImport',
]);
export const SavedObjectResolveAliasPurposeEnum = SavedObjectResolveAliasPurpose.enum;
export type SavedObjectResolveAliasPurposeEnum = typeof SavedObjectResolveAliasPurpose.enum;
export type RelatedIntegration = z.infer<typeof RelatedIntegration>;
export const RelatedIntegration = z.object({
package: z.string().min(1),
version: z.string().min(1),
integration: z.string().min(1).optional(),
});
export type RelatedIntegrationArray = z.infer<typeof RelatedIntegrationArray>;
export const RelatedIntegrationArray = z.array(RelatedIntegration);

View file

@ -4,7 +4,7 @@ info:
version: 'not applicable'
paths: {}
components:
x-codegen-enabled: false
x-codegen-enabled: true
schemas:
UUID:
type: string

View file

@ -0,0 +1,547 @@
/*
* 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 { z } from 'zod';
/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*/
export type Action = z.infer<typeof Action>;
export const Action = z.object({
/**
* The action type used for sending notifications.
*/
action_type_id: z.string(),
/**
* Optionally groups actions by use cases. Use `default` for alert notifications.
*/
group: z.string(),
/**
* The connector ID.
*/
id: z.string(),
/**
* Object containing the allowed connector fields, which varies according to the connector type.
*/
params: z.object({}),
uuid: z.string().optional(),
/**
* TODO implement the schema type
*/
alerts_filter: z.object({}).optional(),
/**
* TODO implement the schema type
*/
frequency: z.object({}).optional(),
});
export type AlertSuppression = z.infer<typeof AlertSuppression>;
export const AlertSuppression = z.object({
group_by: z.array(z.string()).min(1).max(3),
duration: z
.object({
value: z.number().int().min(1),
unit: z.enum(['s', 'm', 'h']),
})
.optional(),
missing_fields_strategy: z.enum(['doNotSuppress', 'suppress']).optional(),
});
export type BaseRule = z.infer<typeof BaseRule>;
export const BaseRule = z.object({
/**
* Rule name
*/
name: z.string(),
/**
* Rule description
*/
description: z.string(),
/**
* Risk score (0 to 100)
*/
risk_score: z.number().int().min(0).max(100),
/**
* Severity of the rule
*/
severity: z.enum(['low', 'medium', 'high', 'critical']),
/**
* Sets the source field for the alert's signal.rule.name value
*/
rule_name_override: z.string().optional(),
/**
* Sets the time field used to query indices (optional)
*/
timestamp_override: z.string().optional(),
/**
* Timeline template ID
*/
timeline_id: z.string().optional(),
/**
* Timeline template title
*/
timeline_title: z.string().optional(),
outcome: z.enum(['exactMatch', 'aliasMatch', 'conflict']).optional(),
/**
* TODO
*/
alias_target_id: z.string().optional(),
/**
* TODO
*/
alias_purpose: z.enum(['savedObjectConversion', 'savedObjectImport']).optional(),
/**
* The rules license.
*/
license: z.string().optional(),
/**
* Notes to help investigate alerts produced by the rule.
*/
note: z.string().optional(),
/**
* Determines if the rule acts as a building block. By default, building-block alerts are not displayed in the UI. These rules are used as a foundation for other rules that do generate alerts. Its value must be default.
*/
building_block_type: z.string().optional(),
/**
* (deprecated) Has no effect.
*/
output_index: z.string().optional(),
/**
* Has no effect.
*/
namespace: z.string().optional(),
/**
* Stores rule metadata.
*/
meta: z.object({}).optional(),
/**
* Defines the interval on which a rule's actions are executed.
*/
throttle: z.string().optional(),
/**
* The rules version number. Defaults to 1.
*/
version: z.number().int().min(1).optional().default(1),
/**
* String array containing words and phrases to help categorize, filter, and search rules. Defaults to an empty array.
*/
tags: z.array(z.string()).optional().default([]),
/**
* Determines whether the rule is enabled. Defaults to true.
*/
enabled: z.boolean().optional().default(true),
/**
* Overrides generated alerts' risk_score with a value from the source event
*/
risk_score_mapping: z
.array(
z.object({
field: z.string(),
operator: z.enum(['equals']),
value: z.string(),
risk_score: z.number().int().min(0).max(100).optional(),
})
)
.optional()
.default([]),
/**
* Overrides generated alerts' severity with values from the source event
*/
severity_mapping: z
.array(
z.object({
field: z.string(),
operator: z.enum(['equals']),
severity: z.enum(['low', 'medium', 'high', 'critical']),
value: z.string(),
})
)
.optional()
.default([]),
/**
* Frequency of rule execution, using a date math range. For example, "1h" means the rule runs every hour. Defaults to 5m (5 minutes).
*/
interval: z.string().optional().default('5m'),
/**
* Time from which data is analyzed each time the rule executes, using a date math range. For example, now-4200s means the rule analyzes data from 70 minutes before its start time. Defaults to now-6m (analyzes data from 6 minutes before the start time).
*/
from: z.string().optional().default('now-6m'),
/**
* TODO
*/
to: z.string().optional().default('now'),
actions: z.array(Action).optional().default([]),
exceptions_list: z
.array(
z.object({
/**
* ID of the exception container
*/
id: z.string().min(1),
/**
* List ID of the exception container
*/
list_id: z.string().min(1),
/**
* The exception type
*/
type: z.enum([
'detection',
'rule_default',
'endpoint',
'endpoint_trusted_apps',
'endpoint_events',
'endpoint_host_isolation_exceptions',
'endpoint_blocklists',
]),
/**
* Determines the exceptions validity in rule's Kibana space
*/
namespace_type: z.enum(['agnostic', 'single']),
})
)
.optional()
.default([]),
author: z.array(z.string()).optional().default([]),
false_positives: z.array(z.string()).optional().default([]),
references: z.array(z.string()).optional().default([]),
max_signals: z.number().int().min(1).optional().default(100),
threat: z
.array(
z.object({
/**
* Relevant attack framework
*/
framework: z.string(),
tactic: z.object({
id: z.string(),
name: z.string(),
reference: z.string(),
}),
technique: z
.array(
z.object({
id: z.string(),
name: z.string(),
reference: z.string(),
subtechnique: z
.array(
z.object({
id: z.string(),
name: z.string(),
reference: z.string(),
})
)
.optional(),
})
)
.optional(),
})
)
.optional(),
});
export type QueryRule = z.infer<typeof QueryRule>;
export const QueryRule = BaseRule.and(
z.object({
/**
* Rule type
*/
type: z.enum(['query']),
index: z.array(z.string()).optional(),
data_view_id: z.string().optional(),
filters: z.array(z.unknown()).optional(),
saved_id: z.string().optional(),
/**
* TODO
*/
response_actions: z.array(z.object({})).optional(),
alert_suppression: AlertSuppression.optional(),
/**
* Query to execute
*/
query: z.string().optional().default(''),
/**
* Query language to use.
*/
language: z.enum(['kuery', 'lucene']).optional().default('kuery'),
})
);
export type SavedQueryRule = z.infer<typeof SavedQueryRule>;
export const SavedQueryRule = BaseRule.and(
z.object({
/**
* Rule type
*/
type: z.enum(['saved_query']),
index: z.array(z.string()).optional(),
data_view_id: z.string().optional(),
filters: z.array(z.unknown()).optional(),
saved_id: z.string(),
/**
* TODO
*/
response_actions: z.array(z.object({})).optional(),
alert_suppression: AlertSuppression.optional(),
/**
* Query to execute
*/
query: z.string().optional(),
/**
* Query language to use.
*/
language: z.enum(['kuery', 'lucene']).optional().default('kuery'),
})
);
export type ThresholdRule = z.infer<typeof ThresholdRule>;
export const ThresholdRule = BaseRule.and(
z.object({
/**
* Rule type
*/
type: z.enum(['threshold']),
query: z.string(),
threshold: z.object({
/**
* Field to aggregate on
*/
field: z.union([z.string(), z.array(z.string())]),
/**
* Threshold value
*/
value: z.number().int().min(1).optional(),
cardinality: z
.array(
z.object({
field: z.string().optional(),
value: z.number().int().min(0).optional(),
})
)
.optional(),
}),
index: z.array(z.string()).optional(),
data_view_id: z.string().optional(),
filters: z.array(z.unknown()).optional(),
saved_id: z.string().optional(),
/**
* Query language to use.
*/
language: z.enum(['kuery', 'lucene']).optional().default('kuery'),
})
);
export type ThreatMatchRule = z.infer<typeof ThreatMatchRule>;
export const ThreatMatchRule = BaseRule.and(
z.object({
/**
* Rule type
*/
type: z.enum(['threat_match']),
query: z.string(),
/**
* Query to execute
*/
threat_query: z.string(),
threat_mapping: z
.array(
z.object({
entries: z
.array(
z.object({
field: z.string().min(1).optional(),
type: z.enum(['mapping']).optional(),
value: z.string().min(1).optional(),
})
)
.optional(),
})
)
.min(1),
threat_index: z.array(z.string()),
index: z.array(z.string()).optional(),
data_view_id: z.string().optional(),
filters: z.array(z.unknown()).optional(),
saved_id: z.string().optional(),
threat_filters: z.array(z.unknown()).optional(),
/**
* Defines the path to the threat indicator in the indicator documents (optional)
*/
threat_indicator_path: z.string().optional(),
/**
* Query language to use.
*/
threat_language: z.enum(['kuery', 'lucene']).optional(),
concurrent_searches: z.number().int().min(1).optional(),
items_per_search: z.number().int().min(1).optional(),
/**
* Query language to use.
*/
language: z.enum(['kuery', 'lucene']).optional().default('kuery'),
})
);
export type MlRule = z.infer<typeof MlRule>;
export const MlRule = BaseRule.and(
z.object({
/**
* Rule type
*/
type: z.enum(['machine_learning']),
/**
* Anomaly threshold
*/
anomaly_threshold: z.number().int().min(0),
/**
* Machine learning job ID
*/
machine_learning_job_id: z.union([z.string(), z.array(z.string()).min(1)]),
})
);
export type EqlRule = z.infer<typeof EqlRule>;
export const EqlRule = BaseRule.and(
z.object({
/**
* Rule type
*/
type: z.enum(['eql']),
language: z.enum(['eql']),
/**
* EQL query to execute
*/
query: z.string(),
index: z.array(z.string()).optional(),
data_view_id: z.string().optional(),
filters: z.array(z.unknown()).optional(),
/**
* Contains the event classification
*/
event_category_field: z.string().optional(),
/**
* Sets a secondary field for sorting events
*/
tiebreaker_field: z.string().optional(),
/**
* Contains the event timestamp used for sorting a sequence of events
*/
timestamp_field: z.string().optional(),
})
);
export type NewTermsRule = z.infer<typeof NewTermsRule>;
export const NewTermsRule = BaseRule.and(
z.object({
/**
* Rule type
*/
type: z.enum(['new_terms']),
query: z.string(),
new_terms_fields: z.array(z.string()).min(1).max(3),
history_window_size: z.string().min(1).optional(),
index: z.array(z.string()).optional(),
data_view_id: z.string().optional(),
filters: z.array(z.unknown()).optional(),
language: z.enum(['kuery', 'lucene']).optional().default('kuery'),
})
);
export type Rule = z.infer<typeof Rule>;
export const Rule = z.union([
QueryRule,
SavedQueryRule,
ThresholdRule,
ThreatMatchRule,
MlRule,
EqlRule,
NewTermsRule,
]);
/**
* Defines the maximum interval in which a rule's actions are executed.
*/
export type Throttle = z.infer<typeof Throttle>;
export const Throttle = z.enum(['rule', '1h', '1d', '7d']);
export const ThrottleEnum = Throttle.enum;
export type ThrottleEnum = typeof Throttle.enum;
export type Subtechnique = z.infer<typeof Subtechnique>;
export const Subtechnique = z.object({
/**
* Subtechnique ID
*/
id: z.string(),
/**
* Subtechnique name
*/
name: z.string(),
/**
* Subtechnique reference
*/
reference: z.string(),
});
export type Technique = z.infer<typeof Technique>;
export const Technique = z.object({
/**
* Technique ID
*/
id: z.string(),
/**
* Technique name
*/
name: z.string(),
/**
* Technique reference
*/
reference: z.string(),
/**
* Array containing more specific information on the attack technique
*/
subtechnique: z.array(Subtechnique).optional(),
});
export type Threat = z.infer<typeof Threat>;
export const Threat = z.object({
/**
* Relevant attack framework
*/
framework: z.string(),
tactic: z.object({
/**
* Tactic ID
*/
id: z.string(),
/**
* Tactic name
*/
name: z.string(),
/**
* Tactic reference
*/
reference: z.string(),
}),
/**
* Array containing information on the attack techniques (optional)
*/
technique: z.array(Technique).optional(),
});
export type RuleResponse = z.infer<typeof RuleResponse>;
export const RuleResponse = z.object({});
export type RuleCreateProps = z.infer<typeof RuleCreateProps>;
export const RuleCreateProps = z.object({});
export type RuleUpdateProps = z.infer<typeof RuleUpdateProps>;
export const RuleUpdateProps = z.object({});
export type RulePatchProps = z.infer<typeof RulePatchProps>;
export const RulePatchProps = z.object({});

View file

@ -4,98 +4,8 @@ info:
version: 'not applicable'
paths: {}
components:
x-codegen-enabled: false
x-codegen-enabled: true
schemas:
SortOrder:
type: string
enum:
- asc
- desc
RuleExecutionStatus:
type: string
description: |-
Custom execution status of Security rules that is different from the status used in the Alerting Framework. We merge our custom status with the Framework's status to determine the resulting status of a rule.
- going to run - @deprecated Replaced by the 'running' status but left for backwards compatibility with rule execution events already written to Event Log in the prior versions of Kibana. Don't use when writing rule status changes.
- running - Rule execution started but not reached any intermediate or final status.
- partial failure - Rule can partially fail for various reasons either in the middle of an execution (in this case we update its status right away) or in the end of it. So currently this status can be both intermediate and final at the same time. A typical reason for a partial failure: not all the indices that the rule searches over actually exist.
- failed - Rule failed to execute due to unhandled exception or a reason defined in the business logic of its executor function.
- succeeded - Rule executed successfully without any issues. Note: this status is just an indication of a rule's "health". The rule might or might not generate any alerts despite of it.
enum:
- going to run
- running
- partial failure
- failed
- succeeded
RuleExecutionResult:
type: object
description: |-
Rule execution result is an aggregate that groups plain rule execution events by execution UUID.
properties:
execution_uuid:
type: string
timestamp:
type: string
format: date-time
duration_ms:
type: integer
status:
type: string
message:
type: string
num_active_alerts:
type: integer
num_new_alerts:
type: integer
num_recovered_alerts:
type: integer
num_triggered_actions:
type: integer
num_succeeded_actions:
type: integer
num_errored_actions:
type: integer
total_search_duration_ms:
type: integer
es_search_duration_ms:
type: integer
schedule_delay_ms:
type: integer
timed_out:
type: boolean
indexing_duration_ms:
type: integer
search_duration_ms:
type: integer
gap_duration_s:
type: integer
security_status:
type: string
security_message:
type: string
required:
- execution_uuid
- timestamp
- duration_ms
- status
- message
- num_active_alerts
- num_new_alerts
- num_recovered_alerts
- num_triggered_actions
- num_succeeded_actions
- num_errored_actions
- total_search_duration_ms
- es_search_duration_ms
- schedule_delay_ms
- timed_out
- indexing_duration_ms
- search_duration_ms
- gap_duration_s
- security_status
- security_message
Action:
type: object
properties:

View file

@ -0,0 +1,18 @@
/*
* 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 { z } from 'zod';
/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*/
export type SortOrder = z.infer<typeof SortOrder>;
export const SortOrder = z.enum(['asc', 'desc']);
export const SortOrderEnum = SortOrder.enum;
export type SortOrderEnum = typeof SortOrder.enum;

View file

@ -0,0 +1,13 @@
openapi: 3.0.0
info:
title: Sorting Schema
version: 'not applicable'
paths: {}
components:
x-codegen-enabled: true
schemas:
SortOrder:
type: string
enum:
- asc
- desc

View file

@ -20,5 +20,5 @@ const required = t.exact(
})
);
export const warningSchema = t.intersection([partial, required]);
export type WarningSchema = t.TypeOf<typeof warningSchema>;
export const WarningSchema = t.intersection([partial, required]);
export type WarningSchema = t.TypeOf<typeof WarningSchema>;

View file

@ -7,7 +7,7 @@
export * from './get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.gen';
export * from './get_prebuilt_rules_status/get_prebuilt_rules_status_route';
export * from './install_prebuilt_rules_and_timelines/install_prebuilt_rules_and_timelines_route';
export * from './install_prebuilt_rules_and_timelines/install_prebuilt_rules_and_timelines_route.gen';
export * from './perform_rule_installation/perform_rule_installation_route';
export * from './perform_rule_upgrade/perform_rule_upgrade_route';
export * from './review_rule_installation/review_rule_installation_route';

View file

@ -0,0 +1,37 @@
/*
* 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 { z } from 'zod';
/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*/
export type InstallPrebuiltRulesAndTimelinesResponse = z.infer<
typeof InstallPrebuiltRulesAndTimelinesResponse
>;
export const InstallPrebuiltRulesAndTimelinesResponse = z
.object({
/**
* The number of rules installed
*/
rules_installed: z.number().int().min(0),
/**
* The number of rules updated
*/
rules_updated: z.number().int().min(0),
/**
* The number of timelines installed
*/
timelines_installed: z.number().int().min(0),
/**
* The number of timelines updated
*/
timelines_updated: z.number().int().min(0),
})
.strict();

View file

@ -5,8 +5,8 @@ info:
paths:
/api/detection_engine/rules/prepackaged:
put:
operationId: InstallPrebuiltRules
x-codegen-enabled: false
operationId: InstallPrebuiltRulesAndTimelines
x-codegen-enabled: true
summary: Installs all Elastic prebuilt rules and timelines
tags:
- Prebuilt Rules API
@ -17,6 +17,7 @@ paths:
application/json:
schema:
type: object
additionalProperties: false
properties:
rules_installed:
type: integer

View file

@ -5,11 +5,9 @@
* 2.0.
*/
import { left } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils';
import { InstallPrebuiltRulesAndTimelinesResponse } from './install_prebuilt_rules_and_timelines_route';
import { stringifyZodError } from '@kbn/securitysolution-es-utils';
import { expectParseError, expectParseSuccess } from '../../../../test/zod_helpers';
import { InstallPrebuiltRulesAndTimelinesResponse } from './install_prebuilt_rules_and_timelines_route.gen';
describe('Install prebuilt rules and timelines response schema', () => {
test('it should validate an empty prepackaged response with defaults', () => {
@ -19,12 +17,9 @@ describe('Install prebuilt rules and timelines response schema', () => {
timelines_installed: 0,
timelines_updated: 0,
};
const decoded = InstallPrebuiltRulesAndTimelinesResponse.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
const result = InstallPrebuiltRulesAndTimelinesResponse.safeParse(payload);
expectParseSuccess(result);
expect(result.data).toEqual(payload);
});
test('it should not validate an extra invalid field added', () => {
@ -35,12 +30,11 @@ describe('Install prebuilt rules and timelines response schema', () => {
timelines_installed: 0,
timelines_updated: 0,
};
const decoded = InstallPrebuiltRulesAndTimelinesResponse.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_field"']);
expect(message.schema).toEqual({});
const result = InstallPrebuiltRulesAndTimelinesResponse.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toEqual(
"Unrecognized key(s) in object: 'invalid_field'"
);
});
test('it should NOT validate an empty prepackaged response with a negative "rules_installed" number', () => {
@ -50,14 +44,11 @@ describe('Install prebuilt rules and timelines response schema', () => {
timelines_installed: 0,
timelines_updated: 0,
};
const decoded = InstallPrebuiltRulesAndTimelinesResponse.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "-1" supplied to "rules_installed"',
]);
expect(message.schema).toEqual({});
const result = InstallPrebuiltRulesAndTimelinesResponse.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toEqual(
'rules_installed: Number must be greater than or equal to 0'
);
});
test('it should NOT validate an empty prepackaged response with a negative "rules_updated"', () => {
@ -67,14 +58,11 @@ describe('Install prebuilt rules and timelines response schema', () => {
timelines_installed: 0,
timelines_updated: 0,
};
const decoded = InstallPrebuiltRulesAndTimelinesResponse.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "-1" supplied to "rules_updated"',
]);
expect(message.schema).toEqual({});
const result = InstallPrebuiltRulesAndTimelinesResponse.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toEqual(
'rules_updated: Number must be greater than or equal to 0'
);
});
test('it should NOT validate an empty prepackaged response if "rules_installed" is not there', () => {
@ -86,14 +74,9 @@ describe('Install prebuilt rules and timelines response schema', () => {
};
// @ts-expect-error
delete payload.rules_installed;
const decoded = InstallPrebuiltRulesAndTimelinesResponse.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "rules_installed"',
]);
expect(message.schema).toEqual({});
const result = InstallPrebuiltRulesAndTimelinesResponse.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toEqual('rules_installed: Required');
});
test('it should NOT validate an empty prepackaged response if "rules_updated" is not there', () => {
@ -105,13 +88,8 @@ describe('Install prebuilt rules and timelines response schema', () => {
};
// @ts-expect-error
delete payload.rules_updated;
const decoded = InstallPrebuiltRulesAndTimelinesResponse.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "rules_updated"',
]);
expect(message.schema).toEqual({});
const result = InstallPrebuiltRulesAndTimelinesResponse.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toEqual('rules_updated: Required');
});
});

View file

@ -1,22 +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 * as t from 'io-ts';
import { PositiveInteger } from '@kbn/securitysolution-io-ts-types';
export type InstallPrebuiltRulesAndTimelinesResponse = t.TypeOf<
typeof InstallPrebuiltRulesAndTimelinesResponse
>;
export const InstallPrebuiltRulesAndTimelinesResponse = t.exact(
t.type({
rules_installed: PositiveInteger,
rules_updated: PositiveInteger,
timelines_installed: PositiveInteger,
timelines_updated: PositiveInteger,
})
);

View file

@ -7,7 +7,7 @@
import * as t from 'io-ts';
import { RuleResponse, errorSchema } from '../../model';
import { RuleResponse, ErrorSchema } from '../../model';
export type BulkCrudRulesResponse = t.TypeOf<typeof BulkCrudRulesResponse>;
export const BulkCrudRulesResponse = t.array(t.union([RuleResponse, errorSchema]));
export const BulkCrudRulesResponse = t.array(t.union([RuleResponse, ErrorSchema]));

View file

@ -0,0 +1,49 @@
/*
* 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 { z } from 'zod';
/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*/
import { RuleSignatureId } from '../../model/rule_schema/common_attributes.gen';
export type ExportRulesRequestQuery = z.infer<typeof ExportRulesRequestQuery>;
export const ExportRulesRequestQuery = z.object({
/**
* Determines whether a summary of the exported rules is returned.
*/
exclude_export_details: z.preprocess(
(value: unknown) => (typeof value === 'boolean' ? String(value) : value),
z
.enum(['true', 'false'])
.default('false')
.transform((value) => value === 'true')
),
/**
* File name for saving the exported rules.
*/
file_name: z.string().optional().default('export.ndjson'),
});
export type ExportRulesRequestQueryInput = z.input<typeof ExportRulesRequestQuery>;
export type ExportRulesRequestBody = z.infer<typeof ExportRulesRequestBody>;
export const ExportRulesRequestBody = z
.object({
/**
* Array of `rule_id` fields. Exports all rules when unspecified.
*/
objects: z.array(
z.object({
rule_id: RuleSignatureId,
})
),
})
.nullable();
export type ExportRulesRequestBodyInput = z.input<typeof ExportRulesRequestBody>;

View file

@ -7,7 +7,7 @@ paths:
summary: Exports rules to an `.ndjson` file
post:
operationId: ExportRules
x-codegen-enabled: false
x-codegen-enabled: true
summary: Export rules
description: Exports rules to an `.ndjson` file. The following configuration items are also included in the `.ndjson` file - Actions, Exception lists. Prebuilt rules cannot be exported.
tags:
@ -35,6 +35,7 @@ paths:
type: object
required:
- objects
nullable: true
properties:
objects:
type: array
@ -44,13 +45,13 @@ paths:
- rule_id
properties:
rule_id:
type: string
$ref: '../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleSignatureId'
description: Array of `rule_id` fields. Exports all rules when unspecified.
responses:
200:
description: Indicates a successful call.
content:
application/json:
application/ndjson:
schema:
type: string
format: binary

View file

@ -5,55 +5,43 @@
* 2.0.
*/
import { left } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils';
import { ExportRulesRequestBody, ExportRulesRequestQuery } from './export_rules_route';
import { stringifyZodError } from '@kbn/securitysolution-es-utils';
import { expectParseError, expectParseSuccess } from '../../../../test/zod_helpers';
import type { ExportRulesRequestQueryInput } from './export_rules_route.gen';
import { ExportRulesRequestBody, ExportRulesRequestQuery } from './export_rules_route.gen';
describe('Export rules request schema', () => {
describe('ExportRulesRequestBody', () => {
test('null value or absent values validate', () => {
const payload: Partial<ExportRulesRequestBody> = null;
const decoded = ExportRulesRequestBody.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
const result = ExportRulesRequestBody.safeParse(payload);
expectParseSuccess(result);
expect(result.data).toEqual(payload);
});
test('empty object does not validate', () => {
const payload = {};
const decoded = ExportRulesRequestBody.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "objects"',
'Invalid value "{}" supplied to "({| objects: Array<{| rule_id: string |}> |} | null)"',
]);
expect(message.schema).toEqual(payload);
const result = ExportRulesRequestBody.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toEqual('objects: Required');
});
test('empty object array does validate', () => {
const payload: ExportRulesRequestBody = { objects: [] };
const decoded = ExportRulesRequestBody.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
const result = ExportRulesRequestBody.safeParse(payload);
expectParseSuccess(result);
expect(result.data).toEqual(payload);
});
test('array with rule_id validates', () => {
const payload: ExportRulesRequestBody = { objects: [{ rule_id: 'test-1' }] };
const decoded = ExportRulesRequestBody.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
const result = ExportRulesRequestBody.safeParse(payload);
expectParseSuccess(result);
expect(result.data).toEqual(payload);
});
test('array with id does not validate as we do not allow that on purpose since we export rule_id', () => {
@ -61,94 +49,80 @@ describe('Export rules request schema', () => {
objects: [{ id: '4a7ff83d-3055-4bb2-ba68-587b9c6c15a4' }],
};
const decoded = ExportRulesRequestBody.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "objects,rule_id"',
'Invalid value "{"objects":[{"id":"4a7ff83d-3055-4bb2-ba68-587b9c6c15a4"}]}" supplied to "({| objects: Array<{| rule_id: string |}> |} | null)"',
]);
expect(message.schema).toEqual({});
const result = ExportRulesRequestBody.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toEqual('objects.0.rule_id: Required');
});
});
describe('ExportRulesRequestQuery', () => {
test('default value for file_name is export.ndjson and default for exclude_export_details is false', () => {
const payload: Partial<ExportRulesRequestQuery> = {};
const payload: ExportRulesRequestQueryInput = {};
const decoded = ExportRulesRequestQuery.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
const expected: ExportRulesRequestQuery = {
file_name: 'export.ndjson',
exclude_export_details: false,
};
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(expected);
const result = ExportRulesRequestQuery.safeParse(payload);
expectParseSuccess(result);
expect(result.data).toEqual(expected);
});
test('file_name validates', () => {
const payload: ExportRulesRequestQuery = {
const payload: ExportRulesRequestQueryInput = {
file_name: 'test.ndjson',
};
const decoded = ExportRulesRequestQuery.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
const expected: ExportRulesRequestQuery = {
file_name: 'test.ndjson',
exclude_export_details: false,
};
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(expected);
const result = ExportRulesRequestQuery.safeParse(payload);
expectParseSuccess(result);
expect(result.data).toEqual(expected);
});
test('file_name does not validate with a number', () => {
const payload: Omit<ExportRulesRequestQuery, 'file_name'> & { file_name: number } = {
const payload: Omit<ExportRulesRequestQueryInput, 'file_name'> & { file_name: number } = {
file_name: 10,
};
const decoded = ExportRulesRequestQuery.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "10" supplied to "file_name"',
]);
expect(message.schema).toEqual({});
const result = ExportRulesRequestQuery.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toEqual(
'file_name: Expected string, received number'
);
});
test('exclude_export_details validates with a boolean true', () => {
const payload: ExportRulesRequestQuery = {
const payload: ExportRulesRequestQueryInput = {
exclude_export_details: true,
};
const decoded = ExportRulesRequestQuery.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
const expected: ExportRulesRequestQuery = {
exclude_export_details: true,
file_name: 'export.ndjson',
};
expect(message.schema).toEqual(expected);
const result = ExportRulesRequestQuery.safeParse(payload);
expectParseSuccess(result);
expect(result.data).toEqual(expected);
});
test('exclude_export_details does not validate with a string', () => {
const payload: Omit<ExportRulesRequestQuery, 'exclude_export_details'> & {
const payload: Omit<ExportRulesRequestQueryInput, 'exclude_export_details'> & {
exclude_export_details: string;
} = {
exclude_export_details: 'invalid string',
};
const decoded = ExportRulesRequestQuery.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "invalid string" supplied to "exclude_export_details"',
]);
expect(message.schema).toEqual({});
const result = ExportRulesRequestQuery.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toEqual(
`exclude_export_details: Invalid enum value. Expected 'true' | 'false', received 'invalid string'`
);
});
});
});

View file

@ -1,31 +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 * as t from 'io-ts';
import { DefaultExportFileName } from '@kbn/securitysolution-io-ts-alerting-types';
import { DefaultStringBooleanFalse } from '@kbn/securitysolution-io-ts-types';
import { RuleSignatureId } from '../../model';
const ObjectsWithRuleId = t.array(t.exact(t.type({ rule_id: RuleSignatureId })));
/**
* Request body parameters of the API route.
*/
export type ExportRulesRequestBody = t.TypeOf<typeof ExportRulesRequestBody>;
export const ExportRulesRequestBody = t.union([
t.exact(t.type({ objects: ObjectsWithRuleId })),
t.null,
]);
/**
* Query string parameters of the API route.
*/
export type ExportRulesRequestQuery = t.TypeOf<typeof ExportRulesRequestQuery>;
export const ExportRulesRequestQuery = t.exact(
t.partial({ file_name: DefaultExportFileName, exclude_export_details: DefaultStringBooleanFalse })
);

View file

@ -0,0 +1,78 @@
/*
* 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 { z } from 'zod';
/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*/
import { ErrorSchema } from '../../model/error_schema.gen';
import { WarningSchema } from '../../model/warning_schema.gen';
export type ImportRulesRequestQuery = z.infer<typeof ImportRulesRequestQuery>;
export const ImportRulesRequestQuery = z.object({
/**
* Determines whether existing rules with the same `rule_id` are overwritten.
*/
overwrite: z.preprocess(
(value: unknown) => (typeof value === 'boolean' ? String(value) : value),
z
.enum(['true', 'false'])
.default('false')
.transform((value) => value === 'true')
),
/**
* Determines whether existing exception lists with the same `list_id` are overwritten.
*/
overwrite_exceptions: z.preprocess(
(value: unknown) => (typeof value === 'boolean' ? String(value) : value),
z
.enum(['true', 'false'])
.default('false')
.transform((value) => value === 'true')
),
/**
* Determines whether existing actions with the same `kibana.alert.rule.actions.id` are overwritten.
*/
overwrite_action_connectors: z.preprocess(
(value: unknown) => (typeof value === 'boolean' ? String(value) : value),
z
.enum(['true', 'false'])
.default('false')
.transform((value) => value === 'true')
),
/**
* Generates a new list ID for each imported exception list.
*/
as_new_list: z.preprocess(
(value: unknown) => (typeof value === 'boolean' ? String(value) : value),
z
.enum(['true', 'false'])
.default('false')
.transform((value) => value === 'true')
),
});
export type ImportRulesRequestQueryInput = z.input<typeof ImportRulesRequestQuery>;
export type ImportRulesResponse = z.infer<typeof ImportRulesResponse>;
export const ImportRulesResponse = z
.object({
exceptions_success: z.boolean(),
exceptions_success_count: z.number().int().min(0),
exceptions_errors: z.array(ErrorSchema),
rules_count: z.number().int().min(0),
success: z.boolean(),
success_count: z.number().int().min(0),
errors: z.array(ErrorSchema),
action_connectors_errors: z.array(ErrorSchema),
action_connectors_warnings: z.array(WarningSchema),
action_connectors_success: z.boolean(),
action_connectors_success_count: z.number().int().min(0),
})
.strict();

View file

@ -7,7 +7,7 @@ paths:
summary: Imports rules from an `.ndjson` file
post:
operationId: ImportRules
x-codegen-enabled: false
x-codegen-enabled: true
summary: Import rules
description: Imports rules from an `.ndjson` file, including actions and exception lists.
tags:
@ -45,6 +45,13 @@ paths:
schema:
type: boolean
default: false
- name: as_new_list
in: query
required: false
description: Generates a new list ID for each imported exception list.
schema:
type: boolean
default: false
responses:
200:
description: Indicates a successful call.
@ -52,3 +59,51 @@ paths:
application/json:
schema:
type: object
additionalProperties: false
required:
- exceptions_success
- exceptions_success_count
- exceptions_errors
- rules_count
- success
- success_count
- errors
- action_connectors_errors
- action_connectors_warnings
- action_connectors_success
- action_connectors_success_count
properties:
exceptions_success:
type: boolean
exceptions_success_count:
type: integer
minimum: 0
exceptions_errors:
type: array
items:
$ref: '../../model/error_schema.schema.yaml#/components/schemas/ErrorSchema'
rules_count:
type: integer
minimum: 0
success:
type: boolean
success_count:
type: integer
minimum: 0
errors:
type: array
items:
$ref: '../../model/error_schema.schema.yaml#/components/schemas/ErrorSchema'
action_connectors_errors:
type: array
items:
$ref: '../../model/error_schema.schema.yaml#/components/schemas/ErrorSchema'
action_connectors_warnings:
type: array
items:
$ref: '../../model/warning_schema.schema.yaml#/components/schemas/WarningSchema'
action_connectors_success:
type: boolean
action_connectors_success_count:
type: integer
minimum: 0

View file

@ -5,15 +5,10 @@
* 2.0.
*/
import { pipe } from 'fp-ts/lib/pipeable';
import type { Either } from 'fp-ts/lib/Either';
import { left } from 'fp-ts/lib/Either';
import type { Errors } from 'io-ts';
import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils';
import type { ErrorSchema } from '../../model/error_schema';
import { ImportRulesResponse } from './import_rules_route';
import { stringifyZodError } from '@kbn/securitysolution-es-utils';
import { expectParseError, expectParseSuccess } from '../../../../test/zod_helpers';
import type { ErrorSchema } from '../../model/error_schema.gen';
import { ImportRulesResponse } from './import_rules_route.gen';
describe('Import rules schema', () => {
describe('response schema', () => {
@ -31,12 +26,9 @@ describe('Import rules schema', () => {
action_connectors_errors: [],
action_connectors_warnings: [],
};
const decoded = ImportRulesResponse.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
const result = ImportRulesResponse.safeParse(payload);
expectParseSuccess(result);
expect(result.data).toEqual(payload);
});
test('it should validate an empty import response with a single error', () => {
@ -53,12 +45,9 @@ describe('Import rules schema', () => {
action_connectors_errors: [],
action_connectors_warnings: [],
};
const decoded = ImportRulesResponse.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
const result = ImportRulesResponse.safeParse(payload);
expectParseSuccess(result);
expect(result.data).toEqual(payload);
});
test('it should validate an empty import response with a single exceptions error', () => {
@ -75,12 +64,9 @@ describe('Import rules schema', () => {
action_connectors_errors: [],
action_connectors_warnings: [],
};
const decoded = ImportRulesResponse.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
const result = ImportRulesResponse.safeParse(payload);
expectParseSuccess(result);
expect(result.data).toEqual(payload);
});
test('it should validate an empty import response with two errors', () => {
@ -100,12 +86,9 @@ describe('Import rules schema', () => {
action_connectors_errors: [],
action_connectors_warnings: [],
};
const decoded = ImportRulesResponse.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
const result = ImportRulesResponse.safeParse(payload);
expectParseSuccess(result);
expect(result.data).toEqual(payload);
});
test('it should validate an empty import response with two exception errors', () => {
@ -125,12 +108,9 @@ describe('Import rules schema', () => {
action_connectors_errors: [],
action_connectors_warnings: [],
};
const decoded = ImportRulesResponse.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
const result = ImportRulesResponse.safeParse(payload);
expectParseSuccess(result);
expect(result.data).toEqual(payload);
});
test('it should NOT validate a success_count that is a negative number', () => {
@ -147,14 +127,11 @@ describe('Import rules schema', () => {
action_connectors_errors: [],
action_connectors_warnings: [],
};
const decoded = ImportRulesResponse.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "-1" supplied to "success_count"',
]);
expect(message.schema).toEqual({});
const result = ImportRulesResponse.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toEqual(
'success_count: Number must be greater than or equal to 0'
);
});
test('it should NOT validate a exceptions_success_count that is a negative number', () => {
@ -171,35 +148,14 @@ describe('Import rules schema', () => {
action_connectors_errors: [],
action_connectors_warnings: [],
};
const decoded = ImportRulesResponse.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "-1" supplied to "exceptions_success_count"',
]);
expect(message.schema).toEqual({});
const result = ImportRulesResponse.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toEqual(
'exceptions_success_count: Number must be greater than or equal to 0'
);
});
test('it should NOT validate a success that is not a boolean', () => {
type UnsafeCastForTest = Either<
Errors,
{
success: string;
success_count: number;
errors: Array<
{
id?: string | undefined;
rule_id?: string | undefined;
} & {
error: {
status_code: number;
message: string;
};
}
>;
}
>;
const payload: Omit<ImportRulesResponse, 'success'> & { success: string } = {
success: 'hello',
success_count: 0,
@ -213,36 +169,12 @@ describe('Import rules schema', () => {
action_connectors_errors: [],
action_connectors_warnings: [],
};
const decoded = ImportRulesResponse.decode(payload);
const checked = exactCheck(payload, decoded as UnsafeCastForTest);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "hello" supplied to "success"',
]);
expect(message.schema).toEqual({});
const result = ImportRulesResponse.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toEqual('success: Expected boolean, received string');
});
test('it should NOT validate a exceptions_success that is not a boolean', () => {
type UnsafeCastForTest = Either<
Errors,
{
success: boolean;
exceptions_success: string;
success_count: number;
errors: Array<
{
id?: string | undefined;
rule_id?: string | undefined;
} & {
error: {
status_code: number;
message: string;
};
}
>;
}
>;
const payload: Omit<ImportRulesResponse, 'exceptions_success'> & {
exceptions_success: string;
} = {
@ -258,14 +190,11 @@ describe('Import rules schema', () => {
action_connectors_errors: [],
action_connectors_warnings: [],
};
const decoded = ImportRulesResponse.decode(payload);
const checked = exactCheck(payload, decoded as UnsafeCastForTest);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "hello" supplied to "exceptions_success"',
]);
expect(message.schema).toEqual({});
const result = ImportRulesResponse.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toEqual(
'exceptions_success: Expected boolean, received string'
);
});
test('it should NOT validate a success an extra invalid field', () => {
@ -283,12 +212,11 @@ describe('Import rules schema', () => {
action_connectors_errors: [],
action_connectors_warnings: [],
};
const decoded = ImportRulesResponse.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_field"']);
expect(message.schema).toEqual({});
const result = ImportRulesResponse.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toEqual(
"Unrecognized key(s) in object: 'invalid_field'"
);
});
test('it should NOT validate an extra field in the second position of the errors array', () => {
@ -311,12 +239,11 @@ describe('Import rules schema', () => {
action_connectors_errors: [],
action_connectors_warnings: [],
};
const decoded = ImportRulesResponse.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_data"']);
expect(message.schema).toEqual({});
const result = ImportRulesResponse.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toEqual(
"errors.1: Unrecognized key(s) in object: 'invalid_data'"
);
});
test('it should validate an empty import response with a single connectors error', () => {
@ -333,13 +260,11 @@ describe('Import rules schema', () => {
action_connectors_errors: [{ error: { status_code: 400, message: 'some message' } }],
action_connectors_warnings: [],
};
const decoded = ImportRulesResponse.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
const result = ImportRulesResponse.safeParse(payload);
expectParseSuccess(result);
expect(result.data).toEqual(payload);
});
test('it should validate an empty import response with multiple errors', () => {
const payload: ImportRulesResponse = {
success: false,
@ -357,33 +282,12 @@ describe('Import rules schema', () => {
action_connectors_errors: [{ error: { status_code: 400, message: 'some message' } }],
action_connectors_warnings: [],
};
const decoded = ImportRulesResponse.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
const result = ImportRulesResponse.safeParse(payload);
expectParseSuccess(result);
expect(result.data).toEqual(payload);
});
test('it should NOT validate action_connectors_success that is not boolean', () => {
type UnsafeCastForTest = Either<
Errors,
{
success: boolean;
action_connectors_success: string;
success_count: number;
errors: Array<
{
id?: string | undefined;
rule_id?: string | undefined;
} & {
error: {
status_code: number;
message: string;
};
}
>;
}
>;
const payload: Omit<ImportRulesResponse, 'action_connectors_success'> & {
action_connectors_success: string;
} = {
@ -399,15 +303,13 @@ describe('Import rules schema', () => {
action_connectors_errors: [],
action_connectors_warnings: [],
};
const decoded = ImportRulesResponse.decode(payload);
const checked = exactCheck(payload, decoded as UnsafeCastForTest);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "invalid" supplied to "action_connectors_success"',
]);
expect(message.schema).toEqual({});
const result = ImportRulesResponse.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toEqual(
'action_connectors_success: Expected boolean, received string'
);
});
test('it should NOT validate a action_connectors_success_count that is a negative number', () => {
const payload: ImportRulesResponse = {
success: false,
@ -422,16 +324,13 @@ describe('Import rules schema', () => {
action_connectors_errors: [],
action_connectors_warnings: [],
};
const decoded = ImportRulesResponse.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "-1" supplied to "action_connectors_success_count"',
]);
expect(message.schema).toEqual({});
const result = ImportRulesResponse.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toEqual(
'action_connectors_success_count: Number must be greater than or equal to 0'
);
});
test('it should validate a action_connectors_warnings after importing successfully', () => {
test('it should validate a action_connectors_warnings after importing successfully', () => {
const payload: ImportRulesResponse = {
success: false,
success_count: 0,
@ -447,33 +346,12 @@ describe('Import rules schema', () => {
{ type: 'type', message: 'message', actionPath: 'actionPath' },
],
};
const decoded = ImportRulesResponse.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
const result = ImportRulesResponse.safeParse(payload);
expectParseSuccess(result);
expect(result.data).toEqual(payload);
});
test('it should NOT validate a action_connectors_warnings that is not WarningSchema', () => {
type UnsafeCastForTest = Either<
Errors,
{
success: boolean;
action_connectors_warnings: string;
success_count: number;
errors: Array<
{
id?: string | undefined;
rule_id?: string | undefined;
} & {
error: {
status_code: number;
message: string;
};
}
>;
}
>;
const payload: Omit<ImportRulesResponse, 'action_connectors_warnings'> & {
action_connectors_warnings: string;
} = {
@ -489,14 +367,11 @@ describe('Import rules schema', () => {
action_connectors_errors: [],
action_connectors_warnings: 'invalid',
};
const decoded = ImportRulesResponse.decode(payload);
const checked = exactCheck(payload, decoded as UnsafeCastForTest);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "invalid" supplied to "action_connectors_warnings"',
]);
expect(message.schema).toEqual({});
const result = ImportRulesResponse.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toEqual(
'action_connectors_warnings: Expected array, received string'
);
});
});
});

View file

@ -1,44 +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 * as t from 'io-ts';
import { DefaultStringBooleanFalse, PositiveInteger } from '@kbn/securitysolution-io-ts-types';
import { errorSchema, warningSchema } from '../../model';
export const ImportRulesRequestQuery = t.exact(
t.partial({
overwrite: DefaultStringBooleanFalse,
overwrite_exceptions: DefaultStringBooleanFalse,
overwrite_action_connectors: DefaultStringBooleanFalse,
as_new_list: DefaultStringBooleanFalse,
})
);
export type ImportRulesRequestQuery = t.TypeOf<typeof ImportRulesRequestQuery>;
export interface ImportRulesRequestQueryDecoded {
overwrite: boolean;
overwrite_exceptions: boolean;
overwrite_action_connectors: boolean;
as_new_list: boolean;
}
export type ImportRulesResponse = t.TypeOf<typeof ImportRulesResponse>;
export const ImportRulesResponse = t.exact(
t.type({
exceptions_success: t.boolean,
exceptions_success_count: PositiveInteger,
exceptions_errors: t.array(errorSchema),
rules_count: PositiveInteger,
success: t.boolean,
success_count: PositiveInteger,
errors: t.array(errorSchema),
action_connectors_errors: t.array(errorSchema),
action_connectors_warnings: t.array(warningSchema),
action_connectors_success: t.boolean,
action_connectors_success_count: PositiveInteger,
})
);

View file

@ -21,14 +21,14 @@ export * from './crud/read_rule/read_rule_route';
export * from './crud/update_rule/request_schema_validation';
export * from './crud/update_rule/update_rule_route';
export * from './export_rules/export_rules_details_schema';
export * from './export_rules/export_rules_route';
export * from './export_rules/export_rules_route.gen';
export * from './find_rules/find_rules_route';
export * from './find_rules/request_schema_validation';
export * from './get_rule_management_filters/get_rule_management_filters_route';
export * from './import_rules/import_rules_route';
export * from './import_rules/import_rules_route.gen';
export * from './import_rules/rule_to_import_validation';
export * from './import_rules/rule_to_import';
export * from './model/query_rule_by_ids_validation';
export * from './model/query_rule_by_ids';
export * from './urls';
export * from './read_tags/read_tags_route';
export * from './read_tags/read_tags_route.gen';

View file

@ -0,0 +1,18 @@
/*
* 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 { z } from 'zod';
/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*/
import { RuleTagArray } from '../../model/rule_schema/common_attributes.gen';
export type ReadTagsResponse = z.infer<typeof ReadTagsResponse>;
export const ReadTagsResponse = RuleTagArray;

View file

@ -6,8 +6,8 @@ paths:
/api/detection_engine/tags:
summary: Aggregates and returns rule tags
get:
operationId: GetTags
x-codegen-enabled: false
operationId: ReadTags
x-codegen-enabled: true
summary: Aggregates and returns all unique tags from all rules
tags:
- Tags API

View file

@ -1,11 +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 * as t from 'io-ts';
export const ReadTagsResponse = t.array(t.string);
export type ReadTagsResponse = t.TypeOf<typeof ReadTagsResponse>;

View file

@ -11,13 +11,14 @@ export * from './detection_engine_health/get_space_health/get_space_health_route
export * from './detection_engine_health/setup_health/setup_health_route';
export * from './detection_engine_health/model';
export * from './rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route';
export * from './rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route';
export * from './rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.gen';
export * from './urls';
export * from './model/execution_event';
export * from './model/execution_metrics';
export * from './model/execution_result';
export * from './model/execution_result.gen';
export * from './model/execution_settings';
export * from './model/execution_status.gen';
export * from './model/execution_status';
export * from './model/execution_summary';
export * from './model/log_level';

View file

@ -0,0 +1,55 @@
/*
* 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 { z } from 'zod';
/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*/
/**
* Rule execution result is an aggregate that groups plain rule execution events by execution UUID. It contains such information as execution UUID, date, status and metrics.
*/
export type RuleExecutionResult = z.infer<typeof RuleExecutionResult>;
export const RuleExecutionResult = z.object({
execution_uuid: z.string(),
timestamp: z.string().datetime(),
duration_ms: z.number().int(),
status: z.string(),
message: z.string(),
num_active_alerts: z.number().int(),
num_new_alerts: z.number().int(),
num_recovered_alerts: z.number().int(),
num_triggered_actions: z.number().int(),
num_succeeded_actions: z.number().int(),
num_errored_actions: z.number().int(),
total_search_duration_ms: z.number().int(),
es_search_duration_ms: z.number().int(),
schedule_delay_ms: z.number().int(),
timed_out: z.boolean(),
indexing_duration_ms: z.number().int(),
search_duration_ms: z.number().int(),
gap_duration_s: z.number().int(),
security_status: z.string(),
security_message: z.string(),
});
/**
* We support sorting rule execution results by these fields.
*/
export type SortFieldOfRuleExecutionResult = z.infer<typeof SortFieldOfRuleExecutionResult>;
export const SortFieldOfRuleExecutionResult = z.enum([
'timestamp',
'duration_ms',
'gap_duration_s',
'indexing_duration_ms',
'search_duration_ms',
'schedule_delay_ms',
]);
export const SortFieldOfRuleExecutionResultEnum = SortFieldOfRuleExecutionResult.enum;
export type SortFieldOfRuleExecutionResultEnum = typeof SortFieldOfRuleExecutionResult.enum;

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import type { RuleExecutionResult } from './execution_result';
import type { RuleExecutionResult } from './execution_result.gen';
const getSomeResults = (): RuleExecutionResult[] => [
{

View file

@ -0,0 +1,86 @@
openapi: 3.0.0
info:
title: Execution Result Schema
version: not applicable
paths: {}
components:
x-codegen-enabled: true
schemas:
RuleExecutionResult:
type: object
description: |-
Rule execution result is an aggregate that groups plain rule execution events by execution UUID. It contains such information as execution UUID, date, status and metrics.
properties:
execution_uuid:
type: string
timestamp:
type: string
format: date-time
duration_ms:
type: integer
status:
type: string
message:
type: string
num_active_alerts:
type: integer
num_new_alerts:
type: integer
num_recovered_alerts:
type: integer
num_triggered_actions:
type: integer
num_succeeded_actions:
type: integer
num_errored_actions:
type: integer
total_search_duration_ms:
type: integer
es_search_duration_ms:
type: integer
schedule_delay_ms:
type: integer
timed_out:
type: boolean
indexing_duration_ms:
type: integer
search_duration_ms:
type: integer
gap_duration_s:
type: integer
security_status:
type: string
security_message:
type: string
required:
- execution_uuid
- timestamp
- duration_ms
- status
- message
- num_active_alerts
- num_new_alerts
- num_recovered_alerts
- num_triggered_actions
- num_succeeded_actions
- num_errored_actions
- total_search_duration_ms
- es_search_duration_ms
- schedule_delay_ms
- timed_out
- indexing_duration_ms
- search_duration_ms
- gap_duration_s
- security_status
- security_message
SortFieldOfRuleExecutionResult:
type: string
description: We support sorting rule execution results by these fields.
enum:
- timestamp
- duration_ms
- gap_duration_s
- indexing_duration_ms
- search_duration_ms
- schedule_delay_ms

View file

@ -0,0 +1,32 @@
/*
* 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 { z } from 'zod';
/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*/
/**
* Custom execution status of Security rules that is different from the status used in the Alerting Framework. We merge our custom status with the Framework's status to determine the resulting status of a rule.
- going to run - @deprecated Replaced by the 'running' status but left for backwards compatibility with rule execution events already written to Event Log in the prior versions of Kibana. Don't use when writing rule status changes.
- running - Rule execution started but not reached any intermediate or final status.
- partial failure - Rule can partially fail for various reasons either in the middle of an execution (in this case we update its status right away) or in the end of it. So currently this status can be both intermediate and final at the same time. A typical reason for a partial failure: not all the indices that the rule searches over actually exist.
- failed - Rule failed to execute due to unhandled exception or a reason defined in the business logic of its executor function.
- succeeded - Rule executed successfully without any issues. Note: this status is just an indication of a rule's "health". The rule might or might not generate any alerts despite of it.
*/
export type RuleExecutionStatus = z.infer<typeof RuleExecutionStatus>;
export const RuleExecutionStatus = z.enum([
'going to run',
'running',
'partial failure',
'failed',
'succeeded',
]);
export const RuleExecutionStatusEnum = RuleExecutionStatus.enum;
export type RuleExecutionStatusEnum = typeof RuleExecutionStatus.enum;

View file

@ -0,0 +1,24 @@
openapi: 3.0.0
info:
title: Execution Status Schema
version: not applicable
paths: {}
components:
x-codegen-enabled: true
schemas:
RuleExecutionStatus:
type: string
description: |-
Custom execution status of Security rules that is different from the status used in the Alerting Framework. We merge our custom status with the Framework's status to determine the resulting status of a rule.
- going to run - @deprecated Replaced by the 'running' status but left for backwards compatibility with rule execution events already written to Event Log in the prior versions of Kibana. Don't use when writing rule status changes.
- running - Rule execution started but not reached any intermediate or final status.
- partial failure - Rule can partially fail for various reasons either in the middle of an execution (in this case we update its status right away) or in the end of it. So currently this status can be both intermediate and final at the same time. A typical reason for a partial failure: not all the indices that the rule searches over actually exist.
- failed - Rule failed to execute due to unhandled exception or a reason defined in the business logic of its executor function.
- succeeded - Rule executed successfully without any issues. Note: this status is just an indication of a rule's "health". The rule might or might not generate any alerts despite of it.
enum:
- going to run
- running
- partial failure
- failed
- succeeded

View file

@ -5,57 +5,15 @@
* 2.0.
*/
import type * as t from 'io-ts';
import { enumeration, PositiveInteger } from '@kbn/securitysolution-io-ts-types';
import type { RuleLastRunOutcomes } from '@kbn/alerting-plugin/common';
import { enumeration, PositiveInteger } from '@kbn/securitysolution-io-ts-types';
import type * as t from 'io-ts';
import { assertUnreachable } from '../../../../utility_types';
import type { RuleExecutionStatus } from './execution_status.gen';
import { RuleExecutionStatusEnum } from './execution_status.gen';
/**
* Custom execution status of Security rules that is different from the status
* used in the Alerting Framework. We merge our custom status with the
* Framework's status to determine the resulting status of a rule.
*/
export enum RuleExecutionStatus {
/**
* @deprecated Replaced by the 'running' status but left for backwards compatibility
* with rule execution events already written to Event Log in the prior versions of Kibana.
* Don't use when writing rule status changes.
*/
'going to run' = 'going to run',
/**
* Rule execution started but not reached any intermediate or final status.
*/
'running' = 'running',
/**
* Rule can partially fail for various reasons either in the middle of an execution
* (in this case we update its status right away) or in the end of it. So currently
* this status can be both intermediate and final at the same time.
* A typical reason for a partial failure: not all the indices that the rule searches
* over actually exist.
*/
'partial failure' = 'partial failure',
/**
* Rule failed to execute due to unhandled exception or a reason defined in the
* business logic of its executor function.
*/
'failed' = 'failed',
/**
* Rule executed successfully without any issues. Note: this status is just an indication
* of a rule's "health". The rule might or might not generate any alerts despite of it.
*/
'succeeded' = 'succeeded',
}
export const TRuleExecutionStatus = enumeration('RuleExecutionStatus', RuleExecutionStatus);
/**
* An array of supported rule execution statuses.
*/
export const RULE_EXECUTION_STATUSES = Object.values(RuleExecutionStatus);
// TODO remove after the migration to Zod is done
export const TRuleExecutionStatus = enumeration('RuleExecutionStatus', RuleExecutionStatusEnum);
export type RuleExecutionStatusOrder = t.TypeOf<typeof RuleExecutionStatusOrder>;
export const RuleExecutionStatusOrder = PositiveInteger;
@ -64,15 +22,15 @@ export const ruleExecutionStatusToNumber = (
status: RuleExecutionStatus
): RuleExecutionStatusOrder => {
switch (status) {
case RuleExecutionStatus.succeeded:
case RuleExecutionStatusEnum.succeeded:
return 0;
case RuleExecutionStatus['going to run']:
case RuleExecutionStatusEnum['going to run']:
return 10;
case RuleExecutionStatus.running:
case RuleExecutionStatusEnum.running:
return 15;
case RuleExecutionStatus['partial failure']:
case RuleExecutionStatusEnum['partial failure']:
return 20;
case RuleExecutionStatus.failed:
case RuleExecutionStatusEnum.failed:
return 30;
default:
assertUnreachable(status);
@ -85,13 +43,13 @@ export const ruleLastRunOutcomeToExecutionStatus = (
): RuleExecutionStatus => {
switch (outcome) {
case 'succeeded':
return RuleExecutionStatus.succeeded;
return RuleExecutionStatusEnum.succeeded;
case 'warning':
return RuleExecutionStatus['partial failure'];
return RuleExecutionStatusEnum['partial failure'];
case 'failed':
return RuleExecutionStatus.failed;
return RuleExecutionStatusEnum.failed;
default:
assertUnreachable(outcome);
return RuleExecutionStatus.failed;
return RuleExecutionStatusEnum.failed;
}
};

View file

@ -5,13 +5,13 @@
* 2.0.
*/
import { RuleExecutionStatus } from './execution_status';
import { RuleExecutionStatusEnum } from './execution_status.gen';
import type { RuleExecutionSummary } from './execution_summary';
const getSummarySucceeded = (): RuleExecutionSummary => ({
last_execution: {
date: '2020-02-18T15:26:49.783Z',
status: RuleExecutionStatus.succeeded,
status: RuleExecutionStatusEnum.succeeded,
status_order: 0,
message: 'succeeded',
metrics: {
@ -25,7 +25,7 @@ const getSummarySucceeded = (): RuleExecutionSummary => ({
const getSummaryFailed = (): RuleExecutionSummary => ({
last_execution: {
date: '2020-02-18T15:15:58.806Z',
status: RuleExecutionStatus.failed,
status: RuleExecutionStatusEnum.failed,
status_order: 30,
message:
'Signal rule name: "Query with a rule id Number 1", id: "1ea5a820-4da1-4e82-92a1-2b43a7bece08", rule_id: "query-rule-id-1" has a time gap of 5 days (412682928ms), and could be missing signals within that time. Consider increasing your look behind time or adding more Kibana instances.',

View file

@ -7,8 +7,8 @@
export * from './execution_event';
export * from './execution_metrics';
export * from './execution_result';
export * from './execution_result.gen';
export * from './execution_settings';
export * from './execution_status';
export * from './execution_status.gen';
export * from './execution_summary';
export * from './log_level';

View file

@ -8,7 +8,8 @@
import { enumeration } from '@kbn/securitysolution-io-ts-types';
import { enumFromString } from '../../../../utils/enum_from_string';
import { assertUnreachable } from '../../../../utility_types';
import { RuleExecutionStatus } from './execution_status';
import type { RuleExecutionStatus } from './execution_status.gen';
import { RuleExecutionStatusEnum } from './execution_status.gen';
export enum LogLevel {
'trace' = 'trace',
@ -67,13 +68,13 @@ export const logLevelFromString = enumFromString(LogLevel);
export const logLevelFromExecutionStatus = (status: RuleExecutionStatus): LogLevel => {
switch (status) {
case RuleExecutionStatus['going to run']:
case RuleExecutionStatus.running:
case RuleExecutionStatus.succeeded:
case RuleExecutionStatusEnum['going to run']:
case RuleExecutionStatusEnum.running:
case RuleExecutionStatusEnum.succeeded:
return LogLevel.info;
case RuleExecutionStatus['partial failure']:
case RuleExecutionStatusEnum['partial failure']:
return LogLevel.warn;
case RuleExecutionStatus.failed:
case RuleExecutionStatusEnum.failed:
return LogLevel.error;
default:
assertUnreachable(status);

View file

@ -0,0 +1,85 @@
/*
* 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 { z } from 'zod';
/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*/
import { RuleExecutionStatus } from '../../model/execution_status.gen';
import {
SortFieldOfRuleExecutionResult,
RuleExecutionResult,
} from '../../model/execution_result.gen';
import { SortOrder } from '../../../model/sorting.gen';
export type GetRuleExecutionEventsRequestQuery = z.infer<typeof GetRuleExecutionEventsRequestQuery>;
export const GetRuleExecutionEventsRequestQuery = z.object({
/**
* Start date of the time range to query
*/
start: z.string().datetime(),
/**
* End date of the time range to query
*/
end: z.string().datetime(),
/**
* Query text to filter results by
*/
query_text: z.string().optional().default(''),
/**
* Comma-separated list of rule execution statuses to filter results by
*/
status_filters: z
.preprocess(
(value: unknown) =>
typeof value === 'string' ? (value === '' ? [] : value.split(',')) : value,
z.array(RuleExecutionStatus)
)
.optional()
.default([]),
/**
* Field to sort results by
*/
sort_field: SortFieldOfRuleExecutionResult.optional().default('timestamp'),
/**
* Sort order to sort results by
*/
sort_order: SortOrder.optional().default('desc'),
/**
* Page number to return
*/
page: z.coerce.number().int().optional().default(1),
/**
* Number of results per page
*/
per_page: z.coerce.number().int().optional().default(20),
});
export type GetRuleExecutionEventsRequestQueryInput = z.input<
typeof GetRuleExecutionEventsRequestQuery
>;
export type GetRuleExecutionEventsRequestParams = z.infer<
typeof GetRuleExecutionEventsRequestParams
>;
export const GetRuleExecutionEventsRequestParams = z.object({
/**
* Saved object ID of the rule to get execution results for
*/
ruleId: z.string().min(1),
});
export type GetRuleExecutionEventsRequestParamsInput = z.input<
typeof GetRuleExecutionEventsRequestParams
>;
export type GetRuleExecutionEventsResponse = z.infer<typeof GetRuleExecutionEventsResponse>;
export const GetRuleExecutionEventsResponse = z.object({
events: z.array(RuleExecutionResult).optional(),
total: z.number().int().optional(),
});

View file

@ -0,0 +1,92 @@
openapi: 3.0.0
info:
title: Get Rule Execution Events API endpoint
version: '1'
paths:
/internal/detection_engine/rules/{ruleId}/execution/events:
put:
operationId: GetRuleExecutionEvents
x-codegen-enabled: true
summary: Returns execution events of a given rule (aggregated by execution UUID) from Event Log.
tags:
- Rule Execution Log API
parameters:
- name: ruleId
in: path
required: true
description: Saved object ID of the rule to get execution results for
schema:
type: string
minLength: 1
- name: start
in: query
required: true
description: Start date of the time range to query
schema:
type: string
format: date-time
- name: end
in: query
required: true
description: End date of the time range to query
schema:
type: string
format: date-time
- name: query_text
in: query
required: false
description: Query text to filter results by
schema:
type: string
default: ''
- name: status_filters
in: query
required: false
description: Comma-separated list of rule execution statuses to filter results by
schema:
type: array
items:
$ref: '../../model/execution_status.schema.yaml#/components/schemas/RuleExecutionStatus'
default: []
- name: sort_field
in: query
required: false
description: Field to sort results by
schema:
$ref: '../../model/execution_result.schema.yaml#/components/schemas/SortFieldOfRuleExecutionResult'
default: timestamp
- name: sort_order
in: query
required: false
description: Sort order to sort results by
schema:
$ref: '../../../model/sorting.schema.yaml#/components/schemas/SortOrder'
default: desc
- name: page
in: query
required: false
description: Page number to return
schema:
type: integer
default: 1
- name: per_page
in: query
required: false
description: Number of results per page
schema:
type: integer
default: 20
responses:
200:
description: Indicates a successful call
content:
application/json:
schema:
type: object
properties:
events:
type: array
items:
$ref: '../../model/execution_result.schema.yaml#/components/schemas/RuleExecutionResult'
total:
type: integer

View file

@ -0,0 +1,87 @@
/*
* 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 { z } from 'zod';
/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*/
import { RuleExecutionStatus } from '../../model/execution_status.gen';
import {
SortFieldOfRuleExecutionResult,
RuleExecutionResult,
} from '../../model/execution_result.gen';
import { SortOrder } from '../../../model/sorting.gen';
export type GetRuleExecutionResultsRequestQuery = z.infer<
typeof GetRuleExecutionResultsRequestQuery
>;
export const GetRuleExecutionResultsRequestQuery = z.object({
/**
* Start date of the time range to query
*/
start: z.string().datetime(),
/**
* End date of the time range to query
*/
end: z.string().datetime(),
/**
* Query text to filter results by
*/
query_text: z.string().optional().default(''),
/**
* Comma-separated list of rule execution statuses to filter results by
*/
status_filters: z
.preprocess(
(value: unknown) =>
typeof value === 'string' ? (value === '' ? [] : value.split(',')) : value,
z.array(RuleExecutionStatus)
)
.optional()
.default([]),
/**
* Field to sort results by
*/
sort_field: SortFieldOfRuleExecutionResult.optional().default('timestamp'),
/**
* Sort order to sort results by
*/
sort_order: SortOrder.optional().default('desc'),
/**
* Page number to return
*/
page: z.coerce.number().int().optional().default(1),
/**
* Number of results per page
*/
per_page: z.coerce.number().int().optional().default(20),
});
export type GetRuleExecutionResultsRequestQueryInput = z.input<
typeof GetRuleExecutionResultsRequestQuery
>;
export type GetRuleExecutionResultsRequestParams = z.infer<
typeof GetRuleExecutionResultsRequestParams
>;
export const GetRuleExecutionResultsRequestParams = z.object({
/**
* Saved object ID of the rule to get execution results for
*/
ruleId: z.string().min(1),
});
export type GetRuleExecutionResultsRequestParamsInput = z.input<
typeof GetRuleExecutionResultsRequestParams
>;
export type GetRuleExecutionResultsResponse = z.infer<typeof GetRuleExecutionResultsResponse>;
export const GetRuleExecutionResultsResponse = z.object({
events: z.array(RuleExecutionResult).optional(),
total: z.number().int().optional(),
});

View file

@ -6,7 +6,7 @@
*/
import { ruleExecutionResultMock } from '../../model/execution_result.mock';
import type { GetRuleExecutionResultsResponse } from './get_rule_execution_results_route';
import type { GetRuleExecutionResultsResponse } from './get_rule_execution_results_route.gen';
const getSomeResponse = (): GetRuleExecutionResultsResponse => {
const results = ruleExecutionResultMock.getSomeResults();

View file

@ -0,0 +1,92 @@
openapi: 3.0.0
info:
title: Get Rule Execution Results API endpoint
version: '1'
paths:
/internal/detection_engine/rules/{ruleId}/execution/results:
put:
operationId: GetRuleExecutionResults
x-codegen-enabled: true
summary: Returns execution results of a given rule (aggregated by execution UUID) from Event Log.
tags:
- Rule Execution Log API
parameters:
- name: ruleId
in: path
required: true
description: Saved object ID of the rule to get execution results for
schema:
type: string
minLength: 1
- name: start
in: query
required: true
description: Start date of the time range to query
schema:
type: string
format: date-time
- name: end
in: query
required: true
description: End date of the time range to query
schema:
type: string
format: date-time
- name: query_text
in: query
required: false
description: Query text to filter results by
schema:
type: string
default: ''
- name: status_filters
in: query
required: false
description: Comma-separated list of rule execution statuses to filter results by
schema:
type: array
items:
$ref: '../../model/execution_status.schema.yaml#/components/schemas/RuleExecutionStatus'
default: []
- name: sort_field
in: query
required: false
description: Field to sort results by
schema:
$ref: '../../model/execution_result.schema.yaml#/components/schemas/SortFieldOfRuleExecutionResult'
default: timestamp
- name: sort_order
in: query
required: false
description: Sort order to sort results by
schema:
$ref: '../../../model/sorting.schema.yaml#/components/schemas/SortOrder'
default: desc
- name: page
in: query
required: false
description: Page number to return
schema:
type: integer
default: 1
- name: per_page
in: query
required: false
description: Number of results per page
schema:
type: integer
default: 20
responses:
200:
description: Indicates a successful call
content:
application/json:
schema:
type: object
properties:
events:
type: array
items:
$ref: '../../model/execution_result.schema.yaml#/components/schemas/RuleExecutionResult'
total:
type: integer

View file

@ -5,32 +5,29 @@
* 2.0.
*/
import { pipe } from 'fp-ts/lib/pipeable';
import { left } from 'fp-ts/lib/Either';
import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils';
import { stringifyZodError } from '@kbn/securitysolution-es-utils';
import { expectParseError, expectParseSuccess } from '../../../../../test/zod_helpers';
import { RuleExecutionStatus } from '../../model';
import { GetRuleExecutionResultsRequestQuery } from './get_rule_execution_results_route.gen';
import { RULE_EXECUTION_STATUSES } from '../../model/execution_status';
import {
DefaultSortField,
DefaultRuleExecutionStatusCsvArray,
} from './get_rule_execution_results_route';
const StatusFiltersSchema = GetRuleExecutionResultsRequestQuery.shape.status_filters;
const SortFieldSchema = GetRuleExecutionResultsRequestQuery.shape.sort_field;
describe('Request schema of Get rule execution results', () => {
describe('DefaultRuleExecutionStatusCsvArray', () => {
describe('Validation succeeds', () => {
describe('when input is a single rule execution status', () => {
const cases = RULE_EXECUTION_STATUSES.map((supportedStatus) => {
const cases = RuleExecutionStatus.options.map((supportedStatus) => {
return { input: supportedStatus };
});
cases.forEach(({ input }) => {
it(`${input}`, () => {
const decoded = DefaultRuleExecutionStatusCsvArray.decode(input);
const message = pipe(decoded, foldLeftRight);
const expectedOutput = [input]; // note that it's an array after decode
const result = StatusFiltersSchema.safeParse(input);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(expectedOutput);
expectParseSuccess(result);
expect(result.data).toEqual(expectedOutput);
});
});
});
@ -43,12 +40,11 @@ describe('Request schema of Get rule execution results', () => {
cases.forEach(({ input }) => {
it(`${input}`, () => {
const decoded = DefaultRuleExecutionStatusCsvArray.decode(input);
const message = pipe(decoded, foldLeftRight);
const expectedOutput = input;
const result = StatusFiltersSchema.safeParse(input);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(expectedOutput);
expectParseSuccess(result);
expect(result.data).toEqual(expectedOutput);
});
});
});
@ -67,11 +63,10 @@ describe('Request schema of Get rule execution results', () => {
cases.forEach(({ input, expectedOutput }) => {
it(`${input}`, () => {
const decoded = DefaultRuleExecutionStatusCsvArray.decode(input);
const message = pipe(decoded, foldLeftRight);
const result = StatusFiltersSchema.safeParse(input);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(expectedOutput);
expectParseSuccess(result);
expect(result.data).toEqual(expectedOutput);
});
});
});
@ -82,37 +77,30 @@ describe('Request schema of Get rule execution results', () => {
const cases = [
{
input: 'val',
expectedErrors: [
'Invalid value "val" supplied to "DefaultCsvArray<RuleExecutionStatus>"',
],
expectedErrors:
"0: Invalid enum value. Expected 'going to run' | 'running' | 'partial failure' | 'failed' | 'succeeded', received 'val'",
},
{
input: '5',
expectedErrors: [
'Invalid value "5" supplied to "DefaultCsvArray<RuleExecutionStatus>"',
],
expectedErrors:
"0: Invalid enum value. Expected 'going to run' | 'running' | 'partial failure' | 'failed' | 'succeeded', received '5'",
},
{
input: 5,
expectedErrors: [
'Invalid value "5" supplied to "DefaultCsvArray<RuleExecutionStatus>"',
],
expectedErrors: 'Expected array, received number',
},
{
input: {},
expectedErrors: [
'Invalid value "{}" supplied to "DefaultCsvArray<RuleExecutionStatus>"',
],
expectedErrors: 'Expected array, received object',
},
];
cases.forEach(({ input, expectedErrors }) => {
it(`${input}`, () => {
const decoded = DefaultRuleExecutionStatusCsvArray.decode(input);
const message = pipe(decoded, foldLeftRight);
const result = StatusFiltersSchema.safeParse(input);
expect(getPaths(left(message.errors))).toEqual(expectedErrors);
expect(message.schema).toEqual({});
expectParseError(result);
expect(stringifyZodError(result.error)).toEqual(expectedErrors);
});
});
});
@ -121,34 +109,27 @@ describe('Request schema of Get rule execution results', () => {
const cases = [
{
input: ['value 1', 5],
expectedErrors: [
'Invalid value "value 1" supplied to "DefaultCsvArray<RuleExecutionStatus>"',
'Invalid value "5" supplied to "DefaultCsvArray<RuleExecutionStatus>"',
],
expectedErrors:
"0: Invalid enum value. Expected 'going to run' | 'running' | 'partial failure' | 'failed' | 'succeeded', received 'value 1', 1: Expected 'going to run' | 'running' | 'partial failure' | 'failed' | 'succeeded', received number",
},
{
input: ['value 1', 'succeeded'],
expectedErrors: [
'Invalid value "value 1" supplied to "DefaultCsvArray<RuleExecutionStatus>"',
],
expectedErrors:
"0: Invalid enum value. Expected 'going to run' | 'running' | 'partial failure' | 'failed' | 'succeeded', received 'value 1'",
},
{
input: ['', 5, {}],
expectedErrors: [
'Invalid value "" supplied to "DefaultCsvArray<RuleExecutionStatus>"',
'Invalid value "5" supplied to "DefaultCsvArray<RuleExecutionStatus>"',
'Invalid value "{}" supplied to "DefaultCsvArray<RuleExecutionStatus>"',
],
expectedErrors:
"0: Invalid enum value. Expected 'going to run' | 'running' | 'partial failure' | 'failed' | 'succeeded', received '', 1: Expected 'going to run' | 'running' | 'partial failure' | 'failed' | 'succeeded', received number, 2: Expected 'going to run' | 'running' | 'partial failure' | 'failed' | 'succeeded', received object",
},
];
cases.forEach(({ input, expectedErrors }) => {
it(`${input}`, () => {
const decoded = DefaultRuleExecutionStatusCsvArray.decode(input);
const message = pipe(decoded, foldLeftRight);
const result = StatusFiltersSchema.safeParse(input);
expect(getPaths(left(message.errors))).toEqual(expectedErrors);
expect(message.schema).toEqual({});
expectParseError(result);
expect(stringifyZodError(result.error)).toEqual(expectedErrors);
});
});
});
@ -157,34 +138,27 @@ describe('Request schema of Get rule execution results', () => {
const cases = [
{
input: 'value 1,5',
expectedErrors: [
'Invalid value "value 1" supplied to "DefaultCsvArray<RuleExecutionStatus>"',
'Invalid value "5" supplied to "DefaultCsvArray<RuleExecutionStatus>"',
],
expectedErrors:
"0: Invalid enum value. Expected 'going to run' | 'running' | 'partial failure' | 'failed' | 'succeeded', received 'value 1', 1: Invalid enum value. Expected 'going to run' | 'running' | 'partial failure' | 'failed' | 'succeeded', received '5'",
},
{
input: 'value 1,succeeded',
expectedErrors: [
'Invalid value "value 1" supplied to "DefaultCsvArray<RuleExecutionStatus>"',
],
expectedErrors:
"0: Invalid enum value. Expected 'going to run' | 'running' | 'partial failure' | 'failed' | 'succeeded', received 'value 1'",
},
{
input: ',5,{}',
expectedErrors: [
'Invalid value "" supplied to "DefaultCsvArray<RuleExecutionStatus>"',
'Invalid value "5" supplied to "DefaultCsvArray<RuleExecutionStatus>"',
'Invalid value "{}" supplied to "DefaultCsvArray<RuleExecutionStatus>"',
],
expectedErrors:
"0: Invalid enum value. Expected 'going to run' | 'running' | 'partial failure' | 'failed' | 'succeeded', received '', 1: Invalid enum value. Expected 'going to run' | 'running' | 'partial failure' | 'failed' | 'succeeded', received '5', 2: Invalid enum value. Expected 'going to run' | 'running' | 'partial failure' | 'failed' | 'succeeded', received '{}'",
},
];
cases.forEach(({ input, expectedErrors }) => {
it(`${input}`, () => {
const decoded = DefaultRuleExecutionStatusCsvArray.decode(input);
const message = pipe(decoded, foldLeftRight);
const result = StatusFiltersSchema.safeParse(input);
expect(getPaths(left(message.errors))).toEqual(expectedErrors);
expect(message.schema).toEqual({});
expectParseError(result);
expect(stringifyZodError(result.error)).toEqual(expectedErrors);
});
});
});
@ -192,16 +166,14 @@ describe('Request schema of Get rule execution results', () => {
describe('Validation returns default value (an empty array)', () => {
describe('when input is', () => {
const cases = [{ input: null }, { input: undefined }, { input: '' }, { input: [] }];
const cases = [{ input: undefined }, { input: '' }, { input: [] }];
cases.forEach(({ input }) => {
it(`${input}`, () => {
const decoded = DefaultRuleExecutionStatusCsvArray.decode(input);
const message = pipe(decoded, foldLeftRight);
const expectedOutput: string[] = [];
const result = StatusFiltersSchema.safeParse(input);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(expectedOutput);
expectParseSuccess(result);
expect(result.data).toEqual([]);
});
});
});
@ -222,11 +194,9 @@ describe('Request schema of Get rule execution results', () => {
cases.forEach(({ input }) => {
it(`${input}`, () => {
const decoded = DefaultSortField.decode(input);
const message = pipe(decoded, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(input);
const result = SortFieldSchema.safeParse(input);
expectParseSuccess(result);
expect(result.data).toEqual(input);
});
});
});
@ -244,12 +214,10 @@ describe('Request schema of Get rule execution results', () => {
cases.forEach(({ input }) => {
it(`${input}`, () => {
const decoded = DefaultSortField.decode(input);
const message = pipe(decoded, foldLeftRight);
const expectedErrors = [`Invalid value "${input}" supplied to "DefaultSortField"`];
expect(getPaths(left(message.errors))).toEqual(expectedErrors);
expect(message.schema).toEqual({});
const expectedErrors = `Invalid enum value. Expected 'timestamp' | 'duration_ms' | 'gap_duration_s' | 'indexing_duration_ms' | 'search_duration_ms' | 'schedule_delay_ms', received '${input}'`;
const result = SortFieldSchema.safeParse(input);
expectParseError(result);
expect(stringifyZodError(result.error)).toEqual(expectedErrors);
});
});
});
@ -257,18 +225,38 @@ describe('Request schema of Get rule execution results', () => {
describe('Validation returns the default sort field "timestamp"', () => {
describe('when input is', () => {
const cases = [{ input: null }, { input: undefined }];
const cases = [{ input: undefined }];
cases.forEach(({ input }) => {
it(`${input}`, () => {
const decoded = DefaultSortField.decode(input);
const message = pipe(decoded, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual('timestamp');
const result = SortFieldSchema.safeParse(input);
expectParseSuccess(result);
expect(result.data).toEqual('timestamp');
});
});
});
});
});
describe('GetRuleExecutionResultsRequestQuery', () => {
it('should convert string values to numbers', () => {
const result = GetRuleExecutionResultsRequestQuery.safeParse({
start: '2021-08-01T00:00:00.000Z',
end: '2021-08-02T00:00:00.000Z',
page: '1',
per_page: '10',
});
expectParseSuccess(result);
expect(result.data).toEqual({
end: '2021-08-02T00:00:00.000Z',
page: 1,
per_page: 10,
query_text: '',
sort_field: 'timestamp',
sort_order: 'desc',
start: '2021-08-01T00:00:00.000Z',
status_filters: [],
});
});
});
});

View file

@ -1,86 +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 * as t from 'io-ts';
import { DefaultPage, DefaultPerPage } from '@kbn/securitysolution-io-ts-alerting-types';
import {
defaultCsvArray,
DefaultEmptyString,
defaultValue,
IsoDateString,
NonEmptyString,
} from '@kbn/securitysolution-io-ts-types';
import { DefaultSortOrderDesc } from '../../../model';
import {
RuleExecutionResult,
SortFieldOfRuleExecutionResult,
TRuleExecutionStatus,
} from '../../model';
/**
* Types the DefaultRuleExecutionStatusCsvArray as:
* - If not specified, then a default empty array will be set
* - If an array is sent in, then the array will be validated to ensure all elements are a RuleExecutionStatus
* (or that the array is empty)
* - If a CSV string is sent in, then it will be parsed to an array which will be validated
*/
export const DefaultRuleExecutionStatusCsvArray = defaultCsvArray(TRuleExecutionStatus);
/**
* Types the DefaultSortField as:
* - If undefined, then a default sort field of 'timestamp' will be set
* - If a string is sent in, then the string will be validated to ensure it is as valid sortFields
*/
export const DefaultSortField = defaultValue(
SortFieldOfRuleExecutionResult,
'timestamp',
'DefaultSortField'
);
/**
* Path parameters of the API route.
*/
export type GetRuleExecutionResultsRequestParams = t.TypeOf<
typeof GetRuleExecutionResultsRequestParams
>;
export const GetRuleExecutionResultsRequestParams = t.exact(
t.type({
ruleId: NonEmptyString,
})
);
/**
* Query string parameters of the API route.
*/
export type GetRuleExecutionResultsRequestQuery = t.TypeOf<
typeof GetRuleExecutionResultsRequestQuery
>;
export const GetRuleExecutionResultsRequestQuery = t.exact(
t.type({
start: IsoDateString,
end: IsoDateString,
query_text: DefaultEmptyString, // defaults to ''
status_filters: DefaultRuleExecutionStatusCsvArray, // defaults to []
sort_field: DefaultSortField, // defaults to 'timestamp'
sort_order: DefaultSortOrderDesc, // defaults to 'desc'
page: DefaultPage, // defaults to 1
per_page: DefaultPerPage, // defaults to 20
})
);
/**
* Response body of the API route.
*/
export type GetRuleExecutionResultsResponse = t.TypeOf<typeof GetRuleExecutionResultsResponse>;
export const GetRuleExecutionResultsResponse = t.exact(
t.type({
events: t.array(RuleExecutionResult),
total: t.number,
})
);

View file

@ -19,7 +19,7 @@ import {
SavedObjectResolveAliasTargetId,
SavedObjectResolveOutcome,
} from '../../detection_engine/model/rule_schema';
import { errorSchema, success, success_count as successCount } from '../../detection_engine';
import { ErrorSchema, success, success_count as successCount } from '../../detection_engine';
export const BareNoteSchema = runtimeTypes.intersection([
runtimeTypes.type({
@ -499,7 +499,7 @@ export const importTimelineResultSchema = runtimeTypes.exact(
success_count: successCount,
timelines_installed: PositiveInteger,
timelines_updated: PositiveInteger,
errors: runtimeTypes.array(errorSchema),
errors: runtimeTypes.array(ErrorSchema),
})
);

View file

@ -6,7 +6,8 @@
*/
import type { Type } from '@kbn/securitysolution-io-ts-alerting-types';
import { RuleExecutionStatus } from '../../api/detection_engine';
import type { RuleExecutionStatus } from '../../api/detection_engine';
import { RuleExecutionStatusEnum } from '../../api/detection_engine';
import { prepareKQLStringParam } from '../../utils/kql';
import {
ENABLED_FIELD,
@ -75,11 +76,11 @@ export function convertRulesFilterToKQL({
kql.push(`NOT ${convertRuleTypesToKQL(excludeRuleTypes)}`);
}
if (ruleExecutionStatus === RuleExecutionStatus.succeeded) {
if (ruleExecutionStatus === RuleExecutionStatusEnum.succeeded) {
kql.push(`${LAST_RUN_OUTCOME_FIELD}: "succeeded"`);
} else if (ruleExecutionStatus === RuleExecutionStatus['partial failure']) {
} else if (ruleExecutionStatus === RuleExecutionStatusEnum['partial failure']) {
kql.push(`${LAST_RUN_OUTCOME_FIELD}: "warning"`);
} else if (ruleExecutionStatus === RuleExecutionStatus.failed) {
} else if (ruleExecutionStatus === RuleExecutionStatusEnum.failed) {
kql.push(`${LAST_RUN_OUTCOME_FIELD}: "failed"`);
}

View file

@ -9,7 +9,8 @@ import React, { useCallback } from 'react';
import { replace } from 'lodash';
import { EuiFieldSearch, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { RuleExecutionStatus } from '../../../../../../common/api/detection_engine/rule_monitoring';
import type { RuleExecutionStatus } from '../../../../../../common/api/detection_engine/rule_monitoring';
import { RuleExecutionStatusEnum } from '../../../../../../common/api/detection_engine/rule_monitoring';
import { ExecutionStatusFilter } from '../../../../rule_monitoring';
import * as i18n from './translations';
@ -36,10 +37,10 @@ export const replaceQueryTextAliases = (queryText: string): string => {
};
// This only includes statuses which are or can be final
const STATUS_FILTERS = [
RuleExecutionStatus.succeeded,
RuleExecutionStatus.failed,
RuleExecutionStatus['partial failure'],
const STATUS_FILTERS: RuleExecutionStatus[] = [
RuleExecutionStatusEnum.succeeded,
RuleExecutionStatusEnum.failed,
RuleExecutionStatusEnum['partial failure'],
];
interface ExecutionLogTableSearchProps {

View file

@ -15,7 +15,7 @@ import {
EuiButtonIcon,
} from '@elastic/eui';
import { RuleExecutionStatus } from '../../../../../common/api/detection_engine/rule_monitoring';
import { RuleExecutionStatusEnum } from '../../../../../common/api/detection_engine/rule_monitoring';
import type { SecurityJob } from '../../../../common/components/ml_popover/types';
import * as i18n from '../rules_table/translations';
@ -64,7 +64,7 @@ const MlRuleWarningPopoverComponent: React.FC<MlRuleWarningPopoverComponentProps
onClick={togglePopover}
/>
);
const popoverTitle = getCapitalizedStatusText(RuleExecutionStatus['partial failure']);
const popoverTitle = getCapitalizedStatusText(RuleExecutionStatusEnum['partial failure']);
return (
<EuiPopover

View file

@ -15,7 +15,7 @@ import {
EuiButtonIcon,
} from '@elastic/eui';
import { RuleExecutionStatus } from '../../../../../common/api/detection_engine/rule_monitoring';
import { RuleExecutionStatusEnum } from '../../../../../common/api/detection_engine/rule_monitoring';
import type { SecurityJob } from '../../../../common/components/ml_popover/types';
import * as i18n from './translations';
@ -64,7 +64,7 @@ const MlRuleWarningPopoverComponent: React.FC<MlRuleWarningPopoverComponentProps
onClick={togglePopover}
/>
);
const popoverTitle = getCapitalizedStatusText(RuleExecutionStatus['partial failure']);
const popoverTitle = getCapitalizedStatusText(RuleExecutionStatusEnum['partial failure']);
return (
<EuiPopover

View file

@ -9,7 +9,8 @@ import React, { useState } from 'react';
import type { EuiSelectableOption } from '@elastic/eui';
import { EuiFilterButton, EuiPopover, EuiSelectable } from '@elastic/eui';
import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations';
import { RuleExecutionStatus } from '../../../../../../common/api/detection_engine/rule_monitoring/model/execution_status';
import type { RuleExecutionStatus } from '../../../../../../common/api/detection_engine';
import { RuleExecutionStatusEnum } from '../../../../../../common/api/detection_engine';
import { getCapitalizedStatusText } from '../../../../../detections/components/rules/rule_execution_status/utils';
import { RuleStatusBadge } from '../../../../../detections/components/rules/rule_execution_status/rule_status_badge';
@ -36,19 +37,19 @@ const RuleExecutionStatusSelectorComponent = ({
const selectableOptions: EuiSelectableOption[] = [
{
label: getCapitalizedStatusText(RuleExecutionStatus.succeeded) || '',
data: { status: RuleExecutionStatus.succeeded },
checked: selectedStatus === RuleExecutionStatus.succeeded ? 'on' : undefined,
label: getCapitalizedStatusText(RuleExecutionStatusEnum.succeeded) || '',
data: { status: RuleExecutionStatusEnum.succeeded },
checked: selectedStatus === RuleExecutionStatusEnum.succeeded ? 'on' : undefined,
},
{
label: getCapitalizedStatusText(RuleExecutionStatus['partial failure']) || '',
data: { status: RuleExecutionStatus['partial failure'] },
checked: selectedStatus === RuleExecutionStatus['partial failure'] ? 'on' : undefined,
label: getCapitalizedStatusText(RuleExecutionStatusEnum['partial failure']) || '',
data: { status: RuleExecutionStatusEnum['partial failure'] },
checked: selectedStatus === RuleExecutionStatusEnum['partial failure'] ? 'on' : undefined,
},
{
label: getCapitalizedStatusText(RuleExecutionStatus.failed) || '',
data: { status: RuleExecutionStatus.failed },
checked: selectedStatus === RuleExecutionStatus.failed ? 'on' : undefined,
label: getCapitalizedStatusText(RuleExecutionStatusEnum.failed) || '',
data: { status: RuleExecutionStatusEnum.failed },
checked: selectedStatus === RuleExecutionStatusEnum.failed ? 'on' : undefined,
},
];

View file

@ -13,11 +13,11 @@ import { useRuleManagementFilters } from '../../../../rule_management/logic/use_
import { RULES_TABLE_ACTIONS } from '../../../../../common/lib/apm/user_actions';
import { useStartTransaction } from '../../../../../common/lib/apm/use_start_transaction';
import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations';
import type { RuleExecutionStatus } from '../../../../../../common/api/detection_engine/rule_monitoring/model/execution_status';
import { useRulesTableContext } from '../rules_table/rules_table_context';
import { TagsFilterPopover } from './tags_filter_popover';
import { RuleExecutionStatusSelector } from './rule_execution_status_selector';
import { RuleSearchField } from './rule_search_field';
import type { RuleExecutionStatus } from '../../../../../../common/api/detection_engine';
const FilterWrapper = styled(EuiFlexGroup)`
margin-bottom: ${({ theme }) => theme.eui.euiSizeXS};

View file

@ -8,12 +8,12 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { RuleExecutionStatus } from '../../../../../common/api/detection_engine/rule_monitoring';
import { RuleExecutionStatusEnum } from '../../../../../common/api/detection_engine/rule_monitoring';
import { RuleStatusBadge } from './rule_status_badge';
describe('RuleStatusBadge', () => {
it('renders capitalized status text', () => {
render(<RuleStatusBadge status={RuleExecutionStatus.succeeded} />);
render(<RuleStatusBadge status={RuleExecutionStatusEnum.succeeded} />);
expect(screen.getByText('Succeeded')).toBeInTheDocument();
});

View file

@ -11,7 +11,8 @@ import { getEmptyTagValue } from '../../../../common/components/empty_value';
import { HealthTruncateText } from '../../../../common/components/health_truncate_text';
import { getCapitalizedStatusText, getStatusColor } from './utils';
import { RuleExecutionStatus } from '../../../../../common/api/detection_engine/rule_monitoring';
import type { RuleExecutionStatus } from '../../../../../common/api/detection_engine/rule_monitoring';
import { RuleExecutionStatusEnum } from '../../../../../common/api/detection_engine/rule_monitoring';
interface RuleStatusBadgeProps {
status: RuleExecutionStatus | null | undefined;
@ -29,7 +30,8 @@ const RuleStatusBadgeComponent = ({
showTooltip = true,
}: RuleStatusBadgeProps) => {
const isFailedStatus =
status === RuleExecutionStatus.failed || status === RuleExecutionStatus['partial failure'];
status === RuleExecutionStatusEnum.failed ||
status === RuleExecutionStatusEnum['partial failure'];
const statusText = getCapitalizedStatusText(status);
const statusTooltip = isFailedStatus && message ? message : statusText;

View file

@ -8,7 +8,8 @@
import React from 'react';
import { render } from '@testing-library/react';
import { RuleExecutionStatus } from '../../../../../common/api/detection_engine/rule_monitoring';
import type { RuleExecutionStatus } from '../../../../../common/api/detection_engine/rule_monitoring';
import { RuleExecutionStatusEnum } from '../../../../../common/api/detection_engine/rule_monitoring';
import { RuleStatusFailedCallOut } from './rule_status_failed_callout';
jest.mock('../../../../common/lib/kibana');
@ -32,22 +33,22 @@ describe('RuleStatusFailedCallOut', () => {
});
it('is hidden if status is "going to run"', () => {
const result = renderWith(RuleExecutionStatus['going to run']);
const result = renderWith(RuleExecutionStatusEnum['going to run']);
expect(result.queryByTestId(TEST_ID)).toBe(null);
});
it('is hidden if status is "running"', () => {
const result = renderWith(RuleExecutionStatus.running);
const result = renderWith(RuleExecutionStatusEnum.running);
expect(result.queryByTestId(TEST_ID)).toBe(null);
});
it('is hidden if status is "succeeded"', () => {
const result = renderWith(RuleExecutionStatus.succeeded);
const result = renderWith(RuleExecutionStatusEnum.succeeded);
expect(result.queryByTestId(TEST_ID)).toBe(null);
});
it('is visible if status is "partial failure"', () => {
const result = renderWith(RuleExecutionStatus['partial failure']);
const result = renderWith(RuleExecutionStatusEnum['partial failure']);
result.getByTestId(TEST_ID);
result.getByText('Warning at');
result.getByText('Jan 27, 2022 @ 15:03:31.176');
@ -55,7 +56,7 @@ describe('RuleStatusFailedCallOut', () => {
});
it('is visible if status is "failed"', () => {
const result = renderWith(RuleExecutionStatus.failed);
const result = renderWith(RuleExecutionStatusEnum.failed);
result.getByTestId(TEST_ID);
result.getByText('Rule failure at');
result.getByText('Jan 27, 2022 @ 15:03:31.176');

View file

@ -10,7 +10,8 @@ import React from 'react';
import { EuiCallOut, EuiCodeBlock } from '@elastic/eui';
import { FormattedDate } from '../../../../common/components/formatted_date';
import { RuleExecutionStatus } from '../../../../../common/api/detection_engine/rule_monitoring';
import type { RuleExecutionStatus } from '../../../../../common/api/detection_engine/rule_monitoring';
import { RuleExecutionStatusEnum } from '../../../../../common/api/detection_engine/rule_monitoring';
import * as i18n from './translations';
@ -75,13 +76,13 @@ interface HelperProps {
const getPropsByStatus = (status: RuleExecutionStatus | null | undefined): HelperProps => {
switch (status) {
case RuleExecutionStatus.failed:
case RuleExecutionStatusEnum.failed:
return {
shouldBeDisplayed: true,
color: 'danger',
title: i18n.ERROR_CALLOUT_TITLE,
};
case RuleExecutionStatus['partial failure']:
case RuleExecutionStatusEnum['partial failure']:
return {
shouldBeDisplayed: true,
color: 'warning',

View file

@ -8,13 +8,14 @@
import type { IconColor } from '@elastic/eui';
import { capitalize } from 'lodash';
import { assertUnreachable } from '../../../../../common/utility_types';
import { RuleExecutionStatus } from '../../../../../common/api/detection_engine/rule_monitoring';
import type { RuleExecutionStatus } from '../../../../../common/api/detection_engine/rule_monitoring';
import { RuleExecutionStatusEnum } from '../../../../../common/api/detection_engine/rule_monitoring';
export const getStatusText = (value: RuleExecutionStatus | null | undefined): string | null => {
if (value == null) {
return null;
}
if (value === RuleExecutionStatus['partial failure']) {
if (value === RuleExecutionStatusEnum['partial failure']) {
return 'warning';
}
return value;
@ -31,16 +32,16 @@ export const getStatusColor = (status: RuleExecutionStatus | null | undefined):
if (status == null) {
return 'subdued';
}
if (status === RuleExecutionStatus.succeeded) {
if (status === RuleExecutionStatusEnum.succeeded) {
return 'success';
}
if (status === RuleExecutionStatus.failed) {
if (status === RuleExecutionStatusEnum.failed) {
return 'danger';
}
if (
status === RuleExecutionStatus.running ||
status === RuleExecutionStatus['partial failure'] ||
status === RuleExecutionStatus['going to run']
status === RuleExecutionStatusEnum.running ||
status === RuleExecutionStatusEnum['partial failure'] ||
status === RuleExecutionStatusEnum['going to run']
) {
return 'warning';
}

View file

@ -8,7 +8,6 @@
import type { RulesClient } from '@kbn/alerting-plugin/server';
import type { ExceptionListClient } from '@kbn/lists-plugin/server';
import { transformError } from '@kbn/securitysolution-es-utils';
import { validate } from '@kbn/securitysolution-io-ts-utils';
import moment from 'moment';
import {
InstallPrebuiltRulesAndTimelinesResponse,
@ -116,9 +115,7 @@ export const createPrepackagedRules = async (
throw new AggregateError(result.errors, 'Error installing new prebuilt rules');
}
const { result: timelinesResult, error: timelinesError } = await performTimelinesInstallation(
context
);
const { result: timelinesResult } = await performTimelinesInstallation(context);
await upgradePrebuiltRules(rulesClient, rulesToUpdate);
@ -129,17 +126,5 @@ export const createPrepackagedRules = async (
timelines_updated: timelinesResult?.timelines_updated ?? 0,
};
const [validated, genericErrors] = validate(
prebuiltRulesOutput,
InstallPrebuiltRulesAndTimelinesResponse
);
if (genericErrors != null && timelinesError != null) {
throw new PrepackagedRulesError(
[genericErrors, timelinesError].filter((msg) => msg != null).join(', '),
500
);
}
return validated;
return InstallPrebuiltRulesAndTimelinesResponse.parse(prebuiltRulesOutput);
};

View file

@ -14,7 +14,7 @@ import {
ExportRulesRequestQuery,
} from '../../../../../../../common/api/detection_engine/rule_management';
import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation';
import { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation';
import type { SecuritySolutionPluginRouter } from '../../../../../../types';
import type { ConfigType } from '../../../../../../config';
import { getNonPackagedRulesCount } from '../../../logic/search/get_existing_prepackaged_rules';
@ -40,8 +40,8 @@ export const exportRulesRoute = (
version: '2023-10-31',
validate: {
request: {
query: buildRouteValidation(ExportRulesRequestQuery),
body: buildRouteValidation(ExportRulesRequestBody),
query: buildRouteValidationWithZod(ExportRulesRequestQuery),
body: buildRouteValidationWithZod(ExportRulesRequestBody),
},
},
},

View file

@ -5,40 +5,34 @@
* 2.0.
*/
import { schema } from '@kbn/config-schema';
import type { IKibanaResponse } from '@kbn/core/server';
import { transformError } from '@kbn/securitysolution-es-utils';
import { createPromiseFromStreams } from '@kbn/utils';
import { chunk } from 'lodash/fp';
import { extname } from 'path';
import { schema } from '@kbn/config-schema';
import { createPromiseFromStreams } from '@kbn/utils';
import { transformError } from '@kbn/securitysolution-es-utils';
import { validate } from '@kbn/securitysolution-io-ts-utils';
import type { IKibanaResponse } from '@kbn/core/server';
import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants';
import type { ImportRulesRequestQueryDecoded } from '../../../../../../../common/api/detection_engine/rule_management';
import {
ImportRulesRequestQuery,
ImportRulesResponse,
} from '../../../../../../../common/api/detection_engine/rule_management';
import type { HapiReadableStream, SecuritySolutionPluginRouter } from '../../../../../../types';
import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants';
import type { ConfigType } from '../../../../../../config';
import type { SetupPlugins } from '../../../../../../plugin';
import type { HapiReadableStream, SecuritySolutionPluginRouter } from '../../../../../../types';
import { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation';
import { buildMlAuthz } from '../../../../../machine_learning/authz';
import type { ImportRuleResponse, BulkError } from '../../../../routes/utils';
import { isBulkError, isImportRegular, buildSiemResponse } from '../../../../routes/utils';
import type { BulkError, ImportRuleResponse } from '../../../../routes/utils';
import { buildSiemResponse, isBulkError, isImportRegular } from '../../../../routes/utils';
import { importRuleActionConnectors } from '../../../logic/import/action_connectors/import_rule_action_connectors';
import { createRulesAndExceptionsStreamFromNdJson } from '../../../logic/import/create_rules_stream_from_ndjson';
import { getReferencedExceptionLists } from '../../../logic/import/gather_referenced_exceptions';
import type { RuleExceptionsPromiseFromStreams } from '../../../logic/import/import_rules_utils';
import { importRules as importRulesHelper } from '../../../logic/import/import_rules_utils';
import { importRuleExceptions } from '../../../logic/import/import_rule_exceptions';
import {
getTupleDuplicateErrorsAndUniqueRules,
migrateLegacyActionsIds,
} from '../../../utils/utils';
import { createRulesAndExceptionsStreamFromNdJson } from '../../../logic/import/create_rules_stream_from_ndjson';
import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation';
import type { RuleExceptionsPromiseFromStreams } from '../../../logic/import/import_rules_utils';
import { importRules as importRulesHelper } from '../../../logic/import/import_rules_utils';
import { getReferencedExceptionLists } from '../../../logic/import/gather_referenced_exceptions';
import { importRuleExceptions } from '../../../logic/import/import_rule_exceptions';
import { importRuleActionConnectors } from '../../../logic/import/action_connectors/import_rule_action_connectors';
const CHUNK_PARSED_OBJECT_SIZE = 50;
@ -64,10 +58,7 @@ export const importRulesRoute = (
version: '2023-10-31',
validate: {
request: {
query: buildRouteValidation<
typeof ImportRulesRequestQuery,
ImportRulesRequestQueryDecoded
>(ImportRulesRequestQuery),
query: buildRouteValidationWithZod(ImportRulesRequestQuery),
body: schema.any(), // validation on file object is accomplished later in the handler.
},
},
@ -202,12 +193,7 @@ export const importRulesRoute = (
action_connectors_warnings: actionConnectorWarnings,
};
const [validated, errors] = validate(importRules, ImportRulesResponse);
if (errors != null) {
return siemResponse.error({ statusCode: 500, body: errors });
} else {
return response.ok({ body: validated ?? {} });
}
return response.ok({ body: ImportRulesResponse.parse(importRules) });
} catch (err) {
const error = transformError(err);
return siemResponse.error({

View file

@ -29,11 +29,8 @@ export const readTagsRoute = (router: SecuritySolutionPluginRouter) => {
},
async (context, request, response): Promise<IKibanaResponse<ReadTagsResponse>> => {
const siemResponse = buildSiemResponse(response);
const rulesClient = (await context.alerting)?.getRulesClient();
if (!rulesClient) {
return siemResponse.error({ statusCode: 404 });
}
const ctx = await context.resolve(['alerting']);
const rulesClient = ctx.alerting.getRulesClient();
try {
const tags = await readTags({

View file

@ -5,17 +5,17 @@
* 2.0.
*/
import { transformError } from '@kbn/securitysolution-es-utils';
import type { IKibanaResponse } from '@kbn/core/server';
import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation';
import { buildSiemResponse } from '../../../../routes/utils';
import { transformError } from '@kbn/securitysolution-es-utils';
import type { SecuritySolutionPluginRouter } from '../../../../../../types';
import { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation';
import { buildSiemResponse } from '../../../../routes/utils';
import type { GetRuleExecutionResultsResponse } from '../../../../../../../common/api/detection_engine/rule_monitoring';
import {
GET_RULE_EXECUTION_RESULTS_URL,
GetRuleExecutionResultsRequestParams,
GetRuleExecutionResultsRequestQuery,
GET_RULE_EXECUTION_RESULTS_URL,
} from '../../../../../../../common/api/detection_engine/rule_monitoring';
/**
@ -36,8 +36,8 @@ export const getRuleExecutionResultsRoute = (router: SecuritySolutionPluginRoute
version: '1',
validate: {
request: {
params: buildRouteValidation(GetRuleExecutionResultsRequestParams),
query: buildRouteValidation(GetRuleExecutionResultsRequestQuery),
params: buildRouteValidationWithZod(GetRuleExecutionResultsRequestParams),
query: buildRouteValidationWithZod(GetRuleExecutionResultsRequestQuery),
},
},
},

View file

@ -15,10 +15,11 @@ import type {
NumberOfLoggedMessages,
RuleExecutionStats,
TopMessages,
RuleExecutionStatus,
} from '../../../../../../../../common/api/detection_engine/rule_monitoring';
import {
RuleExecutionEventType,
RuleExecutionStatus,
RuleExecutionStatusEnum,
LogLevel,
} from '../../../../../../../../common/api/detection_engine/rule_monitoring';
@ -72,8 +73,8 @@ export const getRuleExecutionStatsAggregation = (
{
terms: {
[f.RULE_EXECUTION_STATUS]: [
RuleExecutionStatus.running,
RuleExecutionStatus['going to run'],
RuleExecutionStatusEnum.running,
RuleExecutionStatusEnum['going to run'],
],
},
},
@ -223,9 +224,9 @@ const normalizeNumberOfExecutions = (
return {
total: Number(totalExecutions.value || 0),
by_outcome: {
succeeded: getStatusCount(RuleExecutionStatus.succeeded),
warning: getStatusCount(RuleExecutionStatus['partial failure']),
failed: getStatusCount(RuleExecutionStatus.failed),
succeeded: getStatusCount(RuleExecutionStatusEnum.succeeded),
warning: getStatusCount(RuleExecutionStatusEnum['partial failure']),
failed: getStatusCount(RuleExecutionStatusEnum.failed),
},
};
};

View file

@ -5,30 +5,31 @@
* 2.0.
*/
import type { Logger } from '@kbn/core/server';
import { sum } from 'lodash';
import type { Duration } from 'moment';
import type { Logger } from '@kbn/core/server';
import type {
PublicRuleResultService,
PublicRuleMonitoringService,
PublicRuleResultService,
} from '@kbn/alerting-plugin/server/types';
import type {
RuleExecutionMetrics,
RuleExecutionSettings,
RuleExecutionStatus,
} from '../../../../../../../common/api/detection_engine/rule_monitoring';
import {
LogLevel,
logLevelFromExecutionStatus,
LogLevelSetting,
logLevelToNumber,
RuleExecutionStatus,
RuleExecutionStatusEnum,
} from '../../../../../../../common/api/detection_engine/rule_monitoring';
import { assertUnreachable } from '../../../../../../../common/utility_types';
import { withSecuritySpan } from '../../../../../../utils/with_security_span';
import { truncateValue } from '../../utils/normalization';
import type { ExtMeta } from '../../utils/console_logging';
import { truncateValue } from '../../utils/normalization';
import { getCorrelationIds } from './correlation_ids';
import type { IEventLogWriter } from '../event_log/event_log_writer';
@ -164,7 +165,7 @@ export const createRuleExecutionLogClientForExecutors = (
const writeStatusChangeToRuleObject = async (args: NormalizedStatusChangeArgs): Promise<void> => {
const { newStatus, message, metrics } = args;
if (newStatus === RuleExecutionStatus.running) {
if (newStatus === RuleExecutionStatusEnum.running) {
return;
}
@ -186,9 +187,9 @@ export const createRuleExecutionLogClientForExecutors = (
ruleMonitoringService.setLastRunMetricsGapDurationS(executionGapDurationS);
}
if (newStatus === RuleExecutionStatus.failed) {
if (newStatus === RuleExecutionStatusEnum.failed) {
ruleResultService.addLastRunError(message);
} else if (newStatus === RuleExecutionStatus['partial failure']) {
} else if (newStatus === RuleExecutionStatusEnum['partial failure']) {
ruleResultService.addLastRunWarning(message);
}
@ -234,7 +235,7 @@ interface NormalizedStatusChangeArgs {
}
const normalizeStatusChangeArgs = (args: StatusChangeArgs): NormalizedStatusChangeArgs => {
if (args.newStatus === RuleExecutionStatus.running) {
if (args.newStatus === RuleExecutionStatusEnum.running) {
return {
newStatus: args.newStatus,
message: '',

View file

@ -6,7 +6,10 @@
*/
import type { Duration } from 'moment';
import type { RuleExecutionStatus } from '../../../../../../../common/api/detection_engine/rule_monitoring';
import type {
RuleExecutionStatus,
RuleExecutionStatusEnum,
} from '../../../../../../../common/api/detection_engine/rule_monitoring';
/**
* Used from rule executors to log various information about the rule execution:
@ -109,7 +112,7 @@ export interface RuleExecutionContext {
}
export interface RunningStatusChangeArgs {
newStatus: RuleExecutionStatus.running;
newStatus: RuleExecutionStatusEnum['running'];
}
/**

View file

@ -9,9 +9,9 @@ import type { ResolvedSanitizedRule, SanitizedRule } from '@kbn/alerting-plugin/
import type { RuleExecutionSummary } from '../../../../../../common/api/detection_engine/rule_monitoring';
import {
ruleLastRunOutcomeToExecutionStatus,
RuleExecutionStatusEnum,
ruleExecutionStatusToNumber,
RuleExecutionStatus,
ruleLastRunOutcomeToExecutionStatus,
} from '../../../../../../common/api/detection_engine/rule_monitoring';
import type { RuleParams } from '../../../rule_schema';
@ -39,8 +39,8 @@ export const createRuleExecutionSummary = (
return {
last_execution: {
date: lastRunInternal.timestamp,
status: RuleExecutionStatus.running,
status_order: ruleExecutionStatusToNumber(RuleExecutionStatus.running),
status: RuleExecutionStatusEnum.running,
status_order: ruleExecutionStatusToNumber(RuleExecutionStatusEnum.running),
message: '',
metrics: {},
},

View file

@ -13,7 +13,7 @@
*/
import { MAX_EXECUTION_EVENTS_DISPLAYED } from '@kbn/securitysolution-rules';
import { RuleExecutionStatus } from '../../../../../../../../../common/api/detection_engine/rule_monitoring';
import { RuleExecutionStatusEnum } from '../../../../../../../../../common/api/detection_engine/rule_monitoring';
import {
formatExecutionEventResponse,
@ -1328,30 +1328,30 @@ describe('mapRuleStatusToPlatformStatus', () => {
expect(mapRuleExecutionStatusToPlatformStatus([])).toEqual([]);
});
test('should correctly translate RuleExecutionStatus.failed to `failure` platform status', () => {
expect(mapRuleExecutionStatusToPlatformStatus([RuleExecutionStatus.failed])).toEqual([
test('should correctly translate RuleExecutionStatusEnum.failed to `failure` platform status', () => {
expect(mapRuleExecutionStatusToPlatformStatus([RuleExecutionStatusEnum.failed])).toEqual([
'failure',
]);
});
test('should correctly translate RuleExecutionStatus.succeeded to `success` platform status', () => {
expect(mapRuleExecutionStatusToPlatformStatus([RuleExecutionStatus.succeeded])).toEqual([
test('should correctly translate RuleExecutionStatusEnum.succeeded to `success` platform status', () => {
expect(mapRuleExecutionStatusToPlatformStatus([RuleExecutionStatusEnum.succeeded])).toEqual([
'success',
]);
});
test('should correctly translate RuleExecutionStatus.["going to run"] to empty array platform status', () => {
expect(mapRuleExecutionStatusToPlatformStatus([RuleExecutionStatus['going to run']])).toEqual(
[]
);
test('should correctly translate RuleExecutionStatusEnum.["going to run"] to empty array platform status', () => {
expect(
mapRuleExecutionStatusToPlatformStatus([RuleExecutionStatusEnum['going to run']])
).toEqual([]);
});
test("should correctly translate multiple RuleExecutionStatus's to platform statuses", () => {
expect(
mapRuleExecutionStatusToPlatformStatus([
RuleExecutionStatus.succeeded,
RuleExecutionStatus.failed,
RuleExecutionStatus['going to run'],
RuleExecutionStatusEnum.succeeded,
RuleExecutionStatusEnum.failed,
RuleExecutionStatusEnum['going to run'],
]).sort()
).toEqual(['failure', 'success']);
});
@ -1362,13 +1362,15 @@ describe('mapPlatformStatusToRuleExecutionStatus', () => {
expect(mapPlatformStatusToRuleExecutionStatus('')).toEqual(undefined);
});
test('should correctly translate `failure` platform status to `RuleExecutionStatus.failed`', () => {
expect(mapPlatformStatusToRuleExecutionStatus('failure')).toEqual(RuleExecutionStatus.failed);
test('should correctly translate `failure` platform status to `RuleExecutionStatusEnum.failed`', () => {
expect(mapPlatformStatusToRuleExecutionStatus('failure')).toEqual(
RuleExecutionStatusEnum.failed
);
});
test('should correctly translate `success` platform status to `RuleExecutionStatus.succeeded`', () => {
test('should correctly translate `success` platform status to `RuleExecutionStatusEnum.succeeded`', () => {
expect(mapPlatformStatusToRuleExecutionStatus('success')).toEqual(
RuleExecutionStatus.succeeded
RuleExecutionStatusEnum.succeeded
);
});
});

View file

@ -5,23 +5,24 @@
* 2.0.
*/
import { flatMap, get } from 'lodash';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { AggregateEventsBySavedObjectResult } from '@kbn/event-log-plugin/server';
import { BadRequestError } from '@kbn/securitysolution-es-utils';
import { MAX_EXECUTION_EVENTS_DISPLAYED } from '@kbn/securitysolution-rules';
import type { AggregateEventsBySavedObjectResult } from '@kbn/event-log-plugin/server';
import { flatMap, get } from 'lodash';
import type {
RuleExecutionResult,
GetRuleExecutionResultsResponse,
RuleExecutionResult,
RuleExecutionStatus,
} from '../../../../../../../../../common/api/detection_engine/rule_monitoring';
import { RuleExecutionStatus } from '../../../../../../../../../common/api/detection_engine/rule_monitoring';
import { RuleExecutionStatusEnum } from '../../../../../../../../../common/api/detection_engine/rule_monitoring';
import * as f from '../../../../event_log/event_log_fields';
import type {
ExecutionEventAggregationOptions,
ExecutionUuidAggResult,
ExecutionUuidAggBucket,
ExecutionUuidAggResult,
} from './types';
import * as f from '../../../../event_log/event_log_fields';
// TODO: https://github.com/elastic/kibana/issues/125642 Move the fields from this file to `event_log_fields.ts`
@ -378,9 +379,9 @@ export const mapRuleExecutionStatusToPlatformStatus = (
): string[] => {
return flatMap(ruleStatuses, (rs) => {
switch (rs) {
case RuleExecutionStatus.failed:
case RuleExecutionStatusEnum.failed:
return 'failure';
case RuleExecutionStatus.succeeded:
case RuleExecutionStatusEnum.succeeded:
return 'success';
default:
return [];
@ -397,9 +398,9 @@ export const mapPlatformStatusToRuleExecutionStatus = (
): RuleExecutionStatus | undefined => {
switch (platformStatus) {
case 'failure':
return RuleExecutionStatus.failed;
return RuleExecutionStatusEnum.failed;
case 'success':
return RuleExecutionStatus.succeeded;
return RuleExecutionStatusEnum.succeeded;
default:
return undefined;
}

View file

@ -24,7 +24,7 @@ import {
DETECTION_ENGINE_RULES_PREVIEW,
} from '../../../../../../common/constants';
import { validateCreateRuleProps } from '../../../../../../common/api/detection_engine/rule_management';
import { RuleExecutionStatus } from '../../../../../../common/api/detection_engine/rule_monitoring';
import { RuleExecutionStatusEnum } from '../../../../../../common/api/detection_engine/rule_monitoring';
import type {
PreviewResponse,
RulePreviewLogs,
@ -287,11 +287,11 @@ export const previewRulesRoute = async (
})) as { state: TState });
const errors = loggedStatusChanges
.filter((item) => item.newStatus === RuleExecutionStatus.failed)
.filter((item) => item.newStatus === RuleExecutionStatusEnum.failed)
.map((item) => item.message ?? 'Unknown Error');
const warnings = loggedStatusChanges
.filter((item) => item.newStatus === RuleExecutionStatus['partial failure'])
.filter((item) => item.newStatus === RuleExecutionStatusEnum['partial failure'])
.map((item) => item.message ?? 'Unknown Warning');
logs.push({

View file

@ -36,7 +36,7 @@ import { getNotificationResultsLink } from '../rule_actions_legacy';
import { formatAlertForNotificationActions } from '../rule_actions_legacy/logic/notifications/schedule_notification_actions';
import { createResultObject } from './utils';
import { bulkCreateFactory, wrapHitsFactory, wrapSequencesFactory } from './factories';
import { RuleExecutionStatus } from '../../../../common/api/detection_engine/rule_monitoring';
import { RuleExecutionStatusEnum } from '../../../../common/api/detection_engine/rule_monitoring';
import { truncateList } from '../rule_monitoring';
import aadFieldConversion from '../routes/index/signal_aad_mapping.json';
import { extractReferences, injectReferences } from './saved_object_references';
@ -179,7 +179,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
ruleExecutionLogger.debug(`Starting Security Rule execution (interval: ${interval})`);
await ruleExecutionLogger.logStatusChange({
newStatus: RuleExecutionStatus.running,
newStatus: RuleExecutionStatusEnum.running,
});
let result = createResultObject(state);
@ -240,7 +240,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
: `Check for indices to search failed ${exc}`;
await ruleExecutionLogger.logStatusChange({
newStatus: RuleExecutionStatus.failed,
newStatus: RuleExecutionStatusEnum.failed,
message: errorMessage,
});
@ -299,7 +299,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
}
} catch (exc) {
await ruleExecutionLogger.logStatusChange({
newStatus: RuleExecutionStatus['partial failure'],
newStatus: RuleExecutionStatusEnum['partial failure'],
message: `Check privileges failed to execute ${exc}`,
});
wroteWarningStatus = true;
@ -321,7 +321,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
const gapDuration = `${remainingGap.humanize()} (${remainingGap.asMilliseconds()}ms)`;
await ruleExecutionLogger.logStatusChange({
newStatus: RuleExecutionStatus.failed,
newStatus: RuleExecutionStatusEnum.failed,
message: `${gapDuration} were not queried between this rule execution and the last execution, so signals may have been missed. Consider increasing your look behind time or adding more Kibana instances`,
metrics: { executionGap: remainingGap },
});
@ -459,7 +459,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
if (result.warningMessages.length) {
await ruleExecutionLogger.logStatusChange({
newStatus: RuleExecutionStatus['partial failure'],
newStatus: RuleExecutionStatusEnum['partial failure'],
message: truncateList(result.warningMessages).join(),
metrics: {
searchDurations: result.searchAfterTimes,
@ -485,7 +485,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
if (!hasError && !wroteWarningStatus && !result.warning) {
await ruleExecutionLogger.logStatusChange({
newStatus: RuleExecutionStatus.succeeded,
newStatus: RuleExecutionStatusEnum.succeeded,
message: 'Rule execution completed successfully',
metrics: {
searchDurations: result.searchAfterTimes,
@ -495,7 +495,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
});
} else if (wroteWarningStatus && !hasError && !result.warning) {
await ruleExecutionLogger.logStatusChange({
newStatus: RuleExecutionStatus['partial failure'],
newStatus: RuleExecutionStatusEnum['partial failure'],
message: warningMessage,
metrics: {
searchDurations: result.searchAfterTimes,
@ -506,7 +506,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
}
} else {
await ruleExecutionLogger.logStatusChange({
newStatus: RuleExecutionStatus.failed,
newStatus: RuleExecutionStatusEnum.failed,
message: `Bulk Indexing of alerts failed: ${truncateList(result.errors).join()}`,
metrics: {
searchDurations: result.searchAfterTimes,
@ -519,7 +519,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
const errorMessage = error.message ?? '(no error message given)';
await ruleExecutionLogger.logStatusChange({
newStatus: RuleExecutionStatus.failed,
newStatus: RuleExecutionStatusEnum.failed,
message: `An error occurred during rule execution: message: "${errorMessage}"`,
metrics: {
searchDurations: result.searchAfterTimes,

View file

@ -14,7 +14,7 @@ import type { RuleExecutorServicesMock } from '@kbn/alerting-plugin/server/mocks
import { alertsMock } from '@kbn/alerting-plugin/server/mocks';
import { listMock } from '@kbn/lists-plugin/server/mocks';
import type { ExceptionListClient } from '@kbn/lists-plugin/server';
import { RuleExecutionStatus } from '../../../../../common/api/detection_engine/rule_monitoring';
import { RuleExecutionStatusEnum } from '../../../../../common/api/detection_engine/rule_monitoring';
import { getListArrayMock } from '../../../../../common/detection_engine/schemas/types/lists.mock';
import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock';
@ -655,7 +655,7 @@ describe('utils', () => {
expect(wroteWarningStatus).toBeTruthy();
expect(foundNoIndices).toBeFalsy();
expect(ruleExecutionLogger.logStatusChange).toHaveBeenCalledWith({
newStatus: RuleExecutionStatus['partial failure'],
newStatus: RuleExecutionStatusEnum['partial failure'],
message:
'The following indices are missing the timestamp override field "event.ingested": ["myfakeindex-1","myfakeindex-2"]',
});
@ -699,7 +699,7 @@ describe('utils', () => {
expect(wroteWarningStatus).toBeTruthy();
expect(foundNoIndices).toBeFalsy();
expect(ruleExecutionLogger.logStatusChange).toHaveBeenCalledWith({
newStatus: RuleExecutionStatus['partial failure'],
newStatus: RuleExecutionStatusEnum['partial failure'],
message:
'The following indices are missing the timestamp field "@timestamp": ["myfakeindex-1","myfakeindex-2"]',
});
@ -732,7 +732,7 @@ describe('utils', () => {
expect(wroteWarningStatus).toBeTruthy();
expect(foundNoIndices).toBeTruthy();
expect(ruleExecutionLogger.logStatusChange).toHaveBeenCalledWith({
newStatus: RuleExecutionStatus['partial failure'],
newStatus: RuleExecutionStatusEnum['partial failure'],
message:
'This rule is attempting to query data from Elasticsearch indices listed in the "Index patterns" section of the rule definition, however no index matching: ["logs-endpoint.alerts-*"] was found. This warning will continue to appear until a matching index is created or this rule is disabled. If you have recently enrolled agents enabled with Endpoint Security through Fleet, this warning should stop once an alert is sent from an agent.',
});
@ -766,7 +766,7 @@ describe('utils', () => {
expect(wroteWarningStatus).toBeTruthy();
expect(foundNoIndices).toBeTruthy();
expect(ruleExecutionLogger.logStatusChange).toHaveBeenCalledWith({
newStatus: RuleExecutionStatus['partial failure'],
newStatus: RuleExecutionStatusEnum['partial failure'],
message:
'This rule is attempting to query data from Elasticsearch indices listed in the "Index patterns" section of the rule definition, however no index matching: ["logs-endpoint.alerts-*"] was found. This warning will continue to appear until a matching index is created or this rule is disabled.',
});

View file

@ -32,7 +32,7 @@ import { parseDuration } from '@kbn/alerting-plugin/server';
import type { ExceptionListClient, ListClient, ListPluginSetup } from '@kbn/lists-plugin/server';
import type { TimestampOverride } from '../../../../../common/api/detection_engine/model/rule_schema';
import type { Privilege } from '../../../../../common/api/detection_engine';
import { RuleExecutionStatus } from '../../../../../common/api/detection_engine/rule_monitoring';
import { RuleExecutionStatusEnum } from '../../../../../common/api/detection_engine/rule_monitoring';
import type {
BulkResponseErrorAggregation,
SignalHit,
@ -94,7 +94,7 @@ export const hasReadIndexPrivileges = async (args: {
const indexesString = JSON.stringify(indexesWithNoReadPrivileges);
warningStatusMessage = `This rule may not have the required read privileges to the following index patterns: ${indexesString}`;
await ruleExecutionLogger.logStatusChange({
newStatus: RuleExecutionStatus['partial failure'],
newStatus: RuleExecutionStatusEnum['partial failure'],
message: warningStatusMessage,
});
return { wroteWarningMessage: true, warningStatusMessage };
@ -129,7 +129,7 @@ export const hasTimestampFields = async (args: {
}`;
await ruleExecutionLogger.logStatusChange({
newStatus: RuleExecutionStatus['partial failure'],
newStatus: RuleExecutionStatusEnum['partial failure'],
message: errorString.trimEnd(),
});
@ -157,7 +157,7 @@ export const hasTimestampFields = async (args: {
)}`;
await ruleExecutionLogger.logStatusChange({
newStatus: RuleExecutionStatus['partial failure'],
newStatus: RuleExecutionStatusEnum['partial failure'],
message: errorString,
});

View file

@ -14,6 +14,8 @@ import type {
RouteValidationResultFactory,
RouteValidationError,
} from '@kbn/core/server';
import type { TypeOf, ZodType } from 'zod';
import { stringifyZodError } from '@kbn/securitysolution-es-utils';
import type { GenericIntersectionC } from '../runtime_types';
import { excess } from '../runtime_types';
@ -65,3 +67,14 @@ export const buildRouteValidationWithExcess =
(validatedInput: A) => validationResult.ok(validatedInput)
)
);
export const buildRouteValidationWithZod =
<T extends ZodType, A = TypeOf<T>>(schema: T): RouteValidationFunction<A> =>
(inputValue: unknown, validationResult: RouteValidationResultFactory) => {
const decoded = schema.safeParse(inputValue);
if (decoded.success) {
return validationResult.ok(decoded.data);
} else {
return validationResult.badRequest(stringifyZodError(decoded.error));
}
};

View file

@ -7,7 +7,7 @@
import expect from '@kbn/expect';
import { orderBy } from 'lodash';
import { RuleExecutionStatus } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring';
import { RuleExecutionStatusEnum } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring';
import {
EqlRuleCreateProps,
QueryRuleCreateProps,
@ -261,7 +261,7 @@ export default ({ getService }: FtrProviderContext) => {
log,
es,
createdRule,
RuleExecutionStatus['partial failure']
RuleExecutionStatusEnum['partial failure']
);
expect(signalsOpen.hits.hits.length).eql(0);
});
@ -340,7 +340,7 @@ export default ({ getService }: FtrProviderContext) => {
log,
es,
createdRule,
RuleExecutionStatus['partial failure']
RuleExecutionStatusEnum['partial failure']
);
expect(signalsOpen.hits.hits.length).eql(0);
});

View file

@ -28,7 +28,7 @@ import {
QueryRuleCreateProps,
AlertSuppressionMissingFieldsStrategy,
} from '@kbn/security-solution-plugin/common/api/detection_engine';
import { RuleExecutionStatus } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring';
import { RuleExecutionStatusEnum } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring';
import { Ancestor } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/types';
import {
ALERT_ANCESTORS,
@ -797,7 +797,7 @@ export default ({ getService }: FtrProviderContext) => {
log,
es,
createdRule,
RuleExecutionStatus.succeeded,
RuleExecutionStatusEnum.succeeded,
undefined,
afterTimestamp
);
@ -873,7 +873,7 @@ export default ({ getService }: FtrProviderContext) => {
log,
es,
createdRule,
RuleExecutionStatus.succeeded,
RuleExecutionStatusEnum.succeeded,
undefined,
afterTimestamp
);

View file

@ -34,7 +34,7 @@ import {
ALERT_ORIGINAL_EVENT_MODULE,
ALERT_ORIGINAL_TIME,
} from '@kbn/security-solution-plugin/common/field_maps/field_names';
import { RuleExecutionStatus } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring';
import { RuleExecutionStatusEnum } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring';
import { getMaxSignalsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils';
import {
previewRule,
@ -173,7 +173,7 @@ export default ({ getService }: FtrProviderContext) => {
log,
es,
createdRule,
RuleExecutionStatus.succeeded,
RuleExecutionStatusEnum.succeeded,
100
);
expect(alerts.hits.hits.length).equal(88);
@ -354,7 +354,7 @@ export default ({ getService }: FtrProviderContext) => {
log,
es,
createdRule,
RuleExecutionStatus.succeeded,
RuleExecutionStatusEnum.succeeded,
100
);
expect(alerts.hits.hits.length).equal(88);
@ -557,7 +557,7 @@ export default ({ getService }: FtrProviderContext) => {
log,
es,
createdRuleTerm,
RuleExecutionStatus.succeeded,
RuleExecutionStatusEnum.succeeded,
100
);
const alertsMatch = await getOpenSignals(
@ -565,7 +565,7 @@ export default ({ getService }: FtrProviderContext) => {
log,
es,
createdRuleMatch,
RuleExecutionStatus.succeeded,
RuleExecutionStatusEnum.succeeded,
100
);

View file

@ -8,7 +8,10 @@
import type SuperTest from 'supertest';
import type { Client } from '@elastic/elasticsearch';
import type { ToolingLog } from '@kbn/tooling-log';
import { RuleExecutionStatus } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring';
import {
RuleExecutionStatus,
RuleExecutionStatusEnum,
} from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring';
import type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine';
import { waitForRuleStatus } from './wait_for_rule_status';
@ -20,7 +23,7 @@ export const getOpenSignals = async (
log: ToolingLog,
es: Client,
rule: RuleResponse,
status: RuleExecutionStatus = RuleExecutionStatus.succeeded,
status: RuleExecutionStatus = RuleExecutionStatusEnum.succeeded,
size?: number,
afterDate?: Date
) => {

View file

@ -8,7 +8,10 @@
import type { ToolingLog } from '@kbn/tooling-log';
import type SuperTest from 'supertest';
import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants';
import { RuleExecutionStatus } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring';
import {
RuleExecutionStatus,
RuleExecutionStatusEnum,
} from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring';
import { waitFor } from './wait_for';
import { routeWithNamespace } from './route_with_namespace';
@ -70,10 +73,10 @@ export const waitForRuleStatus = async (
};
export const waitForRuleSuccess = (params: WaitForRuleStatusParams): Promise<void> =>
waitForRuleStatus(RuleExecutionStatus.succeeded, params);
waitForRuleStatus(RuleExecutionStatusEnum.succeeded, params);
export const waitForRulePartialFailure = (params: WaitForRuleStatusParams): Promise<void> =>
waitForRuleStatus(RuleExecutionStatus['partial failure'], params);
waitForRuleStatus(RuleExecutionStatusEnum['partial failure'], params);
export const waitForRuleFailure = (params: WaitForRuleStatusParams): Promise<void> =>
waitForRuleStatus(RuleExecutionStatus.failed, params);
waitForRuleStatus(RuleExecutionStatusEnum.failed, params);