mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Security Solution] Migrate Rules schema to OpenAPI (#167999)
**Parent meta ticket: https://github.com/elastic/security-team/issues/7491** Resolves: https://github.com/elastic/security-team/issues/7582 Resolves: https://github.com/elastic/security-team/issues/7580 Resolves: https://github.com/elastic/security-team/issues/7581 ## Summary This PR migrates the rules schema to OpenAPI, Zod, and code generation. The following APIs now have complete OpenAPI specifications and are enabled for code generation: | Method | Endpoint | OpenAPI spec | Fully migrated | | ------ | ---------------------------------------------------------------- | ------------ | -------------- | | POST | /api/detection_engine/rules | ✅ | ✅ | | GET | /api/detection_engine/rules | ✅ | ✅ | | PUT | /api/detection_engine/rules | ✅ | ✅ | | PATCH | /api/detection_engine/rules | ✅ | ✅ | | DELETE | /api/detection_engine/rules | ✅ | ✅ | | POST | /api/detection_engine/rules/\_bulk_create | ✅ | ✅ | | PUT | /api/detection_engine/rules/\_bulk_update | ✅ | ✅ | | PATCH | /api/detection_engine/rules/\_bulk_update | ✅ | ✅ | | DELETE | /api/detection_engine/rules/\_bulk_delete | ✅ | ✅ | | POST | /api/detection_engine/rules/\_bulk_delete | ✅ | ✅ | ### Rule schemas are now forward-compatible We now allow extra fields in schemas for forward compatibility, but we remove them from the payload during parsing. So from now on, extra fields are simply ignored and won't lead to validation errors.
This commit is contained in:
parent
46ca1f08b7
commit
3a18916b31
250 changed files with 5671 additions and 5813 deletions
3
.github/CODEOWNERS
vendored
3
.github/CODEOWNERS
vendored
|
@ -553,7 +553,7 @@ x-pack/plugins/observability @elastic/actionable-observability
|
|||
x-pack/plugins/observability_shared @elastic/observability-ui
|
||||
x-pack/test/security_api_integration/plugins/oidc_provider @elastic/kibana-security
|
||||
test/common/plugins/otel_metrics @elastic/infra-monitoring-ui
|
||||
packages/kbn-openapi-generator @elastic/security-detection-engine
|
||||
packages/kbn-openapi-generator @elastic/security-detection-rule-management
|
||||
packages/kbn-optimizer @elastic/kibana-operations
|
||||
packages/kbn-optimizer-webpack-helpers @elastic/kibana-operations
|
||||
packages/kbn-osquery-io-ts-types @elastic/security-asset-management
|
||||
|
@ -828,6 +828,7 @@ packages/kbn-web-worker-stub @elastic/kibana-operations
|
|||
packages/kbn-whereis-pkg-cli @elastic/kibana-operations
|
||||
packages/kbn-xstate-utils @elastic/infra-monitoring-ui
|
||||
packages/kbn-yarn-lock-validator @elastic/kibana-operations
|
||||
packages/kbn-zod-helpers @elastic/security-detection-rule-management
|
||||
####
|
||||
## Everything below this line overrides the default assignments for each package.
|
||||
## Items lower in the file have higher precedence:
|
||||
|
|
|
@ -815,6 +815,7 @@
|
|||
"@kbn/visualizations-plugin": "link:src/plugins/visualizations",
|
||||
"@kbn/watcher-plugin": "link:x-pack/plugins/watcher",
|
||||
"@kbn/xstate-utils": "link:packages/kbn-xstate-utils",
|
||||
"@kbn/zod-helpers": "link:packages/kbn-zod-helpers",
|
||||
"@loaders.gl/core": "^3.4.7",
|
||||
"@loaders.gl/json": "^3.4.7",
|
||||
"@loaders.gl/shapefile": "^3.4.7",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"devOnly": true,
|
||||
"id": "@kbn/openapi-generator",
|
||||
"owner": "@elastic/security-detection-engine",
|
||||
"owner": "@elastic/security-detection-rule-management",
|
||||
"type": "shared-common"
|
||||
}
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
{
|
||||
"bin": {
|
||||
"openapi-generator": "./bin/openapi-generator.js"
|
||||
},
|
||||
"description": "OpenAPI code generator for Kibana",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0",
|
||||
"name": "@kbn/openapi-generator",
|
||||
|
|
|
@ -35,6 +35,12 @@ export function registerHelpers(handlebarsInstance: typeof Handlebars) {
|
|||
handlebarsInstance.registerHelper('defined', (val) => {
|
||||
return val !== undefined;
|
||||
});
|
||||
handlebarsInstance.registerHelper('first', (val) => {
|
||||
return Array.isArray(val) ? val[0] : val;
|
||||
});
|
||||
handlebarsInstance.registerHelper('isSingle', (val) => {
|
||||
return Array.isArray(val) && val.length === 1;
|
||||
});
|
||||
/**
|
||||
* Check if the OpenAPI schema is unknown
|
||||
*/
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
import { requiredOptional, isValidDateMath } from "@kbn/zod-helpers"
|
||||
|
||||
{{> disclaimer}}
|
||||
|
||||
|
@ -24,8 +25,10 @@ import {
|
|||
export type {{@key}} = z.infer<typeof {{@key}}>;
|
||||
export const {{@key}} = {{> zod_schema_item}};
|
||||
{{#if enum}}
|
||||
export const {{@key}}Enum = {{@key}}.enum;
|
||||
{{#unless (isSingle enum)}}
|
||||
export type {{@key}}Enum = typeof {{@key}}.enum;
|
||||
export const {{@key}}Enum = {{@key}}.enum;
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
|
||||
{{/each}}
|
||||
|
|
|
@ -10,6 +10,9 @@
|
|||
{{~#if nullable}}.nullable(){{/if~}}
|
||||
{{~#if (eq requiredBool false)}}.optional(){{/if~}}
|
||||
{{~#if (defined default)}}.default({{{toJSON default}}}){{/if~}}
|
||||
{{~#if (eq x-modify "partial")}}.partial(){{/if~}}
|
||||
{{~#if (eq x-modify "required")}}.required(){{/if~}}
|
||||
{{~#if (eq x-modify "requiredOptional")}}.transform(requiredOptional){{/if~}}
|
||||
{{~/if~}}
|
||||
|
||||
{{~#if allOf~}}
|
||||
|
@ -28,6 +31,8 @@
|
|||
{{~> zod_schema_item ~}},
|
||||
{{~/each~}}
|
||||
])
|
||||
{{~#if nullable}}.nullable(){{/if~}}
|
||||
{{~#if (eq requiredBool false)}}.optional(){{/if~}}
|
||||
{{~/if~}}
|
||||
|
||||
{{~#if oneOf~}}
|
||||
|
@ -36,6 +41,8 @@
|
|||
{{~> zod_schema_item ~}},
|
||||
{{~/each~}}
|
||||
])
|
||||
{{~#if nullable}}.nullable(){{/if~}}
|
||||
{{~#if (eq requiredBool false)}}.optional(){{/if~}}
|
||||
{{~/if~}}
|
||||
|
||||
{{#if (isUnknown .)}}
|
||||
|
@ -76,22 +83,38 @@ z.unknown()
|
|||
{{@key}}: {{> zod_schema_item requiredBool=(includes ../required @key)}},
|
||||
{{/each}}
|
||||
})
|
||||
{{#if (eq additionalProperties false)}}.strict(){{/if}}
|
||||
{{~#if (eq additionalProperties false)}}.strict(){{/if~}}
|
||||
{{~#if additionalProperties}}
|
||||
{{~#if (eq additionalProperties true)~}}
|
||||
.catchall(z.unknown())
|
||||
{{~else~}}
|
||||
.catchall({{> zod_schema_item additionalProperties}})
|
||||
{{~/if~}}
|
||||
{{~/if~}}
|
||||
{{~#if (eq x-modify "partial")}}.partial(){{/if~}}
|
||||
{{~#if (eq x-modify "required")}}.required(){{/if~}}
|
||||
{{~#if (eq x-modify "requiredOptional")}}.transform(requiredOptional){{/if~}}
|
||||
{{~/inline~}}
|
||||
|
||||
{{~#*inline "type_string"~}}
|
||||
{{~#if enum~}}
|
||||
z.enum([
|
||||
{{~#each enum~}}
|
||||
"{{.}}",
|
||||
{{~/each~}}
|
||||
])
|
||||
{{~#if (isSingle enum)~}}
|
||||
z.literal("{{first enum}}")
|
||||
{{~else~}}
|
||||
z.enum([
|
||||
{{~#each enum~}}
|
||||
"{{.}}",
|
||||
{{~/each~}}
|
||||
])
|
||||
{{~/if~}}
|
||||
{{~else~}}
|
||||
z.string()
|
||||
{{~#if minLength}}.min({{minLength}}){{/if~}}
|
||||
{{~#if maxLength}}.max({{maxLength}}){{/if~}}
|
||||
{{~#if (eq format 'date-time')}}.datetime(){{/if~}}
|
||||
{{~#if (eq format 'date-math')}}.superRefine(isValidDateMath){{/if~}}
|
||||
{{~#if (eq format 'uuid')}}.uuid(){{/if~}}
|
||||
{{~#if pattern}}.regex(/{{pattern}}/){{/if~}}
|
||||
{{~/if~}}
|
||||
{{#if transform}}.transform({{{transform}}}){{/if~}}
|
||||
{{~/inline~}}
|
||||
|
||||
|
|
|
@ -6,8 +6,9 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import Boom from '@hapi/boom';
|
||||
import { errors } from '@elastic/elasticsearch';
|
||||
import Boom from '@hapi/boom';
|
||||
import { stringifyZodError } from '@kbn/zod-helpers';
|
||||
import { ZodError } from 'zod';
|
||||
import { BadRequestError } from '../bad_request_error';
|
||||
|
||||
|
@ -60,15 +61,3 @@ export const transformError = (err: Error & Partial<errors.ResponseError>): Outp
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function stringifyZodError(err: ZodError<any>) {
|
||||
return err.issues
|
||||
.map((issue) => {
|
||||
// If the path is empty, the error is for the root object
|
||||
if (issue.path.length === 0) {
|
||||
return issue.message;
|
||||
}
|
||||
return `${issue.path.join('.')}: ${issue.message}`;
|
||||
})
|
||||
.join(', ');
|
||||
}
|
||||
|
|
|
@ -12,5 +12,8 @@
|
|||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/zod-helpers",
|
||||
]
|
||||
}
|
||||
|
|
14
packages/kbn-zod-helpers/README.md
Normal file
14
packages/kbn-zod-helpers/README.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
# Helpers and utilities for Zod
|
||||
|
||||
[Zod](https://zod.dev/) is a schema validation library with static type inference for TypeScript.
|
||||
|
||||
Helpers defined in this package:
|
||||
|
||||
- Can be used in other packages and plugins to make it easier to define schemas with Zod, such as API schemas.
|
||||
- Are already used in `packages/kbn-openapi-generator`.
|
||||
- Are already used in `x-pack/plugins/security_solution`.
|
||||
|
||||
When you add some helper code to this package, please make sure that:
|
||||
|
||||
- The code is generic and domain-agnostic (doesn't "know" about any domains such as Security or Observability).
|
||||
- The code is reusable and there are already a few use cases for it. Try to not generalize prematurely.
|
13
packages/kbn-zod-helpers/index.ts
Normal file
13
packages/kbn-zod-helpers/index.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export * from './src/expect_parse_error';
|
||||
export * from './src/expect_parse_success';
|
||||
export * from './src/is_valid_date_math';
|
||||
export * from './src/required_optional';
|
||||
export * from './src/stringify_zod_error';
|
13
packages/kbn-zod-helpers/jest.config.js
Normal file
13
packages/kbn-zod-helpers/jest.config.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../..',
|
||||
roots: ['<rootDir>/packages/kbn-zod-helpers'],
|
||||
};
|
6
packages/kbn-zod-helpers/kibana.jsonc
Normal file
6
packages/kbn-zod-helpers/kibana.jsonc
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"devOnly": false,
|
||||
"id": "@kbn/zod-helpers",
|
||||
"owner": "@elastic/security-detection-rule-management",
|
||||
"type": "shared-common"
|
||||
}
|
7
packages/kbn-zod-helpers/package.json
Normal file
7
packages/kbn-zod-helpers/package.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"description": "Zod helpers for Kibana",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0",
|
||||
"name": "@kbn/zod-helpers",
|
||||
"private": true,
|
||||
"version": "1.0.0"
|
||||
}
|
15
packages/kbn-zod-helpers/src/expect_parse_error.ts
Normal file
15
packages/kbn-zod-helpers/src/expect_parse_error.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { SafeParseError, SafeParseReturnType } from 'zod';
|
||||
|
||||
export function expectParseError<Input, Output>(
|
||||
result: SafeParseReturnType<Input, Output>
|
||||
): asserts result is SafeParseError<Input> {
|
||||
expect(result.success).toEqual(false);
|
||||
}
|
|
@ -1,17 +1,12 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { SafeParseError, SafeParseReturnType, SafeParseSuccess } from 'zod';
|
||||
|
||||
export function expectParseError<Input, Output>(
|
||||
result: SafeParseReturnType<Input, Output>
|
||||
): asserts result is SafeParseError<Input> {
|
||||
expect(result.success).toEqual(false);
|
||||
}
|
||||
import type { SafeParseReturnType, SafeParseSuccess } from 'zod';
|
||||
|
||||
export function expectParseSuccess<Input, Output>(
|
||||
result: SafeParseReturnType<Input, Output>
|
31
packages/kbn-zod-helpers/src/is_valid_date_math.ts
Normal file
31
packages/kbn-zod-helpers/src/is_valid_date_math.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import * as z from 'zod';
|
||||
import dateMath from '@kbn/datemath';
|
||||
|
||||
function validateDateMath(time: string): boolean {
|
||||
const isValidDateString = !isNaN(Date.parse(time));
|
||||
if (isValidDateString) {
|
||||
return true;
|
||||
}
|
||||
const isDateMath = time.trim().startsWith('now');
|
||||
if (isDateMath) {
|
||||
return Boolean(dateMath.parse(time));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isValidDateMath(input: string, ctx: z.RefinementCtx) {
|
||||
if (!validateDateMath(input)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: 'Failed to parse date-math expression',
|
||||
});
|
||||
}
|
||||
}
|
41
packages/kbn-zod-helpers/src/required_optional.ts
Normal file
41
packages/kbn-zod-helpers/src/required_optional.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Make any optional fields required, but add `| undefined` to their type.
|
||||
*
|
||||
* This bit of logic is to force all fields to be accounted for in conversions
|
||||
* from the internal rule schema to the response schema. Rather than use
|
||||
* partial, which makes each field optional, we make each field required but
|
||||
* possibly undefined. The result is that if a field is forgotten in the
|
||||
* conversion from internal schema to response schema TS will report an error.
|
||||
* If we just used partial instead, then optional fields can be accidentally
|
||||
* omitted from the conversion - and any actual values in those fields
|
||||
* internally will be stripped in the response.
|
||||
*
|
||||
* @example
|
||||
* type A = RequiredOptional<{ a?: string; b: number }>;
|
||||
* will yield a type of: type A = { a: string | undefined; b: number; }
|
||||
*
|
||||
* @note
|
||||
* We should consider removing this logic altogether from our schemas and use it
|
||||
* in place with converters whenever needed.
|
||||
*/
|
||||
export type RequiredOptional<T> = { [K in keyof T]-?: [T[K]] } extends infer U
|
||||
? U extends Record<keyof U, [unknown]>
|
||||
? { [K in keyof U]: U[K][0] }
|
||||
: never
|
||||
: never;
|
||||
|
||||
/**
|
||||
* This helper designed to be used with `z.transform` to make all optional fields required.
|
||||
*
|
||||
* @param schema Zod schema
|
||||
* @returns The same schema but with all optional fields required.
|
||||
*/
|
||||
export const requiredOptional = <T>(schema: T) => schema as RequiredOptional<T>;
|
21
packages/kbn-zod-helpers/src/stringify_zod_error.ts
Normal file
21
packages/kbn-zod-helpers/src/stringify_zod_error.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ZodError } from 'zod';
|
||||
|
||||
export function stringifyZodError(err: ZodError<any>) {
|
||||
return err.issues
|
||||
.map((issue) => {
|
||||
// If the path is empty, the error is for the root object
|
||||
if (issue.path.length === 0) {
|
||||
return issue.message;
|
||||
}
|
||||
return `${issue.path.join('.')}: ${issue.message}`;
|
||||
})
|
||||
.join(', ');
|
||||
}
|
12
packages/kbn-zod-helpers/tsconfig.json
Normal file
12
packages/kbn-zod-helpers/tsconfig.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"exclude": ["target/**/*"],
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": ["**/*.ts"],
|
||||
"kbn_references": [
|
||||
"@kbn/datemath",
|
||||
]
|
||||
}
|
|
@ -1650,6 +1650,8 @@
|
|||
"@kbn/xstate-utils/*": ["packages/kbn-xstate-utils/*"],
|
||||
"@kbn/yarn-lock-validator": ["packages/kbn-yarn-lock-validator"],
|
||||
"@kbn/yarn-lock-validator/*": ["packages/kbn-yarn-lock-validator/*"],
|
||||
"@kbn/zod-helpers": ["packages/kbn-zod-helpers"],
|
||||
"@kbn/zod-helpers/*": ["packages/kbn-zod-helpers/*"],
|
||||
// END AUTOMATED PACKAGE LISTING
|
||||
// Allows for importing from `kibana` package for the exported types.
|
||||
"@emotion/core": [
|
||||
|
|
|
@ -70,7 +70,8 @@ import type {
|
|||
ALERT_RULE_TIMESTAMP_OVERRIDE,
|
||||
} from '../../../../../field_maps/field_names';
|
||||
// TODO: Create and import 8.0.0 versioned RuleAlertAction type
|
||||
import type { RuleAlertAction, SearchTypes } from '../../../../../detection_engine/types';
|
||||
import type { SearchTypes } from '../../../../../detection_engine/types';
|
||||
import type { RuleAction } from '../../rule_schema';
|
||||
|
||||
/* DO NOT MODIFY THIS SCHEMA TO ADD NEW FIELDS. These types represent the alerts that shipped in 8.0.0.
|
||||
Any changes to these types should be bug fixes so the types more accurately represent the alerts from 8.0.0.
|
||||
|
@ -110,7 +111,7 @@ export interface BaseFields800 {
|
|||
[ALERT_RISK_SCORE]: number;
|
||||
// TODO: version rule schemas and pull in 8.0.0 versioned rule schema to define alert rule parameters type
|
||||
[ALERT_RULE_PARAMETERS]: { [key: string]: SearchTypes };
|
||||
[ALERT_RULE_ACTIONS]: RuleAlertAction[];
|
||||
[ALERT_RULE_ACTIONS]: RuleAction[];
|
||||
[ALERT_RULE_AUTHOR]: string[];
|
||||
[ALERT_RULE_CREATED_AT]: string;
|
||||
[ALERT_RULE_CREATED_BY]: string;
|
||||
|
|
|
@ -4,8 +4,9 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ErrorSchema } from './error_schema';
|
||||
// TODO https://github.com/elastic/security-team/issues/7491
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import type { ErrorSchema } from './error_schema_legacy';
|
||||
|
||||
export const getErrorSchemaMock = (
|
||||
id: string = '819eded6-e9c8-445b-a647-519aea39e063'
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
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 { ErrorSchema } from './error_schema';
|
||||
// TODO https://github.com/elastic/security-team/issues/7491
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { ErrorSchema } from './error_schema_legacy';
|
||||
import { getErrorSchemaMock } from './error_schema.mock';
|
||||
|
||||
describe('error_schema', () => {
|
||||
|
|
|
@ -8,7 +8,10 @@
|
|||
import { NonEmptyString } from '@kbn/securitysolution-io-ts-types';
|
||||
import * as t from 'io-ts';
|
||||
|
||||
import { RuleSignatureId } from './rule_schema';
|
||||
// TODO https://github.com/elastic/security-team/issues/7491
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { RuleSignatureId } from './rule_schema_legacy';
|
||||
|
||||
import { status_code, message } from './schemas';
|
||||
|
||||
// We use id: t.string intentionally and _never_ the id from global schemas as
|
|
@ -8,8 +8,12 @@
|
|||
export * from './alerts';
|
||||
export * from './rule_response_actions';
|
||||
export * from './rule_schema';
|
||||
export * from './error_schema';
|
||||
// TODO https://github.com/elastic/security-team/issues/7491
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
export * from './error_schema_legacy';
|
||||
export * from './pagination';
|
||||
export * from './schemas';
|
||||
export * from './sorting';
|
||||
export * from './warning_schema';
|
||||
// TODO https://github.com/elastic/security-team/issues/7491
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
export * from './sorting_legacy';
|
||||
export * from './warning_schema.gen';
|
||||
|
|
|
@ -1,21 +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 { ENABLED_AUTOMATED_RESPONSE_ACTION_COMMANDS } from '../../../../endpoint/service/response_actions/constants';
|
||||
|
||||
// to enable using RESPONSE_ACTION_API_COMMANDS_NAMES as a type
|
||||
function keyObject<T extends readonly string[]>(arr: T): { [K in T[number]]: null } {
|
||||
return Object.fromEntries(arr.map((v) => [v, null])) as never;
|
||||
}
|
||||
|
||||
export const EndpointParams = t.type({
|
||||
command: t.keyof(keyObject(ENABLED_AUTOMATED_RESPONSE_ACTION_COMMANDS)),
|
||||
comment: t.union([t.string, t.undefined]),
|
||||
});
|
||||
|
||||
export type EndpointParams = t.TypeOf<typeof EndpointParams>;
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export * from './response_actions';
|
||||
export * from './endpoint';
|
||||
export * from './osquery';
|
||||
// TODO https://github.com/elastic/security-team/issues/7491
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
export { RESPONSE_ACTION_TYPES, SUPPORTED_RESPONSE_ACTION_TYPES } from './response_actions_legacy';
|
||||
export * from './response_actions.gen';
|
||||
|
|
|
@ -1,25 +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 { ecsMapping, arrayQueries } from '@kbn/osquery-io-ts-types';
|
||||
|
||||
export const OsqueryParams = t.type({
|
||||
query: t.union([t.string, t.undefined]),
|
||||
ecs_mapping: t.union([ecsMapping, t.undefined]),
|
||||
queries: t.union([arrayQueries, t.undefined]),
|
||||
pack_id: t.union([t.string, t.undefined]),
|
||||
saved_query_id: t.union([t.string, t.undefined]),
|
||||
});
|
||||
|
||||
export const OsqueryParamsCamelCase = t.type({
|
||||
query: t.union([t.string, t.undefined]),
|
||||
ecsMapping: t.union([ecsMapping, t.undefined]),
|
||||
queries: t.union([arrayQueries, t.undefined]),
|
||||
packId: t.union([t.string, t.undefined]),
|
||||
savedQueryId: t.union([t.string, t.undefined]),
|
||||
});
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* 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';
|
||||
import { requiredOptional } from '@kbn/zod-helpers';
|
||||
|
||||
/*
|
||||
* NOTICE: Do not edit this file manually.
|
||||
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
|
||||
*/
|
||||
|
||||
export type ResponseActionTypes = z.infer<typeof ResponseActionTypes>;
|
||||
export const ResponseActionTypes = z.enum(['.osquery', '.endpoint']);
|
||||
export type ResponseActionTypesEnum = typeof ResponseActionTypes.enum;
|
||||
export const ResponseActionTypesEnum = ResponseActionTypes.enum;
|
||||
|
||||
export type EcsMapping = z.infer<typeof EcsMapping>;
|
||||
export const EcsMapping = z.object({}).catchall(
|
||||
z.object({
|
||||
field: z.string().optional(),
|
||||
value: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
})
|
||||
);
|
||||
|
||||
export type OsqueryQuery = z.infer<typeof OsqueryQuery>;
|
||||
export const OsqueryQuery = z
|
||||
.object({
|
||||
/**
|
||||
* Query ID
|
||||
*/
|
||||
id: z.string(),
|
||||
/**
|
||||
* Query to execute
|
||||
*/
|
||||
query: z.string(),
|
||||
ecs_mapping: EcsMapping.optional(),
|
||||
/**
|
||||
* Query version
|
||||
*/
|
||||
version: z.string().optional(),
|
||||
platform: z.string().optional(),
|
||||
removed: z.boolean().optional(),
|
||||
snapshot: z.boolean().optional(),
|
||||
})
|
||||
.transform(requiredOptional);
|
||||
|
||||
export type OsqueryParams = z.infer<typeof OsqueryParams>;
|
||||
export const OsqueryParams = z
|
||||
.object({
|
||||
query: z.string().optional(),
|
||||
ecs_mapping: EcsMapping.optional(),
|
||||
queries: z.array(OsqueryQuery).optional(),
|
||||
pack_id: z.string().optional(),
|
||||
saved_query_id: z.string().optional(),
|
||||
})
|
||||
.transform(requiredOptional);
|
||||
|
||||
export type OsqueryParamsCamelCase = z.infer<typeof OsqueryParamsCamelCase>;
|
||||
export const OsqueryParamsCamelCase = z
|
||||
.object({
|
||||
query: z.string().optional(),
|
||||
ecsMapping: EcsMapping.optional(),
|
||||
queries: z.array(OsqueryQuery).optional(),
|
||||
packId: z.string().optional(),
|
||||
savedQueryId: z.string().optional(),
|
||||
})
|
||||
.transform(requiredOptional);
|
||||
|
||||
export type OsqueryResponseAction = z.infer<typeof OsqueryResponseAction>;
|
||||
export const OsqueryResponseAction = z.object({
|
||||
action_type_id: z.literal('.osquery'),
|
||||
params: OsqueryParams,
|
||||
});
|
||||
|
||||
export type RuleResponseOsqueryAction = z.infer<typeof RuleResponseOsqueryAction>;
|
||||
export const RuleResponseOsqueryAction = z.object({
|
||||
actionTypeId: z.literal('.osquery'),
|
||||
params: OsqueryParamsCamelCase,
|
||||
});
|
||||
|
||||
export type EndpointParams = z.infer<typeof EndpointParams>;
|
||||
export const EndpointParams = z
|
||||
.object({
|
||||
command: z.literal('isolate'),
|
||||
comment: z.string().optional(),
|
||||
})
|
||||
.transform(requiredOptional);
|
||||
|
||||
export type EndpointResponseAction = z.infer<typeof EndpointResponseAction>;
|
||||
export const EndpointResponseAction = z.object({
|
||||
action_type_id: z.literal('.endpoint'),
|
||||
params: EndpointParams,
|
||||
});
|
||||
|
||||
export type RuleResponseEndpointAction = z.infer<typeof RuleResponseEndpointAction>;
|
||||
export const RuleResponseEndpointAction = z.object({
|
||||
actionTypeId: z.literal('.endpoint'),
|
||||
params: EndpointParams,
|
||||
});
|
||||
|
||||
export type ResponseAction = z.infer<typeof ResponseAction>;
|
||||
export const ResponseAction = z.union([OsqueryResponseAction, EndpointResponseAction]);
|
||||
|
||||
export type RuleResponseAction = z.infer<typeof RuleResponseAction>;
|
||||
export const RuleResponseAction = z.union([RuleResponseOsqueryAction, RuleResponseEndpointAction]);
|
|
@ -0,0 +1,164 @@
|
|||
openapi: 3.0.0
|
||||
info:
|
||||
title: Response Actions Schema
|
||||
version: 'not applicable'
|
||||
paths: {}
|
||||
components:
|
||||
x-codegen-enabled: true
|
||||
schemas:
|
||||
ResponseActionTypes:
|
||||
type: string
|
||||
enum:
|
||||
- .osquery
|
||||
- .endpoint
|
||||
|
||||
EcsMapping:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: object
|
||||
properties:
|
||||
field:
|
||||
type: string
|
||||
value:
|
||||
oneOf:
|
||||
- type: string
|
||||
- type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
OsqueryQuery:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: Query ID
|
||||
query:
|
||||
type: string
|
||||
description: Query to execute
|
||||
ecs_mapping:
|
||||
$ref: '#/components/schemas/EcsMapping'
|
||||
version:
|
||||
type: string
|
||||
description: Query version
|
||||
platform:
|
||||
type: string
|
||||
removed:
|
||||
type: boolean
|
||||
snapshot:
|
||||
type: boolean
|
||||
required:
|
||||
- id
|
||||
- query
|
||||
x-modify: requiredOptional
|
||||
|
||||
OsqueryParams:
|
||||
type: object
|
||||
properties:
|
||||
query:
|
||||
type: string
|
||||
ecs_mapping:
|
||||
$ref: '#/components/schemas/EcsMapping'
|
||||
queries:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/OsqueryQuery'
|
||||
pack_id:
|
||||
type: string
|
||||
saved_query_id:
|
||||
type: string
|
||||
x-modify: requiredOptional
|
||||
|
||||
OsqueryParamsCamelCase:
|
||||
type: object
|
||||
properties:
|
||||
query:
|
||||
type: string
|
||||
ecsMapping:
|
||||
$ref: '#/components/schemas/EcsMapping'
|
||||
queries:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/OsqueryQuery'
|
||||
packId:
|
||||
type: string
|
||||
savedQueryId:
|
||||
type: string
|
||||
x-modify: requiredOptional
|
||||
|
||||
OsqueryResponseAction:
|
||||
type: object
|
||||
properties:
|
||||
action_type_id:
|
||||
type: string
|
||||
enum:
|
||||
- .osquery
|
||||
params:
|
||||
$ref: '#/components/schemas/OsqueryParams'
|
||||
required:
|
||||
- action_type_id
|
||||
- params
|
||||
|
||||
# Camel cased versions of OsqueryResponseAction
|
||||
RuleResponseOsqueryAction:
|
||||
type: object
|
||||
properties:
|
||||
actionTypeId:
|
||||
type: string
|
||||
enum:
|
||||
- .osquery
|
||||
params:
|
||||
$ref: '#/components/schemas/OsqueryParamsCamelCase'
|
||||
required:
|
||||
- actionTypeId
|
||||
- params
|
||||
|
||||
EndpointParams:
|
||||
type: object
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
enum:
|
||||
- isolate
|
||||
comment:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
x-modify: requiredOptional
|
||||
|
||||
EndpointResponseAction:
|
||||
type: object
|
||||
properties:
|
||||
action_type_id:
|
||||
type: string
|
||||
enum:
|
||||
- .endpoint
|
||||
params:
|
||||
$ref: '#/components/schemas/EndpointParams'
|
||||
required:
|
||||
- action_type_id
|
||||
- params
|
||||
|
||||
# Camel cased versions of EndpointResponseAction
|
||||
RuleResponseEndpointAction:
|
||||
type: object
|
||||
properties:
|
||||
actionTypeId:
|
||||
type: string
|
||||
enum:
|
||||
- .endpoint
|
||||
params:
|
||||
$ref: '#/components/schemas/EndpointParams'
|
||||
required:
|
||||
- actionTypeId
|
||||
- params
|
||||
|
||||
ResponseAction:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/OsqueryResponseAction'
|
||||
- $ref: '#/components/schemas/EndpointResponseAction'
|
||||
|
||||
# Camel Cased versions of ResponseAction
|
||||
RuleResponseAction:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/RuleResponseOsqueryAction'
|
||||
- $ref: '#/components/schemas/RuleResponseEndpointAction'
|
|
@ -1,60 +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 { EndpointParams } from './endpoint';
|
||||
import { OsqueryParams, OsqueryParamsCamelCase } from './osquery';
|
||||
|
||||
export enum RESPONSE_ACTION_TYPES {
|
||||
OSQUERY = '.osquery',
|
||||
ENDPOINT = '.endpoint',
|
||||
}
|
||||
|
||||
export const SUPPORTED_RESPONSE_ACTION_TYPES = Object.values(RESPONSE_ACTION_TYPES);
|
||||
|
||||
// When we create new response action types, create a union of types
|
||||
export const OsqueryResponseActionRuleParam = t.strict({
|
||||
actionTypeId: t.literal(RESPONSE_ACTION_TYPES.OSQUERY),
|
||||
params: OsqueryParamsCamelCase,
|
||||
});
|
||||
|
||||
export type RuleResponseOsqueryAction = t.TypeOf<typeof OsqueryResponseActionRuleParam>;
|
||||
|
||||
export const EndpointResponseActionRuleParam = t.strict({
|
||||
actionTypeId: t.literal(RESPONSE_ACTION_TYPES.ENDPOINT),
|
||||
params: EndpointParams,
|
||||
});
|
||||
|
||||
export type RuleResponseEndpointAction = t.TypeOf<typeof EndpointResponseActionRuleParam>;
|
||||
|
||||
const ResponseActionRuleParam = t.union([
|
||||
OsqueryResponseActionRuleParam,
|
||||
EndpointResponseActionRuleParam,
|
||||
]);
|
||||
export type RuleResponseAction = t.TypeOf<typeof ResponseActionRuleParam>;
|
||||
|
||||
export const ResponseActionRuleParamsOrUndefined = t.union([
|
||||
t.array(ResponseActionRuleParam),
|
||||
t.undefined,
|
||||
]);
|
||||
|
||||
// When we create new response action types, create a union of types
|
||||
const OsqueryResponseAction = t.strict({
|
||||
action_type_id: t.literal(RESPONSE_ACTION_TYPES.OSQUERY),
|
||||
params: OsqueryParams,
|
||||
});
|
||||
|
||||
const EndpointResponseAction = t.strict({
|
||||
action_type_id: t.literal(RESPONSE_ACTION_TYPES.ENDPOINT),
|
||||
params: EndpointParams,
|
||||
});
|
||||
|
||||
const ResponseAction = t.union([OsqueryResponseAction, EndpointResponseAction]);
|
||||
|
||||
export const ResponseActionArray = t.array(ResponseAction);
|
||||
|
||||
export type ResponseAction = t.TypeOf<typeof ResponseAction>;
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { arrayQueries, ecsMapping } from '@kbn/osquery-io-ts-types';
|
||||
import * as t from 'io-ts';
|
||||
import { ENABLED_AUTOMATED_RESPONSE_ACTION_COMMANDS } from '../../../../endpoint/service/response_actions/constants';
|
||||
import { ResponseActionTypesEnum } from './response_actions.gen';
|
||||
|
||||
export const RESPONSE_ACTION_TYPES = {
|
||||
OSQUERY: ResponseActionTypesEnum['.osquery'],
|
||||
ENDPOINT: ResponseActionTypesEnum['.endpoint'],
|
||||
} as const;
|
||||
|
||||
export const SUPPORTED_RESPONSE_ACTION_TYPES = Object.values(RESPONSE_ACTION_TYPES);
|
||||
|
||||
// to enable using RESPONSE_ACTION_API_COMMANDS_NAMES as a type
|
||||
function keyObject<T extends readonly string[]>(arr: T): { [K in T[number]]: null } {
|
||||
return Object.fromEntries(arr.map((v) => [v, null])) as never;
|
||||
}
|
||||
|
||||
export type EndpointParams = t.TypeOf<typeof EndpointParams>;
|
||||
export const EndpointParams = t.type({
|
||||
command: t.keyof(keyObject(ENABLED_AUTOMATED_RESPONSE_ACTION_COMMANDS)),
|
||||
comment: t.union([t.string, t.undefined]),
|
||||
});
|
||||
|
||||
export const OsqueryParams = t.type({
|
||||
query: t.union([t.string, t.undefined]),
|
||||
ecs_mapping: t.union([ecsMapping, t.undefined]),
|
||||
queries: t.union([arrayQueries, t.undefined]),
|
||||
pack_id: t.union([t.string, t.undefined]),
|
||||
saved_query_id: t.union([t.string, t.undefined]),
|
||||
});
|
||||
|
||||
export const OsqueryParamsCamelCase = t.type({
|
||||
query: t.union([t.string, t.undefined]),
|
||||
ecsMapping: t.union([ecsMapping, t.undefined]),
|
||||
queries: t.union([arrayQueries, t.undefined]),
|
||||
packId: t.union([t.string, t.undefined]),
|
||||
savedQueryId: t.union([t.string, t.undefined]),
|
||||
});
|
||||
|
||||
// When we create new response action types, create a union of types
|
||||
export type RuleResponseOsqueryAction = t.TypeOf<typeof RuleResponseOsqueryAction>;
|
||||
export const RuleResponseOsqueryAction = t.strict({
|
||||
actionTypeId: t.literal(RESPONSE_ACTION_TYPES.OSQUERY),
|
||||
params: OsqueryParamsCamelCase,
|
||||
});
|
||||
|
||||
export type RuleResponseEndpointAction = t.TypeOf<typeof RuleResponseEndpointAction>;
|
||||
export const RuleResponseEndpointAction = t.strict({
|
||||
actionTypeId: t.literal(RESPONSE_ACTION_TYPES.ENDPOINT),
|
||||
params: EndpointParams,
|
||||
});
|
||||
|
||||
export type RuleResponseAction = t.TypeOf<typeof ResponseActionRuleParam>;
|
||||
const ResponseActionRuleParam = t.union([RuleResponseOsqueryAction, RuleResponseEndpointAction]);
|
||||
|
||||
export const ResponseActionRuleParamsOrUndefined = t.union([
|
||||
t.array(ResponseActionRuleParam),
|
||||
t.undefined,
|
||||
]);
|
||||
|
||||
// When we create new response action types, create a union of types
|
||||
const OsqueryResponseAction = t.strict({
|
||||
action_type_id: t.literal(RESPONSE_ACTION_TYPES.OSQUERY),
|
||||
params: OsqueryParams,
|
||||
});
|
||||
|
||||
const EndpointResponseAction = t.strict({
|
||||
action_type_id: t.literal(RESPONSE_ACTION_TYPES.ENDPOINT),
|
||||
params: EndpointParams,
|
||||
});
|
||||
|
||||
export type ResponseAction = t.TypeOf<typeof ResponseAction>;
|
||||
export const ResponseAction = t.union([OsqueryResponseAction, EndpointResponseAction]);
|
||||
|
||||
export const ResponseActionArray = t.array(ResponseAction);
|
|
@ -1,95 +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';
|
||||
|
||||
interface RuleFields<
|
||||
Required extends t.Props,
|
||||
Optional extends t.Props,
|
||||
Defaultable extends t.Props
|
||||
> {
|
||||
required: Required;
|
||||
optional: Optional;
|
||||
defaultable: Defaultable;
|
||||
}
|
||||
|
||||
export const buildRuleSchemas = <R extends t.Props, O extends t.Props, D extends t.Props>(
|
||||
fields: RuleFields<R, O, D>
|
||||
) => {
|
||||
return {
|
||||
...fields,
|
||||
create: buildCreateRuleSchema(fields.required, fields.optional, fields.defaultable),
|
||||
patch: buildPatchRuleSchema(fields.required, fields.optional, fields.defaultable),
|
||||
response: buildResponseRuleSchema(fields.required, fields.optional, fields.defaultable),
|
||||
};
|
||||
};
|
||||
|
||||
const buildCreateRuleSchema = <
|
||||
Required extends t.Props,
|
||||
Optional extends t.Props,
|
||||
Defaultable extends t.Props
|
||||
>(
|
||||
requiredFields: Required,
|
||||
optionalFields: Optional,
|
||||
defaultableFields: Defaultable
|
||||
) => {
|
||||
return t.intersection([
|
||||
t.exact(t.type(requiredFields)),
|
||||
t.exact(t.partial(optionalFields)),
|
||||
t.exact(t.partial(defaultableFields)),
|
||||
]);
|
||||
};
|
||||
|
||||
const buildPatchRuleSchema = <
|
||||
Required extends t.Props,
|
||||
Optional extends t.Props,
|
||||
Defaultable extends t.Props
|
||||
>(
|
||||
requiredFields: Required,
|
||||
optionalFields: Optional,
|
||||
defaultableFields: Defaultable
|
||||
) => {
|
||||
return t.intersection([
|
||||
t.partial(requiredFields),
|
||||
t.partial(optionalFields),
|
||||
t.partial(defaultableFields),
|
||||
]);
|
||||
};
|
||||
|
||||
export type OrUndefined<P extends t.Props> = {
|
||||
[K in keyof P]: P[K] | t.UndefinedC;
|
||||
};
|
||||
|
||||
export const orUndefined = <P extends t.Props>(props: P): OrUndefined<P> => {
|
||||
return Object.keys(props).reduce<t.Props>((acc, key) => {
|
||||
acc[key] = t.union([props[key], t.undefined]);
|
||||
return acc;
|
||||
}, {}) as OrUndefined<P>;
|
||||
};
|
||||
|
||||
export const buildResponseRuleSchema = <
|
||||
Required extends t.Props,
|
||||
Optional extends t.Props,
|
||||
Defaultable extends t.Props
|
||||
>(
|
||||
requiredFields: Required,
|
||||
optionalFields: Optional,
|
||||
defaultableFields: Defaultable
|
||||
) => {
|
||||
// This bit of logic is to force all fields to be accounted for in conversions from the internal
|
||||
// rule schema to the response schema. Rather than use `t.partial`, which makes each field optional,
|
||||
// we make each field required but possibly undefined. The result is that if a field is forgotten in
|
||||
// the conversion from internal schema to response schema TS will report an error. If we just used t.partial
|
||||
// instead, then optional fields can be accidentally omitted from the conversion - and any actual values
|
||||
// in those fields internally will be stripped in the response.
|
||||
const optionalWithUndefined = orUndefined(optionalFields);
|
||||
return t.intersection([
|
||||
t.exact(t.type(requiredFields)),
|
||||
t.exact(t.type(optionalWithUndefined)),
|
||||
t.exact(t.type(defaultableFields)),
|
||||
]);
|
||||
};
|
|
@ -6,20 +6,30 @@
|
|||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { requiredOptional, isValidDateMath } from '@kbn/zod-helpers';
|
||||
|
||||
/*
|
||||
* NOTICE: Do not edit this file manually.
|
||||
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A string that is not empty and does not contain only whitespace
|
||||
*/
|
||||
export type NonEmptyString = z.infer<typeof NonEmptyString>;
|
||||
export const NonEmptyString = z
|
||||
.string()
|
||||
.min(1)
|
||||
.regex(/^(?! *$).+$/);
|
||||
|
||||
/**
|
||||
* A universally unique identifier
|
||||
*/
|
||||
export type UUID = z.infer<typeof UUID>;
|
||||
export const UUID = z.string();
|
||||
export const UUID = z.string().uuid();
|
||||
|
||||
export type RuleObjectId = z.infer<typeof RuleObjectId>;
|
||||
export const RuleObjectId = z.string();
|
||||
export const RuleObjectId = UUID;
|
||||
|
||||
/**
|
||||
* Could be any string, not necessarily a UUID
|
||||
|
@ -33,21 +43,100 @@ export const RuleName = z.string().min(1);
|
|||
export type RuleDescription = z.infer<typeof RuleDescription>;
|
||||
export const RuleDescription = z.string().min(1);
|
||||
|
||||
/**
|
||||
* The rule's version number.
|
||||
*/
|
||||
export type RuleVersion = z.infer<typeof RuleVersion>;
|
||||
export const RuleVersion = z.string();
|
||||
export const RuleVersion = z.number().int().min(1);
|
||||
|
||||
export type QueryLanguage = z.infer<typeof QueryLanguage>;
|
||||
export const QueryLanguage = z.enum(['kuery', 'lucene', 'eql', 'esql']);
|
||||
export type QueryLanguageEnum = typeof QueryLanguage.enum;
|
||||
export const QueryLanguageEnum = QueryLanguage.enum;
|
||||
|
||||
export type KqlQueryLanguage = z.infer<typeof KqlQueryLanguage>;
|
||||
export const KqlQueryLanguage = z.enum(['kuery', 'lucene']);
|
||||
export type KqlQueryLanguageEnum = typeof KqlQueryLanguage.enum;
|
||||
export const KqlQueryLanguageEnum = KqlQueryLanguage.enum;
|
||||
|
||||
export type IsRuleImmutable = z.infer<typeof IsRuleImmutable>;
|
||||
export const IsRuleImmutable = z.boolean();
|
||||
|
||||
/**
|
||||
* Determines whether the rule is enabled.
|
||||
*/
|
||||
export type IsRuleEnabled = z.infer<typeof IsRuleEnabled>;
|
||||
export const IsRuleEnabled = z.boolean();
|
||||
|
||||
/**
|
||||
* Frequency of rule execution, using a date math range. For example, "1h" means the rule runs every hour. Defaults to 5m (5 minutes).
|
||||
*/
|
||||
export type RuleInterval = z.infer<typeof RuleInterval>;
|
||||
export const RuleInterval = z.string();
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
export type RuleIntervalFrom = z.infer<typeof RuleIntervalFrom>;
|
||||
export const RuleIntervalFrom = z.string().superRefine(isValidDateMath);
|
||||
|
||||
export type RuleIntervalTo = z.infer<typeof RuleIntervalTo>;
|
||||
export const RuleIntervalTo = z.string();
|
||||
|
||||
/**
|
||||
* Risk score (0 to 100)
|
||||
*/
|
||||
export type RiskScore = z.infer<typeof RiskScore>;
|
||||
export const RiskScore = z.number().int().min(0).max(100);
|
||||
|
||||
/**
|
||||
* Overrides generated alerts' risk_score with a value from the source event
|
||||
*/
|
||||
export type RiskScoreMapping = z.infer<typeof RiskScoreMapping>;
|
||||
export const RiskScoreMapping = z.array(
|
||||
z
|
||||
.object({
|
||||
field: z.string(),
|
||||
operator: z.literal('equals'),
|
||||
value: z.string(),
|
||||
risk_score: RiskScore.optional(),
|
||||
})
|
||||
.transform(requiredOptional)
|
||||
);
|
||||
|
||||
/**
|
||||
* Severity of the rule
|
||||
*/
|
||||
export type Severity = z.infer<typeof Severity>;
|
||||
export const Severity = z.enum(['low', 'medium', 'high', 'critical']);
|
||||
export type SeverityEnum = typeof Severity.enum;
|
||||
export const SeverityEnum = Severity.enum;
|
||||
|
||||
/**
|
||||
* Overrides generated alerts' severity with values from the source event
|
||||
*/
|
||||
export type SeverityMapping = z.infer<typeof SeverityMapping>;
|
||||
export const SeverityMapping = z.array(
|
||||
z.object({
|
||||
field: z.string(),
|
||||
operator: z.literal('equals'),
|
||||
severity: Severity,
|
||||
value: z.string(),
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* String array containing words and phrases to help categorize, filter, and search rules. Defaults to an empty array.
|
||||
*/
|
||||
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 const RuleMetadata = z.object({}).catchall(z.unknown());
|
||||
|
||||
/**
|
||||
* The rule's license.
|
||||
*/
|
||||
export type RuleLicense = z.infer<typeof RuleLicense>;
|
||||
export const RuleLicense = z.string();
|
||||
|
||||
|
@ -60,26 +149,38 @@ export const RuleFalsePositiveArray = z.array(z.string());
|
|||
export type RuleReferenceArray = z.infer<typeof RuleReferenceArray>;
|
||||
export const RuleReferenceArray = z.array(z.string());
|
||||
|
||||
/**
|
||||
* Notes to help investigate alerts produced by the rule.
|
||||
*/
|
||||
export type InvestigationGuide = z.infer<typeof InvestigationGuide>;
|
||||
export const InvestigationGuide = z.string();
|
||||
|
||||
export type SetupGuide = z.infer<typeof SetupGuide>;
|
||||
export const SetupGuide = z.string();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export type BuildingBlockType = z.infer<typeof BuildingBlockType>;
|
||||
export const BuildingBlockType = z.string();
|
||||
|
||||
/**
|
||||
* (deprecated) Has no effect.
|
||||
*/
|
||||
export type AlertsIndex = z.infer<typeof AlertsIndex>;
|
||||
export const AlertsIndex = z.string();
|
||||
|
||||
/**
|
||||
* Has no effect.
|
||||
*/
|
||||
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({
|
||||
export type ThreatSubtechnique = z.infer<typeof ThreatSubtechnique>;
|
||||
export const ThreatSubtechnique = z.object({
|
||||
/**
|
||||
* Subtechnique ID
|
||||
*/
|
||||
|
@ -94,8 +195,8 @@ export const Subtechnique = z.object({
|
|||
reference: z.string(),
|
||||
});
|
||||
|
||||
export type Technique = z.infer<typeof Technique>;
|
||||
export const Technique = z.object({
|
||||
export type ThreatTechnique = z.infer<typeof ThreatTechnique>;
|
||||
export const ThreatTechnique = z.object({
|
||||
/**
|
||||
* Technique ID
|
||||
*/
|
||||
|
@ -111,7 +212,23 @@ export const Technique = z.object({
|
|||
/**
|
||||
* Array containing more specific information on the attack technique
|
||||
*/
|
||||
subtechnique: z.array(Subtechnique).optional(),
|
||||
subtechnique: z.array(ThreatSubtechnique).optional(),
|
||||
});
|
||||
|
||||
export type ThreatTactic = z.infer<typeof ThreatTactic>;
|
||||
export const ThreatTactic = z.object({
|
||||
/**
|
||||
* Tactic ID
|
||||
*/
|
||||
id: z.string(),
|
||||
/**
|
||||
* Tactic name
|
||||
*/
|
||||
name: z.string(),
|
||||
/**
|
||||
* Tactic reference
|
||||
*/
|
||||
reference: z.string(),
|
||||
});
|
||||
|
||||
export type Threat = z.infer<typeof Threat>;
|
||||
|
@ -120,24 +237,11 @@ 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(),
|
||||
}),
|
||||
tactic: ThreatTactic,
|
||||
/**
|
||||
* Array containing information on the attack techniques (optional)
|
||||
*/
|
||||
technique: z.array(Technique).optional(),
|
||||
technique: z.array(ThreatTechnique).optional(),
|
||||
});
|
||||
|
||||
export type ThreatArray = z.infer<typeof ThreatArray>;
|
||||
|
@ -149,41 +253,59 @@ export const IndexPatternArray = z.array(z.string());
|
|||
export type DataViewId = z.infer<typeof DataViewId>;
|
||||
export const DataViewId = z.string();
|
||||
|
||||
export type SavedQueryId = z.infer<typeof SavedQueryId>;
|
||||
export const SavedQueryId = 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 const RuleFilterArray = z.array(z.unknown());
|
||||
|
||||
/**
|
||||
* Sets the source field for the alert's signal.rule.name value
|
||||
*/
|
||||
export type RuleNameOverride = z.infer<typeof RuleNameOverride>;
|
||||
export const RuleNameOverride = z.string();
|
||||
|
||||
/**
|
||||
* Sets the time field used to query indices
|
||||
*/
|
||||
export type TimestampOverride = z.infer<typeof TimestampOverride>;
|
||||
export const TimestampOverride = z.string();
|
||||
|
||||
/**
|
||||
* Disables the fallback to the event's @timestamp field
|
||||
*/
|
||||
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(),
|
||||
name: NonEmptyString,
|
||||
type: NonEmptyString,
|
||||
ecs: z.boolean(),
|
||||
});
|
||||
|
||||
export type RequiredFieldArray = z.infer<typeof RequiredFieldArray>;
|
||||
export const RequiredFieldArray = z.array(RequiredField);
|
||||
|
||||
/**
|
||||
* Timeline template ID
|
||||
*/
|
||||
export type TimelineTemplateId = z.infer<typeof TimelineTemplateId>;
|
||||
export const TimelineTemplateId = z.string();
|
||||
|
||||
/**
|
||||
* Timeline template title
|
||||
*/
|
||||
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 const SavedObjectResolveOutcomeEnum = SavedObjectResolveOutcome.enum;
|
||||
|
||||
export type SavedObjectResolveAliasTargetId = z.infer<typeof SavedObjectResolveAliasTargetId>;
|
||||
export const SavedObjectResolveAliasTargetId = z.string();
|
||||
|
@ -193,15 +315,110 @@ export const SavedObjectResolveAliasPurpose = z.enum([
|
|||
'savedObjectConversion',
|
||||
'savedObjectImport',
|
||||
]);
|
||||
export const SavedObjectResolveAliasPurposeEnum = SavedObjectResolveAliasPurpose.enum;
|
||||
export type SavedObjectResolveAliasPurposeEnum = typeof SavedObjectResolveAliasPurpose.enum;
|
||||
export const SavedObjectResolveAliasPurposeEnum = 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(),
|
||||
package: NonEmptyString,
|
||||
version: NonEmptyString,
|
||||
integration: NonEmptyString.optional(),
|
||||
});
|
||||
|
||||
export type RelatedIntegrationArray = z.infer<typeof RelatedIntegrationArray>;
|
||||
export const RelatedIntegrationArray = z.array(RelatedIntegration);
|
||||
|
||||
export type InvestigationFields = z.infer<typeof InvestigationFields>;
|
||||
export const InvestigationFields = z.object({
|
||||
field_names: z.array(NonEmptyString).min(1),
|
||||
});
|
||||
|
||||
/**
|
||||
* Defines the interval on which a rule's actions are executed.
|
||||
*/
|
||||
export type RuleActionThrottle = z.infer<typeof RuleActionThrottle>;
|
||||
export const RuleActionThrottle = z.union([
|
||||
z.enum(['no_actions', 'rule']),
|
||||
z.string().regex(/^[1-9]\d*[smhd]$/),
|
||||
]);
|
||||
|
||||
/**
|
||||
* The condition for throttling the notification: `onActionGroupChange`, `onActiveAlert`, or `onThrottleInterval`
|
||||
*/
|
||||
export type RuleActionNotifyWhen = z.infer<typeof RuleActionNotifyWhen>;
|
||||
export const RuleActionNotifyWhen = z.enum([
|
||||
'onActiveAlert',
|
||||
'onThrottleInterval',
|
||||
'onActionGroupChange',
|
||||
]);
|
||||
export type RuleActionNotifyWhenEnum = typeof RuleActionNotifyWhen.enum;
|
||||
export const RuleActionNotifyWhenEnum = RuleActionNotifyWhen.enum;
|
||||
|
||||
/**
|
||||
* The action frequency defines when the action runs (for example, only on rule execution or at specific time intervals).
|
||||
*/
|
||||
export type RuleActionFrequency = z.infer<typeof RuleActionFrequency>;
|
||||
export const RuleActionFrequency = z.object({
|
||||
/**
|
||||
* Action summary indicates whether we will send a summary notification about all the generate alerts or notification per individual alert
|
||||
*/
|
||||
summary: z.boolean(),
|
||||
notifyWhen: RuleActionNotifyWhen,
|
||||
throttle: RuleActionThrottle.nullable(),
|
||||
});
|
||||
|
||||
export type RuleAction = z.infer<typeof RuleAction>;
|
||||
export const RuleAction = 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({}).catchall(z.unknown()),
|
||||
uuid: NonEmptyString.optional(),
|
||||
alerts_filter: z.object({}).catchall(z.unknown()).optional(),
|
||||
frequency: RuleActionFrequency.optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* The exception type
|
||||
*/
|
||||
export type ExceptionListType = z.infer<typeof ExceptionListType>;
|
||||
export const ExceptionListType = z.enum([
|
||||
'detection',
|
||||
'rule_default',
|
||||
'endpoint',
|
||||
'endpoint_trusted_apps',
|
||||
'endpoint_events',
|
||||
'endpoint_host_isolation_exceptions',
|
||||
'endpoint_blocklists',
|
||||
]);
|
||||
export type ExceptionListTypeEnum = typeof ExceptionListType.enum;
|
||||
export const ExceptionListTypeEnum = ExceptionListType.enum;
|
||||
|
||||
export type RuleExceptionList = z.infer<typeof RuleExceptionList>;
|
||||
export const RuleExceptionList = z.object({
|
||||
/**
|
||||
* ID of the exception container
|
||||
*/
|
||||
id: NonEmptyString,
|
||||
/**
|
||||
* List ID of the exception container
|
||||
*/
|
||||
list_id: NonEmptyString,
|
||||
type: ExceptionListType,
|
||||
/**
|
||||
* Determines the exceptions validity in rule's Kibana space
|
||||
*/
|
||||
namespace_type: z.enum(['agnostic', 'single']),
|
||||
});
|
||||
|
|
|
@ -6,13 +6,19 @@ paths: {}
|
|||
components:
|
||||
x-codegen-enabled: true
|
||||
schemas:
|
||||
NonEmptyString:
|
||||
type: string
|
||||
pattern: ^(?! *$).+$
|
||||
minLength: 1
|
||||
description: A string that is not empty and does not contain only whitespace
|
||||
|
||||
UUID:
|
||||
type: string
|
||||
format: uuid
|
||||
description: A universally unique identifier
|
||||
|
||||
RuleObjectId:
|
||||
type: string
|
||||
$ref: '#/components/schemas/UUID'
|
||||
|
||||
RuleSignatureId:
|
||||
type: string
|
||||
|
@ -27,19 +33,103 @@ components:
|
|||
minLength: 1
|
||||
|
||||
RuleVersion:
|
||||
type: integer
|
||||
minimum: 1
|
||||
description: The rule's version number.
|
||||
|
||||
QueryLanguage:
|
||||
type: string
|
||||
format: version
|
||||
enum:
|
||||
- kuery
|
||||
- lucene
|
||||
- eql
|
||||
- esql
|
||||
|
||||
KqlQueryLanguage:
|
||||
type: string
|
||||
enum:
|
||||
- kuery
|
||||
- lucene
|
||||
|
||||
IsRuleImmutable:
|
||||
type: boolean
|
||||
|
||||
IsRuleEnabled:
|
||||
type: boolean
|
||||
description: Determines whether the rule is enabled.
|
||||
|
||||
RuleInterval:
|
||||
type: string
|
||||
description: Frequency of rule execution, using a date math range. For example, "1h" means the rule runs every hour. Defaults to 5m (5 minutes).
|
||||
|
||||
RuleIntervalFrom:
|
||||
type: string
|
||||
description: 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).
|
||||
format: date-math
|
||||
|
||||
RuleIntervalTo:
|
||||
type: string
|
||||
|
||||
RiskScore:
|
||||
type: integer
|
||||
description: Risk score (0 to 100)
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
|
||||
RiskScoreMapping:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
field:
|
||||
type: string
|
||||
operator:
|
||||
type: string
|
||||
enum:
|
||||
- equals
|
||||
value:
|
||||
type: string
|
||||
risk_score:
|
||||
$ref: '#/components/schemas/RiskScore'
|
||||
required:
|
||||
- field
|
||||
- operator
|
||||
- value
|
||||
x-modify: requiredOptional
|
||||
description: Overrides generated alerts' risk_score with a value from the source event
|
||||
|
||||
Severity:
|
||||
type: string
|
||||
enum: [low, medium, high, critical]
|
||||
description: Severity of the rule
|
||||
|
||||
SeverityMapping:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
field:
|
||||
type: string
|
||||
operator:
|
||||
type: string
|
||||
enum:
|
||||
- equals
|
||||
severity:
|
||||
$ref: '#/components/schemas/Severity'
|
||||
value:
|
||||
type: string
|
||||
required:
|
||||
- field
|
||||
- operator
|
||||
- severity
|
||||
- value
|
||||
description: Overrides generated alerts' severity with values from the source event
|
||||
|
||||
RuleTagArray:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: String array containing words and phrases to help categorize, filter, and search rules. Defaults to an empty array.
|
||||
|
||||
RuleMetadata:
|
||||
type: object
|
||||
|
@ -47,6 +137,7 @@ components:
|
|||
|
||||
RuleLicense:
|
||||
type: string
|
||||
description: The rule's license.
|
||||
|
||||
RuleAuthorArray:
|
||||
type: array
|
||||
|
@ -65,24 +156,29 @@ components:
|
|||
|
||||
InvestigationGuide:
|
||||
type: string
|
||||
description: Notes to help investigate alerts produced by the rule.
|
||||
|
||||
SetupGuide:
|
||||
type: string
|
||||
|
||||
BuildingBlockType:
|
||||
type: string
|
||||
description: 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.
|
||||
|
||||
AlertsIndex:
|
||||
type: string
|
||||
description: (deprecated) Has no effect.
|
||||
deprecated: true
|
||||
|
||||
AlertsIndexNamespace:
|
||||
type: string
|
||||
description: Has no effect.
|
||||
|
||||
MaxSignals:
|
||||
type: integer
|
||||
minimum: 1
|
||||
|
||||
Subtechnique:
|
||||
ThreatSubtechnique:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
|
@ -99,7 +195,7 @@ components:
|
|||
- name
|
||||
- reference
|
||||
|
||||
Technique:
|
||||
ThreatTechnique:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
|
@ -114,13 +210,30 @@ components:
|
|||
subtechnique:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Subtechnique'
|
||||
$ref: '#/components/schemas/ThreatSubtechnique'
|
||||
description: Array containing more specific information on the attack technique
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- reference
|
||||
|
||||
ThreatTactic:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: Tactic ID
|
||||
name:
|
||||
type: string
|
||||
description: Tactic name
|
||||
reference:
|
||||
type: string
|
||||
description: Tactic reference
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- reference
|
||||
|
||||
Threat:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -128,25 +241,11 @@ components:
|
|||
type: string
|
||||
description: Relevant attack framework
|
||||
tactic:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: Tactic ID
|
||||
name:
|
||||
type: string
|
||||
description: Tactic name
|
||||
reference:
|
||||
type: string
|
||||
description: Tactic reference
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- reference
|
||||
$ref: '#/components/schemas/ThreatTactic'
|
||||
technique:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Technique'
|
||||
$ref: '#/components/schemas/ThreatTechnique'
|
||||
description: Array containing information on the attack techniques (optional)
|
||||
required:
|
||||
- framework
|
||||
|
@ -155,7 +254,7 @@ components:
|
|||
ThreatArray:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Threat' # Assuming a schema named 'Threat' is defined in the components section.
|
||||
$ref: '#/components/schemas/Threat'
|
||||
|
||||
IndexPatternArray:
|
||||
type: array
|
||||
|
@ -165,35 +264,41 @@ components:
|
|||
DataViewId:
|
||||
type: string
|
||||
|
||||
SavedQueryId:
|
||||
type: string
|
||||
|
||||
RuleQuery:
|
||||
type: string
|
||||
|
||||
RuleFilterArray:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
items: {} # unknown
|
||||
|
||||
RuleNameOverride:
|
||||
type: string
|
||||
description: Sets the source field for the alert's signal.rule.name value
|
||||
|
||||
TimestampOverride:
|
||||
type: string
|
||||
description: Sets the time field used to query indices
|
||||
|
||||
TimestampOverrideFallbackDisabled:
|
||||
type: boolean
|
||||
description: Disables the fallback to the event's @timestamp field
|
||||
|
||||
RequiredField:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
$ref: '#/components/schemas/NonEmptyString'
|
||||
type:
|
||||
type: string
|
||||
minLength: 1
|
||||
$ref: '#/components/schemas/NonEmptyString'
|
||||
ecs:
|
||||
type: boolean
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
- ecs
|
||||
|
||||
RequiredFieldArray:
|
||||
type: array
|
||||
|
@ -202,9 +307,11 @@ components:
|
|||
|
||||
TimelineTemplateId:
|
||||
type: string
|
||||
description: Timeline template ID
|
||||
|
||||
TimelineTemplateTitle:
|
||||
type: string
|
||||
description: Timeline template title
|
||||
|
||||
SavedObjectResolveOutcome:
|
||||
type: string
|
||||
|
@ -226,14 +333,11 @@ components:
|
|||
type: object
|
||||
properties:
|
||||
package:
|
||||
type: string
|
||||
minLength: 1
|
||||
$ref: '#/components/schemas/NonEmptyString'
|
||||
version:
|
||||
type: string
|
||||
minLength: 1
|
||||
$ref: '#/components/schemas/NonEmptyString'
|
||||
integration:
|
||||
type: string
|
||||
minLength: 1
|
||||
$ref: '#/components/schemas/NonEmptyString'
|
||||
required:
|
||||
- package
|
||||
- version
|
||||
|
@ -242,3 +346,117 @@ components:
|
|||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/RelatedIntegration'
|
||||
|
||||
# Schema for fields relating to investigation fields, these are user defined fields we use to highlight in various features in the UI such as alert details flyout and exceptions auto-population from alert. Added in PR #163235
|
||||
# Right now we only have a single field but anticipate adding more related fields to store various configuration states such as `override` - where a user might say if they want only these fields to display, or if they want these fields + the fields we select.
|
||||
InvestigationFields:
|
||||
type: object
|
||||
properties:
|
||||
field_names:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/NonEmptyString'
|
||||
minItems: 1
|
||||
required:
|
||||
- field_names
|
||||
|
||||
RuleActionThrottle:
|
||||
description: Defines the interval on which a rule's actions are executed.
|
||||
oneOf:
|
||||
- type: string
|
||||
enum:
|
||||
- 'no_actions'
|
||||
- 'rule'
|
||||
- type: string
|
||||
pattern: '^[1-9]\d*[smhd]$' # any number except zero followed by one of the suffixes 's', 'm', 'h', 'd'
|
||||
description: Time interval in seconds, minutes, hours, or days.
|
||||
example: '1h'
|
||||
|
||||
RuleActionNotifyWhen:
|
||||
type: string
|
||||
enum:
|
||||
- 'onActiveAlert'
|
||||
- 'onThrottleInterval'
|
||||
- 'onActionGroupChange'
|
||||
description: 'The condition for throttling the notification: `onActionGroupChange`, `onActiveAlert`, or `onThrottleInterval`'
|
||||
|
||||
RuleActionFrequency:
|
||||
type: object
|
||||
description: The action frequency defines when the action runs (for example, only on rule execution or at specific time intervals).
|
||||
properties:
|
||||
summary:
|
||||
type: boolean
|
||||
description: Action summary indicates whether we will send a summary notification about all the generate alerts or notification per individual alert
|
||||
notifyWhen:
|
||||
$ref: '#/components/schemas/RuleActionNotifyWhen'
|
||||
throttle:
|
||||
$ref: '#/components/schemas/RuleActionThrottle'
|
||||
nullable: true
|
||||
required:
|
||||
- summary
|
||||
- notifyWhen
|
||||
- throttle
|
||||
|
||||
RuleAction:
|
||||
type: object
|
||||
properties:
|
||||
action_type_id:
|
||||
type: string
|
||||
description: The action type used for sending notifications.
|
||||
group:
|
||||
type: string
|
||||
description: Optionally groups actions by use cases. Use `default` for alert notifications.
|
||||
id:
|
||||
type: string
|
||||
description: The connector ID.
|
||||
params:
|
||||
type: object
|
||||
description: Object containing the allowed connector fields, which varies according to the connector type.
|
||||
additionalProperties: true
|
||||
uuid:
|
||||
$ref: '#/components/schemas/NonEmptyString'
|
||||
alerts_filter:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
frequency:
|
||||
$ref: '#/components/schemas/RuleActionFrequency'
|
||||
required:
|
||||
- action_type_id
|
||||
- group
|
||||
- id
|
||||
- params
|
||||
|
||||
ExceptionListType:
|
||||
type: string
|
||||
description: The exception type
|
||||
enum:
|
||||
- detection
|
||||
- rule_default
|
||||
- endpoint
|
||||
- endpoint_trusted_apps
|
||||
- endpoint_events
|
||||
- endpoint_host_isolation_exceptions
|
||||
- endpoint_blocklists
|
||||
|
||||
RuleExceptionList:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
$ref: '#/components/schemas/NonEmptyString'
|
||||
description: ID of the exception container
|
||||
list_id:
|
||||
$ref: '#/components/schemas/NonEmptyString'
|
||||
description: List ID of the exception container
|
||||
type:
|
||||
$ref: '#/components/schemas/ExceptionListType'
|
||||
namespace_type:
|
||||
type: string
|
||||
description: Determines the exceptions validity in rule's Kibana space
|
||||
enum:
|
||||
- agnostic
|
||||
- single
|
||||
required:
|
||||
- id
|
||||
- list_id
|
||||
- type
|
||||
- namespace_type
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export * from './common_attributes';
|
||||
export * from './common_attributes.gen';
|
||||
export * from './rule_schemas.gen';
|
||||
|
||||
export * from './specific_attributes/eql_attributes';
|
||||
export * from './specific_attributes/new_terms_attributes';
|
||||
export * from './specific_attributes/query_attributes';
|
||||
export * from './specific_attributes/threshold_attributes';
|
||||
|
||||
export * from './rule_schemas';
|
||||
export * from './build_rule_schemas';
|
||||
export * from './specific_attributes/eql_attributes.gen';
|
||||
export * from './specific_attributes/ml_attributes.gen';
|
||||
export * from './specific_attributes/new_terms_attributes.gen';
|
||||
export * from './specific_attributes/query_attributes.gen';
|
||||
export * from './specific_attributes/threat_match_attributes.gen';
|
||||
export * from './specific_attributes/threshold_attributes.gen';
|
||||
|
|
|
@ -17,7 +17,7 @@ import type {
|
|||
ThresholdRuleCreateProps,
|
||||
NewTermsRuleCreateProps,
|
||||
NewTermsRuleUpdateProps,
|
||||
} from './rule_schemas';
|
||||
} from './rule_schemas.gen';
|
||||
|
||||
export const getCreateRulesSchemaMock = (ruleId = 'rule-1'): QueryRuleCreateProps => ({
|
||||
description: 'Detecting root and admin users',
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -14,7 +14,7 @@ import type {
|
|||
SavedQueryRule,
|
||||
SharedResponseProps,
|
||||
ThreatMatchRule,
|
||||
} from './rule_schemas';
|
||||
} from './rule_schemas.gen';
|
||||
import { getListArrayMock } from '../../../../detection_engine/schemas/types/lists.mock';
|
||||
|
||||
export const ANCHOR_DATE = '2020-02-20T03:57:54.037Z';
|
||||
|
|
|
@ -4,12 +4,8 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 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 { RuleResponse } from './rule_schemas';
|
||||
import { expectParseError, expectParseSuccess, stringifyZodError } from '@kbn/zod-helpers';
|
||||
import { RuleResponse } from './rule_schemas.gen';
|
||||
import {
|
||||
getRulesSchemaMock,
|
||||
getRulesMlSchemaMock,
|
||||
|
@ -23,37 +19,28 @@ describe('Rule response schema', () => {
|
|||
test('it should validate a type of "query" without anything extra', () => {
|
||||
const payload = getRulesSchemaMock();
|
||||
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
const expected = getRulesSchemaMock();
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(expected);
|
||||
const result = RuleResponse.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should NOT validate a type of "query" when it has extra data', () => {
|
||||
test('it should strip any extra data', () => {
|
||||
const payload: RuleResponse & { invalid_extra_data?: string } = getRulesSchemaMock();
|
||||
payload.invalid_extra_data = 'invalid_extra_data';
|
||||
const expected = getRulesSchemaMock();
|
||||
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']);
|
||||
expect(message.schema).toEqual({});
|
||||
const result = RuleResponse.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(expected);
|
||||
});
|
||||
|
||||
test('it should NOT validate invalid_data for the type', () => {
|
||||
const payload: Omit<RuleResponse, 'type'> & { type: string } = getRulesSchemaMock();
|
||||
payload.type = 'invalid_data';
|
||||
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toHaveLength(1);
|
||||
expect(message.schema).toEqual({});
|
||||
const result = RuleResponse.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toEqual('Invalid input');
|
||||
});
|
||||
|
||||
test('it should validate a type of "query" with a saved_id together', () => {
|
||||
|
@ -61,24 +48,17 @@ describe('Rule response schema', () => {
|
|||
payload.type = 'query';
|
||||
payload.saved_id = 'save id 123';
|
||||
|
||||
const decoded = RuleResponse.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 = RuleResponse.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate a type of "saved_query" with a "saved_id" dependent', () => {
|
||||
const payload = getSavedQuerySchemaMock();
|
||||
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
const expected = getSavedQuerySchemaMock();
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(expected);
|
||||
const result = RuleResponse.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should NOT validate a type of "saved_query" without a "saved_id" dependent', () => {
|
||||
|
@ -86,27 +66,9 @@ describe('Rule response schema', () => {
|
|||
// @ts-expect-error
|
||||
delete payload.saved_id;
|
||||
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "saved_id"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate a type of "saved_query" when it has extra data', () => {
|
||||
const payload: RuleResponse & { saved_id?: string; invalid_extra_data?: string } =
|
||||
getSavedQuerySchemaMock();
|
||||
payload.invalid_extra_data = 'invalid_extra_data';
|
||||
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']);
|
||||
expect(message.schema).toEqual({});
|
||||
const result = RuleResponse.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toEqual('Invalid input');
|
||||
});
|
||||
|
||||
test('it should validate a type of "timeline_id" if there is a "timeline_title" dependent', () => {
|
||||
|
@ -114,42 +76,19 @@ describe('Rule response schema', () => {
|
|||
payload.timeline_id = 'some timeline id';
|
||||
payload.timeline_title = 'some timeline title';
|
||||
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
const expected = getRulesSchemaMock();
|
||||
expected.timeline_id = 'some timeline id';
|
||||
expected.timeline_title = 'some timeline title';
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(expected);
|
||||
});
|
||||
|
||||
test('it should NOT validate a type of "timeline_id" if there is "timeline_title" dependent when it has extra invalid data', () => {
|
||||
const payload: RuleResponse & { invalid_extra_data?: string } = getRulesSchemaMock();
|
||||
payload.timeline_id = 'some timeline id';
|
||||
payload.timeline_title = 'some timeline title';
|
||||
payload.invalid_extra_data = 'invalid_extra_data';
|
||||
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']);
|
||||
expect(message.schema).toEqual({});
|
||||
const result = RuleResponse.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
describe('exceptions_list', () => {
|
||||
test('it should validate an empty array for "exceptions_list"', () => {
|
||||
const payload = getRulesSchemaMock();
|
||||
payload.exceptions_list = [];
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
const expected = getRulesSchemaMock();
|
||||
expected.exceptions_list = [];
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(expected);
|
||||
|
||||
const result = RuleResponse.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should NOT validate when "exceptions_list" is not expected type', () => {
|
||||
|
@ -157,49 +96,38 @@ describe('Rule response schema', () => {
|
|||
exceptions_list?: string;
|
||||
} = { ...getRulesSchemaMock(), exceptions_list: 'invalid_data' };
|
||||
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "invalid_data" supplied to "exceptions_list"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
const result = RuleResponse.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toEqual('Invalid input');
|
||||
});
|
||||
});
|
||||
|
||||
describe('esql rule type', () => {
|
||||
test('it should NOT validate a type of "esql" with "index" defined', () => {
|
||||
test('it should omit the "index" field', () => {
|
||||
const payload = { ...getEsqlRuleSchemaMock(), index: ['logs-*'] };
|
||||
const expected = getEsqlRuleSchemaMock();
|
||||
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['invalid keys "index,["logs-*"]"']);
|
||||
expect(message.schema).toEqual({});
|
||||
const result = RuleResponse.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(expected);
|
||||
});
|
||||
|
||||
test('it should NOT validate a type of "esql" with "filters" defined', () => {
|
||||
test('it should omit the "filters" field', () => {
|
||||
const payload = { ...getEsqlRuleSchemaMock(), filters: [] };
|
||||
const expected = getEsqlRuleSchemaMock();
|
||||
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['invalid keys "filters,[]"']);
|
||||
expect(message.schema).toEqual({});
|
||||
const result = RuleResponse.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(expected);
|
||||
});
|
||||
|
||||
test('it should NOT validate a type of "esql" with a "saved_id" dependent', () => {
|
||||
test('it should omit the "saved_id" field', () => {
|
||||
const payload = { ...getEsqlRuleSchemaMock(), saved_id: 'id' };
|
||||
const expected = getEsqlRuleSchemaMock();
|
||||
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['invalid keys "saved_id"']);
|
||||
expect(message.schema).toEqual({});
|
||||
const result = RuleResponse.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -207,13 +135,9 @@ describe('Rule response schema', () => {
|
|||
test('it should validate a type of "query" with "data_view_id" defined', () => {
|
||||
const payload = { ...getRulesSchemaMock(), data_view_id: 'logs-*' };
|
||||
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
const expected = { ...getRulesSchemaMock(), data_view_id: 'logs-*' };
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(expected);
|
||||
const result = RuleResponse.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate a type of "saved_query" with "data_view_id" defined', () => {
|
||||
|
@ -221,149 +145,93 @@ describe('Rule response schema', () => {
|
|||
getSavedQuerySchemaMock();
|
||||
payload.data_view_id = 'logs-*';
|
||||
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
const expected: RuleResponse & { saved_id?: string; data_view_id?: string } =
|
||||
getSavedQuerySchemaMock();
|
||||
|
||||
expected.data_view_id = 'logs-*';
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(expected);
|
||||
const result = RuleResponse.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate a type of "eql" with "data_view_id" defined', () => {
|
||||
const payload = { ...getRulesEqlSchemaMock(), data_view_id: 'logs-*' };
|
||||
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
const expected = { ...getRulesEqlSchemaMock(), data_view_id: 'logs-*' };
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(expected);
|
||||
const result = RuleResponse.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate a type of "threat_match" with "data_view_id" defined', () => {
|
||||
const payload = { ...getThreatMatchingSchemaMock(), data_view_id: 'logs-*' };
|
||||
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
const expected = { ...getThreatMatchingSchemaMock(), data_view_id: 'logs-*' };
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(expected);
|
||||
const result = RuleResponse.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should NOT validate a type of "machine_learning" with "data_view_id" defined', () => {
|
||||
test('it should omit the "data_view_id" field for "machine_learning"rules', () => {
|
||||
const payload = { ...getRulesMlSchemaMock(), data_view_id: 'logs-*' };
|
||||
const expected = getRulesMlSchemaMock();
|
||||
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['invalid keys "data_view_id"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate a type of "esql" with "data_view_id" defined', () => {
|
||||
const payload = { ...getEsqlRuleSchemaMock(), data_view_id: 'logs-*' };
|
||||
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['invalid keys "data_view_id"']);
|
||||
expect(message.schema).toEqual({});
|
||||
const result = RuleResponse.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('investigation_fields', () => {
|
||||
test('it should validate rule with "investigation_fields"', () => {
|
||||
const payload = getRulesSchemaMock();
|
||||
payload.investigation_fields = { field_names: ['foo', 'bar'] };
|
||||
test('it should omit the "data_view_id" field for "esql" rules', () => {
|
||||
const payload = { ...getEsqlRuleSchemaMock(), data_view_id: 'logs-*' };
|
||||
const expected = getEsqlRuleSchemaMock();
|
||||
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
const expected = {
|
||||
...getRulesSchemaMock(),
|
||||
investigation_fields: { field_names: ['foo', 'bar'] },
|
||||
};
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(expected);
|
||||
});
|
||||
|
||||
test('it should validate undefined for "investigation_fields"', () => {
|
||||
const payload: RuleResponse = {
|
||||
...getRulesSchemaMock(),
|
||||
investigation_fields: undefined,
|
||||
};
|
||||
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
const expected = { ...getRulesSchemaMock(), investigation_fields: undefined };
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(expected);
|
||||
});
|
||||
|
||||
test('it should validate "investigation_fields" not in schema', () => {
|
||||
const payload: RuleResponse = {
|
||||
...getRulesSchemaMock(),
|
||||
investigation_fields: undefined,
|
||||
};
|
||||
|
||||
delete payload.investigation_fields;
|
||||
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
const expected = getRulesSchemaMock();
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(expected);
|
||||
});
|
||||
|
||||
test('it should NOT validate an empty array for "investigation_fields.field_names"', () => {
|
||||
const payload: RuleResponse = {
|
||||
...getRulesSchemaMock(),
|
||||
investigation_fields: {
|
||||
field_names: [],
|
||||
},
|
||||
};
|
||||
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "[]" supplied to "investigation_fields,field_names"',
|
||||
'Invalid value "{"field_names":[]}" supplied to "investigation_fields"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate a string for "investigation_fields"', () => {
|
||||
const payload: Omit<RuleResponse, 'investigation_fields'> & {
|
||||
investigation_fields: string;
|
||||
} = {
|
||||
...getRulesSchemaMock(),
|
||||
investigation_fields: 'foo',
|
||||
};
|
||||
|
||||
const decoded = RuleResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "foo" supplied to "investigation_fields"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
const result = RuleResponse.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('investigation_fields', () => {
|
||||
test('it should validate rule with "investigation_fields"', () => {
|
||||
const payload = getRulesSchemaMock();
|
||||
payload.investigation_fields = { field_names: ['foo', 'bar'] };
|
||||
|
||||
const result = RuleResponse.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate undefined for "investigation_fields"', () => {
|
||||
const payload: RuleResponse = {
|
||||
...getRulesSchemaMock(),
|
||||
investigation_fields: undefined,
|
||||
};
|
||||
|
||||
const result = RuleResponse.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should NOT validate an empty array for "investigation_fields.field_names"', () => {
|
||||
const payload: RuleResponse = {
|
||||
...getRulesSchemaMock(),
|
||||
investigation_fields: {
|
||||
field_names: [],
|
||||
},
|
||||
};
|
||||
|
||||
const result = RuleResponse.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toEqual(
|
||||
'investigation_fields.field_names: Array must contain at least 1 element(s)'
|
||||
);
|
||||
});
|
||||
|
||||
test('it should NOT validate a string for "investigation_fields"', () => {
|
||||
const payload: Omit<RuleResponse, 'investigation_fields'> & {
|
||||
investigation_fields: string;
|
||||
} = {
|
||||
...getRulesSchemaMock(),
|
||||
investigation_fields: 'foo',
|
||||
};
|
||||
|
||||
const result = RuleResponse.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toEqual('Invalid input');
|
||||
});
|
||||
});
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 EventCategoryOverride = z.infer<typeof EventCategoryOverride>;
|
||||
export const EventCategoryOverride = z.string();
|
||||
|
||||
/**
|
||||
* Contains the event timestamp used for sorting a sequence of events
|
||||
*/
|
||||
export type TimestampField = z.infer<typeof TimestampField>;
|
||||
export const TimestampField = z.string();
|
||||
|
||||
/**
|
||||
* Sets a secondary field for sorting events
|
||||
*/
|
||||
export type TiebreakerField = z.infer<typeof TiebreakerField>;
|
||||
export const TiebreakerField = z.string();
|
|
@ -0,0 +1,16 @@
|
|||
openapi: 3.0.0
|
||||
info:
|
||||
title: EQL Rule Attributes
|
||||
version: 'not applicable'
|
||||
paths: {}
|
||||
components:
|
||||
x-codegen-enabled: true
|
||||
schemas:
|
||||
EventCategoryOverride:
|
||||
type: string
|
||||
TimestampField:
|
||||
type: string
|
||||
description: Contains the event timestamp used for sorting a sequence of events
|
||||
TiebreakerField:
|
||||
type: string
|
||||
description: Sets a secondary field for sorting events
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Anomaly threshold
|
||||
*/
|
||||
export type AnomalyThreshold = z.infer<typeof AnomalyThreshold>;
|
||||
export const AnomalyThreshold = z.number().int().min(0);
|
||||
|
||||
/**
|
||||
* Machine learning job ID
|
||||
*/
|
||||
export type MachineLearningJobId = z.infer<typeof MachineLearningJobId>;
|
||||
export const MachineLearningJobId = z.union([z.string(), z.array(z.string()).min(1)]);
|
|
@ -0,0 +1,20 @@
|
|||
openapi: 3.0.0
|
||||
info:
|
||||
title: ML Rule Attributes
|
||||
version: 'not applicable'
|
||||
paths: {}
|
||||
components:
|
||||
x-codegen-enabled: true
|
||||
schemas:
|
||||
AnomalyThreshold:
|
||||
type: integer
|
||||
minimum: 0
|
||||
description: Anomaly threshold
|
||||
MachineLearningJobId:
|
||||
oneOf:
|
||||
- type: string
|
||||
- type: array
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
description: Machine learning job ID
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { NonEmptyString } from '../common_attributes.gen';
|
||||
|
||||
export type NewTermsFields = z.infer<typeof NewTermsFields>;
|
||||
export const NewTermsFields = z.array(z.string()).min(1).max(3);
|
||||
|
||||
export type HistoryWindowStart = z.infer<typeof HistoryWindowStart>;
|
||||
export const HistoryWindowStart = NonEmptyString;
|
|
@ -0,0 +1,16 @@
|
|||
openapi: 3.0.0
|
||||
info:
|
||||
title: New Terms Attributes
|
||||
version: 'not applicable'
|
||||
paths: {}
|
||||
components:
|
||||
x-codegen-enabled: true
|
||||
schemas:
|
||||
NewTermsFields:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
maxItems: 3
|
||||
HistoryWindowStart:
|
||||
$ref: '../common_attributes.schema.yaml#/components/schemas/NonEmptyString'
|
|
@ -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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Describes how alerts will be generated for documents with missing suppress by fields:
|
||||
doNotSuppress - per each document a separate alert will be created
|
||||
suppress - only alert will be created per suppress by bucket
|
||||
*/
|
||||
export type AlertSuppressionMissingFieldsStrategy = z.infer<
|
||||
typeof AlertSuppressionMissingFieldsStrategy
|
||||
>;
|
||||
export const AlertSuppressionMissingFieldsStrategy = z.enum(['doNotSuppress', 'suppress']);
|
||||
export type AlertSuppressionMissingFieldsStrategyEnum =
|
||||
typeof AlertSuppressionMissingFieldsStrategy.enum;
|
||||
export const AlertSuppressionMissingFieldsStrategyEnum = AlertSuppressionMissingFieldsStrategy.enum;
|
||||
|
||||
export type AlertSuppressionGroupBy = z.infer<typeof AlertSuppressionGroupBy>;
|
||||
export const AlertSuppressionGroupBy = z.array(z.string()).min(1).max(3);
|
||||
|
||||
export type AlertSuppressionDuration = z.infer<typeof AlertSuppressionDuration>;
|
||||
export const AlertSuppressionDuration = z.object({
|
||||
value: z.number().int().min(1),
|
||||
unit: z.enum(['s', 'm', 'h']),
|
||||
});
|
||||
|
||||
export type AlertSuppression = z.infer<typeof AlertSuppression>;
|
||||
export const AlertSuppression = z.object({
|
||||
group_by: AlertSuppressionGroupBy,
|
||||
duration: AlertSuppressionDuration.optional(),
|
||||
missing_fields_strategy: AlertSuppressionMissingFieldsStrategy.optional(),
|
||||
});
|
||||
|
||||
export type AlertSuppressionCamel = z.infer<typeof AlertSuppressionCamel>;
|
||||
export const AlertSuppressionCamel = z.object({
|
||||
groupBy: AlertSuppressionGroupBy,
|
||||
duration: AlertSuppressionDuration.optional(),
|
||||
missingFieldsStrategy: AlertSuppressionMissingFieldsStrategy.optional(),
|
||||
});
|
|
@ -0,0 +1,64 @@
|
|||
openapi: 3.0.0
|
||||
info:
|
||||
title: Query Rule Attributes
|
||||
version: 'not applicable'
|
||||
paths: {}
|
||||
components:
|
||||
x-codegen-enabled: true
|
||||
schemas:
|
||||
AlertSuppressionMissingFieldsStrategy:
|
||||
type: string
|
||||
enum:
|
||||
- doNotSuppress
|
||||
- suppress
|
||||
description: |-
|
||||
Describes how alerts will be generated for documents with missing suppress by fields:
|
||||
doNotSuppress - per each document a separate alert will be created
|
||||
suppress - only alert will be created per suppress by bucket
|
||||
|
||||
AlertSuppressionGroupBy:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
maxItems: 3
|
||||
|
||||
AlertSuppressionDuration:
|
||||
type: object
|
||||
properties:
|
||||
value:
|
||||
type: integer
|
||||
minimum: 1
|
||||
unit:
|
||||
type: string
|
||||
enum:
|
||||
- s
|
||||
- m
|
||||
- h
|
||||
required:
|
||||
- value
|
||||
- unit
|
||||
|
||||
AlertSuppression:
|
||||
type: object
|
||||
properties:
|
||||
group_by:
|
||||
$ref: '#/components/schemas/AlertSuppressionGroupBy'
|
||||
duration:
|
||||
$ref: '#/components/schemas/AlertSuppressionDuration'
|
||||
missing_fields_strategy:
|
||||
$ref: '#/components/schemas/AlertSuppressionMissingFieldsStrategy'
|
||||
required:
|
||||
- group_by
|
||||
|
||||
AlertSuppressionCamel:
|
||||
type: object
|
||||
properties:
|
||||
groupBy:
|
||||
$ref: '#/components/schemas/AlertSuppressionGroupBy'
|
||||
duration:
|
||||
$ref: '#/components/schemas/AlertSuppressionDuration'
|
||||
missingFieldsStrategy:
|
||||
$ref: '#/components/schemas/AlertSuppressionMissingFieldsStrategy'
|
||||
required:
|
||||
- groupBy
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 { NonEmptyString } from '../common_attributes.gen';
|
||||
|
||||
/**
|
||||
* Query to execute
|
||||
*/
|
||||
export type ThreatQuery = z.infer<typeof ThreatQuery>;
|
||||
export const ThreatQuery = z.string();
|
||||
|
||||
export type ThreatMapping = z.infer<typeof ThreatMapping>;
|
||||
export const ThreatMapping = z
|
||||
.array(
|
||||
z.object({
|
||||
entries: z.array(
|
||||
z.object({
|
||||
field: NonEmptyString,
|
||||
type: z.literal('mapping'),
|
||||
value: NonEmptyString,
|
||||
})
|
||||
),
|
||||
})
|
||||
)
|
||||
.min(1);
|
||||
|
||||
export type ThreatIndex = z.infer<typeof ThreatIndex>;
|
||||
export const ThreatIndex = z.array(z.string());
|
||||
|
||||
export type ThreatFilters = z.infer<typeof ThreatFilters>;
|
||||
export const ThreatFilters = z.array(z.unknown());
|
||||
|
||||
/**
|
||||
* Defines the path to the threat indicator in the indicator documents (optional)
|
||||
*/
|
||||
export type ThreatIndicatorPath = z.infer<typeof ThreatIndicatorPath>;
|
||||
export const ThreatIndicatorPath = z.string();
|
||||
|
||||
export type ConcurrentSearches = z.infer<typeof ConcurrentSearches>;
|
||||
export const ConcurrentSearches = z.number().int().min(1);
|
||||
|
||||
export type ItemsPerSearch = z.infer<typeof ItemsPerSearch>;
|
||||
export const ItemsPerSearch = z.number().int().min(1);
|
|
@ -0,0 +1,59 @@
|
|||
openapi: 3.0.0
|
||||
info:
|
||||
title: Threat Match Rule Attributes
|
||||
version: 'not applicable'
|
||||
paths: {}
|
||||
components:
|
||||
x-codegen-enabled: true
|
||||
schemas:
|
||||
ThreatQuery:
|
||||
type: string
|
||||
description: Query to execute
|
||||
|
||||
ThreatMapping:
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
entries:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
field:
|
||||
$ref: '../common_attributes.schema.yaml#/components/schemas/NonEmptyString'
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- mapping
|
||||
value:
|
||||
$ref: '../common_attributes.schema.yaml#/components/schemas/NonEmptyString'
|
||||
required:
|
||||
- field
|
||||
- type
|
||||
- value
|
||||
required:
|
||||
- entries
|
||||
|
||||
ThreatIndex:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
ThreatFilters:
|
||||
type: array
|
||||
items:
|
||||
description: Query and filter context array used to filter documents from the Elasticsearch index containing the threat values
|
||||
|
||||
ThreatIndicatorPath:
|
||||
type: string
|
||||
description: Defines the path to the threat indicator in the indicator documents (optional)
|
||||
|
||||
ConcurrentSearches:
|
||||
type: integer
|
||||
minimum: 1
|
||||
|
||||
ItemsPerSearch:
|
||||
type: integer
|
||||
minimum: 1
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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 ThresholdCardinality = z.infer<typeof ThresholdCardinality>;
|
||||
export const ThresholdCardinality = z.array(
|
||||
z.object({
|
||||
field: z.string(),
|
||||
value: z.number().int().min(0),
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* Threshold value
|
||||
*/
|
||||
export type ThresholdValue = z.infer<typeof ThresholdValue>;
|
||||
export const ThresholdValue = z.number().int().min(1);
|
||||
|
||||
/**
|
||||
* Field to aggregate on
|
||||
*/
|
||||
export type ThresholdField = z.infer<typeof ThresholdField>;
|
||||
export const ThresholdField = z.union([z.string(), z.array(z.string())]);
|
||||
|
||||
/**
|
||||
* Field to aggregate on
|
||||
*/
|
||||
export type ThresholdFieldNormalized = z.infer<typeof ThresholdFieldNormalized>;
|
||||
export const ThresholdFieldNormalized = z.array(z.string());
|
||||
|
||||
export type Threshold = z.infer<typeof Threshold>;
|
||||
export const Threshold = z.object({
|
||||
field: ThresholdField,
|
||||
value: ThresholdValue,
|
||||
cardinality: ThresholdCardinality.optional(),
|
||||
});
|
||||
|
||||
export type ThresholdNormalized = z.infer<typeof ThresholdNormalized>;
|
||||
export const ThresholdNormalized = z.object({
|
||||
field: ThresholdFieldNormalized,
|
||||
value: ThresholdValue,
|
||||
cardinality: ThresholdCardinality.optional(),
|
||||
});
|
||||
|
||||
export type ThresholdWithCardinality = z.infer<typeof ThresholdWithCardinality>;
|
||||
export const ThresholdWithCardinality = z.object({
|
||||
field: ThresholdFieldNormalized,
|
||||
value: ThresholdValue,
|
||||
cardinality: ThresholdCardinality,
|
||||
});
|
|
@ -0,0 +1,80 @@
|
|||
openapi: 3.0.0
|
||||
info:
|
||||
title: Threshold Rule Attributes
|
||||
version: 'not applicable'
|
||||
paths: {}
|
||||
components:
|
||||
x-codegen-enabled: true
|
||||
schemas:
|
||||
ThresholdCardinality:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
field:
|
||||
type: string
|
||||
value:
|
||||
type: integer
|
||||
minimum: 0
|
||||
required:
|
||||
- field
|
||||
- value
|
||||
|
||||
ThresholdValue:
|
||||
type: integer
|
||||
minimum: 1
|
||||
description: Threshold value
|
||||
|
||||
ThresholdField:
|
||||
oneOf:
|
||||
- type: string
|
||||
- type: array
|
||||
items:
|
||||
type: string
|
||||
description: Field to aggregate on
|
||||
|
||||
ThresholdFieldNormalized:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Field to aggregate on
|
||||
|
||||
Threshold:
|
||||
type: object
|
||||
properties:
|
||||
field:
|
||||
$ref: '#/components/schemas/ThresholdField'
|
||||
value:
|
||||
$ref: '#/components/schemas/ThresholdValue'
|
||||
cardinality:
|
||||
$ref: '#/components/schemas/ThresholdCardinality'
|
||||
required:
|
||||
- field
|
||||
- value
|
||||
|
||||
ThresholdNormalized:
|
||||
type: object
|
||||
properties:
|
||||
field:
|
||||
$ref: '#/components/schemas/ThresholdFieldNormalized'
|
||||
value:
|
||||
$ref: '#/components/schemas/ThresholdValue'
|
||||
cardinality:
|
||||
$ref: '#/components/schemas/ThresholdCardinality'
|
||||
required:
|
||||
- field
|
||||
- value
|
||||
|
||||
ThresholdWithCardinality:
|
||||
type: object
|
||||
properties:
|
||||
field:
|
||||
$ref: '#/components/schemas/ThresholdFieldNormalized'
|
||||
value:
|
||||
$ref: '#/components/schemas/ThresholdValue'
|
||||
cardinality:
|
||||
$ref: '#/components/schemas/ThresholdCardinality'
|
||||
required:
|
||||
- field
|
||||
- value
|
||||
- cardinality
|
|
@ -44,7 +44,7 @@ export const RuleTagArray = t.array(t.string); // should be non-empty strings?
|
|||
* to be added to the meta object
|
||||
*/
|
||||
export type RuleMetadata = t.TypeOf<typeof RuleMetadata>;
|
||||
export const RuleMetadata = t.object; // should be a more specific type?
|
||||
export const RuleMetadata = t.UnknownRecord; // should be a more specific type?
|
||||
|
||||
export type RuleLicense = t.TypeOf<typeof RuleLicense>;
|
||||
export const RuleLicense = t.string;
|
|
@ -5,9 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
export * from './common_attributes';
|
||||
|
||||
import { RuleResponse, ErrorSchema } from '../../model';
|
||||
export * from './eql_attributes';
|
||||
export * from './new_terms_attributes';
|
||||
export * from './query_attributes';
|
||||
export * from './threshold_attributes';
|
||||
|
||||
export type BulkCrudRulesResponse = t.TypeOf<typeof BulkCrudRulesResponse>;
|
||||
export const BulkCrudRulesResponse = t.array(t.union([RuleResponse, ErrorSchema]));
|
||||
export * from './rule_schemas';
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import * as t from 'io-ts';
|
||||
import { LimitedSizeArray, NonEmptyString } from '@kbn/securitysolution-io-ts-types';
|
||||
import { MAX_NUMBER_OF_NEW_TERMS_FIELDS } from '../../../../../constants';
|
||||
import { MAX_NUMBER_OF_NEW_TERMS_FIELDS } from '../../../../constants';
|
||||
|
||||
// Attributes specific to New Terms rules
|
||||
|
|
@ -11,24 +11,13 @@ import {
|
|||
PositiveIntegerGreaterThanZero,
|
||||
enumeration,
|
||||
} from '@kbn/securitysolution-io-ts-types';
|
||||
|
||||
/**
|
||||
* describes how alerts will be generated for documents with missing suppress by fields
|
||||
*/
|
||||
export enum AlertSuppressionMissingFieldsStrategy {
|
||||
// per each document a separate alert will be created
|
||||
DoNotSuppress = 'doNotSuppress',
|
||||
// only alert will be created per suppress by bucket
|
||||
Suppress = 'suppress',
|
||||
}
|
||||
import { AlertSuppressionMissingFieldsStrategyEnum } from '../rule_schema/specific_attributes/query_attributes.gen';
|
||||
|
||||
export type AlertSuppressionMissingFields = t.TypeOf<typeof AlertSuppressionMissingFields>;
|
||||
export const AlertSuppressionMissingFields = enumeration(
|
||||
'AlertSuppressionMissingFields',
|
||||
AlertSuppressionMissingFieldsStrategy
|
||||
AlertSuppressionMissingFieldsStrategyEnum
|
||||
);
|
||||
export const DEFAULT_SUPPRESSION_MISSING_FIELDS_STRATEGY =
|
||||
AlertSuppressionMissingFieldsStrategy.Suppress;
|
||||
|
||||
export const AlertSuppressionGroupBy = LimitedSizeArray({
|
||||
codec: t.string,
|
||||
|
@ -79,5 +68,3 @@ export const AlertSuppressionCamel = t.intersection([
|
|||
})
|
||||
),
|
||||
]);
|
||||
|
||||
export const minimumLicenseForSuppression = 'platinum';
|
|
@ -28,15 +28,17 @@ import {
|
|||
} from '@kbn/securitysolution-io-ts-alerting-types';
|
||||
|
||||
import { RuleExecutionSummary } from '../../rule_monitoring/model';
|
||||
import { ResponseActionArray } from '../rule_response_actions';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { ResponseActionArray } from '../rule_response_actions/response_actions_legacy';
|
||||
|
||||
import {
|
||||
saved_id,
|
||||
anomaly_threshold,
|
||||
updated_at,
|
||||
updated_by,
|
||||
created_at,
|
||||
created_by,
|
||||
revision,
|
||||
saved_id,
|
||||
updated_at,
|
||||
updated_by,
|
||||
} from '../schemas';
|
||||
|
||||
import {
|
||||
|
@ -46,6 +48,7 @@ import {
|
|||
DataViewId,
|
||||
ExceptionListArray,
|
||||
IndexPatternArray,
|
||||
InvestigationFields,
|
||||
InvestigationGuide,
|
||||
IsRuleEnabled,
|
||||
IsRuleImmutable,
|
||||
|
@ -53,7 +56,6 @@ import {
|
|||
RelatedIntegrationArray,
|
||||
RequiredFieldArray,
|
||||
RuleAuthorArray,
|
||||
InvestigationFields,
|
||||
RuleDescription,
|
||||
RuleFalsePositiveArray,
|
||||
RuleFilterArray,
|
||||
|
@ -77,16 +79,53 @@ import {
|
|||
TimestampOverride,
|
||||
TimestampOverrideFallbackDisabled,
|
||||
} from './common_attributes';
|
||||
import {
|
||||
EventCategoryOverride,
|
||||
TiebreakerField,
|
||||
TimestampField,
|
||||
} from './specific_attributes/eql_attributes';
|
||||
import { Threshold } from './specific_attributes/threshold_attributes';
|
||||
import { HistoryWindowStart, NewTermsFields } from './specific_attributes/new_terms_attributes';
|
||||
import { AlertSuppression } from './specific_attributes/query_attributes';
|
||||
import { EventCategoryOverride, TiebreakerField, TimestampField } from './eql_attributes';
|
||||
import { HistoryWindowStart, NewTermsFields } from './new_terms_attributes';
|
||||
import { AlertSuppression } from './query_attributes';
|
||||
import { Threshold } from './threshold_attributes';
|
||||
|
||||
import { buildRuleSchemas } from './build_rule_schemas';
|
||||
export const buildRuleSchemas = <
|
||||
Required extends t.Props,
|
||||
Optional extends t.Props,
|
||||
Defaultable extends t.Props
|
||||
>({
|
||||
required,
|
||||
optional,
|
||||
defaultable,
|
||||
}: {
|
||||
required: Required;
|
||||
optional: Optional;
|
||||
defaultable: Defaultable;
|
||||
}) => ({
|
||||
create: t.intersection([
|
||||
t.exact(t.type(required)),
|
||||
t.exact(t.partial(optional)),
|
||||
t.exact(t.partial(defaultable)),
|
||||
]),
|
||||
patch: t.intersection([t.partial(required), t.partial(optional), t.partial(defaultable)]),
|
||||
response: t.intersection([
|
||||
t.exact(t.type(required)),
|
||||
// This bit of logic is to force all fields to be accounted for in conversions from the internal
|
||||
// rule schema to the response schema. Rather than use `t.partial`, which makes each field optional,
|
||||
// we make each field required but possibly undefined. The result is that if a field is forgotten in
|
||||
// the conversion from internal schema to response schema TS will report an error. If we just used t.partial
|
||||
// instead, then optional fields can be accidentally omitted from the conversion - and any actual values
|
||||
// in those fields internally will be stripped in the response.
|
||||
t.exact(t.type(orUndefined(optional))),
|
||||
t.exact(t.type(defaultable)),
|
||||
]),
|
||||
});
|
||||
|
||||
export type OrUndefined<P extends t.Props> = {
|
||||
[K in keyof P]: P[K] | t.UndefinedC;
|
||||
};
|
||||
|
||||
export const orUndefined = <P extends t.Props>(props: P): OrUndefined<P> => {
|
||||
return Object.keys(props).reduce<t.Props>((acc, key) => {
|
||||
acc[key] = t.union([props[key], t.undefined]);
|
||||
return acc;
|
||||
}, {}) as OrUndefined<P>;
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// Base schema
|
||||
|
@ -106,7 +145,7 @@ export const baseSchema = buildRuleSchemas({
|
|||
// Timeline template
|
||||
timeline_id: TimelineTemplateId,
|
||||
timeline_title: TimelineTemplateTitle,
|
||||
// Atributes related to SavedObjectsClient.resolve API
|
||||
// Attributes related to SavedObjectsClient.resolve API
|
||||
outcome: SavedObjectResolveOutcome,
|
||||
alias_target_id: SavedObjectResolveAliasTargetId,
|
||||
alias_purpose: SavedObjectResolveAliasPurpose,
|
||||
|
@ -578,4 +617,4 @@ export type RulePatchProps = t.TypeOf<typeof RulePatchProps>;
|
|||
export const RulePatchProps = t.intersection([TypeSpecificPatchProps, SharedPatchProps]);
|
||||
|
||||
export type RuleResponse = t.TypeOf<typeof RuleResponse>;
|
||||
export const RuleResponse = t.intersection([SharedResponseProps, TypeSpecificResponse]);
|
||||
export const RuleResponse = t.intersection([TypeSpecificResponse, SharedResponseProps]);
|
|
@ -89,10 +89,6 @@ export const indexRecord = t.record(
|
|||
})
|
||||
);
|
||||
|
||||
export const indexType = t.type({
|
||||
index: indexRecord,
|
||||
});
|
||||
|
||||
export const privilege = t.type({
|
||||
username: t.string,
|
||||
has_all_requested: t.boolean,
|
||||
|
|
|
@ -14,5 +14,5 @@ import { z } from 'zod';
|
|||
|
||||
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;
|
||||
export const SortOrderEnum = SortOrder.enum;
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils';
|
||||
import { DefaultSortOrderAsc, DefaultSortOrderDesc } from './sorting';
|
||||
// TODO https://github.com/elastic/security-team/issues/7491
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { DefaultSortOrderAsc, DefaultSortOrderDesc } from './sorting_legacy';
|
||||
|
||||
describe('Common sorting schemas', () => {
|
||||
describe('DefaultSortOrderAsc', () => {
|
||||
|
|
|
@ -1,24 +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';
|
||||
|
||||
const partial = t.exact(
|
||||
t.partial({
|
||||
buttonLabel: t.string,
|
||||
})
|
||||
);
|
||||
const required = t.exact(
|
||||
t.type({
|
||||
type: t.string,
|
||||
message: t.string,
|
||||
actionPath: t.string,
|
||||
})
|
||||
);
|
||||
|
||||
export const WarningSchema = t.intersection([partial, required]);
|
||||
export type WarningSchema = t.TypeOf<typeof WarningSchema>;
|
|
@ -5,8 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { stringifyZodError } from '@kbn/securitysolution-es-utils';
|
||||
import { expectParseError, expectParseSuccess } from '../../../../test/zod_helpers';
|
||||
import { expectParseError, expectParseSuccess, stringifyZodError } from '@kbn/zod-helpers';
|
||||
import { GetPrebuiltRulesAndTimelinesStatusResponse } from './get_prebuilt_rules_and_timelines_status_route.gen';
|
||||
|
||||
describe('Get prebuilt rules and timelines status response schema', () => {
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { stringifyZodError } from '@kbn/securitysolution-es-utils';
|
||||
import { expectParseError, expectParseSuccess } from '../../../../test/zod_helpers';
|
||||
import { expectParseError, expectParseSuccess, stringifyZodError } from '@kbn/zod-helpers';
|
||||
import { InstallPrebuiltRulesAndTimelinesResponse } from './install_prebuilt_rules_and_timelines_route.gen';
|
||||
|
||||
describe('Install prebuilt rules and timelines response schema', () => {
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
import { orUndefined } from '../../../../model';
|
||||
// TODO https://github.com/elastic/security-team/issues/7491
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { orUndefined } from '../../../../model/rule_schema_legacy';
|
||||
|
||||
interface RuleFields<TRequired extends t.Props, TOptional extends t.Props> {
|
||||
required: TRequired;
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import * as t from 'io-ts';
|
||||
import { TimeDuration } from '@kbn/securitysolution-io-ts-types';
|
||||
// TODO https://github.com/elastic/security-team/issues/7491
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import {
|
||||
BuildingBlockType,
|
||||
DataViewId,
|
||||
|
@ -19,8 +21,8 @@ import {
|
|||
TimelineTemplateTitle,
|
||||
TimestampOverride as TimestampOverrideFieldName,
|
||||
TimestampOverrideFallbackDisabled,
|
||||
saved_id,
|
||||
} from '../../../../model';
|
||||
} from '../../../../model/rule_schema_legacy';
|
||||
import { saved_id } from '../../../../model/schemas';
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// Rule data source
|
||||
|
|
|
@ -22,6 +22,8 @@ import {
|
|||
threat_mapping,
|
||||
} from '@kbn/securitysolution-io-ts-alerting-types';
|
||||
|
||||
// TODO https://github.com/elastic/security-team/issues/7491
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import {
|
||||
AlertSuppression,
|
||||
EventCategoryOverride,
|
||||
|
@ -47,8 +49,7 @@ import {
|
|||
Threshold,
|
||||
TiebreakerField,
|
||||
TimestampField,
|
||||
anomaly_threshold,
|
||||
} from '../../../../model';
|
||||
} from '../../../../model/rule_schema_legacy';
|
||||
|
||||
import {
|
||||
BuildingBlockObject,
|
||||
|
@ -64,6 +65,7 @@ import {
|
|||
} from './diffable_field_types';
|
||||
|
||||
import { buildSchema } from './build_schema';
|
||||
import { anomaly_threshold } from '../../../../model/schemas';
|
||||
|
||||
export type DiffableCommonFields = t.TypeOf<typeof DiffableCommonFields>;
|
||||
export const DiffableCommonFields = buildSchema({
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { RuleTagArray } from '../../model';
|
||||
import type { RuleResponse } from '../../model/rule_schema/rule_schemas';
|
||||
import type { RuleResponse } from '../../model/rule_schema';
|
||||
|
||||
export interface ReviewRuleInstallationResponseBody {
|
||||
/** Aggregated info about all rules available for installation */
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import type { RuleObjectId, RuleSignatureId, RuleTagArray } from '../../model';
|
||||
import type { PartialRuleDiff } from '../model';
|
||||
import type { RuleResponse } from '../../model/rule_schema/rule_schemas';
|
||||
import type { RuleResponse } from '../../model/rule_schema';
|
||||
|
||||
export interface ReviewRuleUpgradeResponseBody {
|
||||
/** Aggregated info about all rules available for upgrade */
|
||||
|
|
|
@ -12,8 +12,9 @@ import type {
|
|||
ExceptionListItemSchema,
|
||||
} from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { createRuleExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
|
||||
|
||||
import { RuleObjectId } from '../../model';
|
||||
// TODO https://github.com/elastic/security-team/issues/7491
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { RuleObjectId } from '../../model/rule_schema_legacy';
|
||||
|
||||
/**
|
||||
* URL path parameters of the API route.
|
||||
|
|
|
@ -13,7 +13,9 @@ import {
|
|||
DefaultNamespaceArray,
|
||||
} from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { NonEmptyStringArray } from '@kbn/securitysolution-io-ts-types';
|
||||
import { RuleName, RuleObjectId, RuleSignatureId } from '../../model';
|
||||
// TODO https://github.com/elastic/security-team/issues/7491
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { RuleName, RuleObjectId, RuleSignatureId } from '../../model/rule_schema_legacy';
|
||||
|
||||
// If ids and list_ids are undefined, route will fetch all lists matching the
|
||||
// specified namespace type
|
||||
|
|
|
@ -20,13 +20,15 @@ import type { BulkActionSkipResult } from '@kbn/alerting-plugin/common';
|
|||
import type { RuleResponse } from '../../model';
|
||||
import type { BulkActionsDryRunErrCode } from '../../../../constants';
|
||||
|
||||
// TODO https://github.com/elastic/security-team/issues/7491
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import {
|
||||
IndexPatternArray,
|
||||
RuleQuery,
|
||||
RuleTagArray,
|
||||
TimelineTemplateId,
|
||||
TimelineTemplateTitle,
|
||||
} from '../../model';
|
||||
} from '../../model/rule_schema_legacy';
|
||||
|
||||
export enum BulkActionType {
|
||||
'enable' = 'enable',
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 { RuleCreateProps } from '../../../model/rule_schema/rule_schemas.gen';
|
||||
import { BulkCrudRulesResponse } from '../response_schema.gen';
|
||||
|
||||
export type BulkCreateRulesRequestBody = z.infer<typeof BulkCreateRulesRequestBody>;
|
||||
export const BulkCreateRulesRequestBody = z.array(RuleCreateProps);
|
||||
export type BulkCreateRulesRequestBodyInput = z.input<typeof BulkCreateRulesRequestBody>;
|
||||
|
||||
export type BulkCreateRulesResponse = z.infer<typeof BulkCreateRulesResponse>;
|
||||
export const BulkCreateRulesResponse = BulkCrudRulesResponse;
|
|
@ -5,8 +5,8 @@ info:
|
|||
paths:
|
||||
/api/detection_engine/rules/_bulk_create:
|
||||
post:
|
||||
operationId: CreateRulesBulk
|
||||
x-codegen-enabled: false
|
||||
operationId: BulkCreateRules
|
||||
x-codegen-enabled: true
|
||||
deprecated: true
|
||||
description: Creates new detection rules in bulk.
|
||||
tags:
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { BulkCreateRulesRequestBody } from './bulk_create_rules_route';
|
||||
import { exactCheck, foldLeftRight, formatErrors } from '@kbn/securitysolution-io-ts-utils';
|
||||
import { expectParseError, expectParseSuccess, stringifyZodError } from '@kbn/zod-helpers';
|
||||
import { getCreateRulesSchemaMock } from '../../../model/rule_schema/mocks';
|
||||
import { BulkCreateRulesRequestBody } from './bulk_create_rules_route.gen';
|
||||
|
||||
// only the basics of testing are here.
|
||||
// see: rule_schemas.test.ts for the bulk of the validation tests
|
||||
|
@ -16,40 +16,25 @@ describe('Bulk create rules request schema', () => {
|
|||
test('can take an empty array and validate it', () => {
|
||||
const payload: BulkCreateRulesRequestBody = [];
|
||||
|
||||
const decoded = BulkCreateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(output.errors).toEqual([]);
|
||||
expect(output.schema).toEqual([]);
|
||||
const result = BulkCreateRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('made up values do not validate for a single element', () => {
|
||||
const payload: Array<{ madeUp: string }> = [{ madeUp: 'hi' }];
|
||||
|
||||
const decoded = BulkCreateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toContain(
|
||||
'Invalid value "undefined" supplied to "description"'
|
||||
);
|
||||
expect(formatErrors(output.errors)).toContain(
|
||||
'Invalid value "undefined" supplied to "risk_score"'
|
||||
);
|
||||
expect(formatErrors(output.errors)).toContain('Invalid value "undefined" supplied to "name"');
|
||||
expect(formatErrors(output.errors)).toContain(
|
||||
'Invalid value "undefined" supplied to "severity"'
|
||||
);
|
||||
expect(output.schema).toEqual({});
|
||||
const result = BulkCreateRulesRequestBody.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`);
|
||||
});
|
||||
|
||||
test('single array element does validate', () => {
|
||||
const payload: BulkCreateRulesRequestBody = [getCreateRulesSchemaMock()];
|
||||
|
||||
const decoded = BulkCreateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([]);
|
||||
expect(output.schema).toEqual(payload);
|
||||
const result = BulkCreateRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('two array elements do validate', () => {
|
||||
|
@ -58,11 +43,9 @@ describe('Bulk create rules request schema', () => {
|
|||
getCreateRulesSchemaMock(),
|
||||
];
|
||||
|
||||
const decoded = BulkCreateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([]);
|
||||
expect(output.schema).toEqual(payload);
|
||||
const result = BulkCreateRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('single array element with a missing value (risk_score) will not validate', () => {
|
||||
|
@ -71,13 +54,9 @@ describe('Bulk create rules request schema', () => {
|
|||
delete singleItem.risk_score;
|
||||
const payload: BulkCreateRulesRequestBody = [singleItem];
|
||||
|
||||
const decoded = BulkCreateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([
|
||||
'Invalid value "undefined" supplied to "risk_score"',
|
||||
]);
|
||||
expect(output.schema).toEqual({});
|
||||
const result = BulkCreateRulesRequestBody.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`);
|
||||
});
|
||||
|
||||
test('two array elements where the first is valid but the second is invalid (risk_score) will not validate', () => {
|
||||
|
@ -87,13 +66,9 @@ describe('Bulk create rules request schema', () => {
|
|||
delete secondItem.risk_score;
|
||||
const payload: BulkCreateRulesRequestBody = [singleItem, secondItem];
|
||||
|
||||
const decoded = BulkCreateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([
|
||||
'Invalid value "undefined" supplied to "risk_score"',
|
||||
]);
|
||||
expect(output.schema).toEqual({});
|
||||
const result = BulkCreateRulesRequestBody.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"1: Invalid input"`);
|
||||
});
|
||||
|
||||
test('two array elements where the first is invalid (risk_score) but the second is valid will not validate', () => {
|
||||
|
@ -103,13 +78,9 @@ describe('Bulk create rules request schema', () => {
|
|||
delete singleItem.risk_score;
|
||||
const payload: BulkCreateRulesRequestBody = [singleItem, secondItem];
|
||||
|
||||
const decoded = BulkCreateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([
|
||||
'Invalid value "undefined" supplied to "risk_score"',
|
||||
]);
|
||||
expect(output.schema).toEqual({});
|
||||
const result = BulkCreateRulesRequestBody.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`);
|
||||
});
|
||||
|
||||
test('two array elements where both are invalid (risk_score) will not validate', () => {
|
||||
|
@ -121,46 +92,14 @@ describe('Bulk create rules request schema', () => {
|
|||
delete secondItem.risk_score;
|
||||
const payload: BulkCreateRulesRequestBody = [singleItem, secondItem];
|
||||
|
||||
const decoded = BulkCreateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([
|
||||
'Invalid value "undefined" supplied to "risk_score"',
|
||||
]);
|
||||
expect(output.schema).toEqual({});
|
||||
const result = BulkCreateRulesRequestBody.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
|
||||
`"0: Invalid input, 1: Invalid input"`
|
||||
);
|
||||
});
|
||||
|
||||
test('two array elements where the first is invalid (extra key and value) but the second is valid will not validate', () => {
|
||||
const singleItem = {
|
||||
...getCreateRulesSchemaMock(),
|
||||
madeUpValue: 'something',
|
||||
};
|
||||
const secondItem = getCreateRulesSchemaMock();
|
||||
const payload: BulkCreateRulesRequestBody = [singleItem, secondItem];
|
||||
|
||||
const decoded = BulkCreateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual(['invalid keys "madeUpValue"']);
|
||||
expect(output.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('two array elements where the second is invalid (extra key and value) but the first is valid will not validate', () => {
|
||||
const singleItem = getCreateRulesSchemaMock();
|
||||
const secondItem = {
|
||||
...getCreateRulesSchemaMock(),
|
||||
madeUpValue: 'something',
|
||||
};
|
||||
const payload: BulkCreateRulesRequestBody = [singleItem, secondItem];
|
||||
|
||||
const decoded = BulkCreateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual(['invalid keys "madeUpValue"']);
|
||||
expect(output.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('two array elements where both are invalid (extra key and value) will not validate', () => {
|
||||
test('extra keys are omitted from the payload', () => {
|
||||
const singleItem = {
|
||||
...getCreateRulesSchemaMock(),
|
||||
madeUpValue: 'something',
|
||||
|
@ -171,22 +110,18 @@ describe('Bulk create rules request schema', () => {
|
|||
};
|
||||
const payload: BulkCreateRulesRequestBody = [singleItem, secondItem];
|
||||
|
||||
const decoded = BulkCreateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual(['invalid keys "madeUpValue,madeUpValue"']);
|
||||
expect(output.schema).toEqual({});
|
||||
const result = BulkCreateRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual([getCreateRulesSchemaMock(), getCreateRulesSchemaMock()]);
|
||||
});
|
||||
|
||||
test('You cannot set the severity to a value other than low, medium, high, or critical', () => {
|
||||
const badSeverity = { ...getCreateRulesSchemaMock(), severity: 'madeup' };
|
||||
const payload = [badSeverity];
|
||||
|
||||
const decoded = BulkCreateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual(['Invalid value "madeup" supplied to "severity"']);
|
||||
expect(output.schema).toEqual({});
|
||||
const result = BulkCreateRulesRequestBody.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`);
|
||||
});
|
||||
|
||||
test('You can set "note" to a string', () => {
|
||||
|
@ -194,21 +129,17 @@ describe('Bulk create rules request schema', () => {
|
|||
{ ...getCreateRulesSchemaMock(), note: '# test markdown' },
|
||||
];
|
||||
|
||||
const decoded = BulkCreateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([]);
|
||||
expect(output.schema).toEqual(payload);
|
||||
const result = BulkCreateRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('You can set "note" to an empty string', () => {
|
||||
const payload: BulkCreateRulesRequestBody = [{ ...getCreateRulesSchemaMock(), note: '' }];
|
||||
|
||||
const decoded = BulkCreateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([]);
|
||||
expect(output.schema).toEqual(payload);
|
||||
const result = BulkCreateRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('You cant set "note" to anything other than string', () => {
|
||||
|
@ -221,12 +152,8 @@ describe('Bulk create rules request schema', () => {
|
|||
},
|
||||
];
|
||||
|
||||
const decoded = BulkCreateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([
|
||||
'Invalid value "{"something":"some object"}" supplied to "note"',
|
||||
]);
|
||||
expect(output.schema).toEqual({});
|
||||
const result = BulkCreateRulesRequestBody.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,15 +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 { RuleCreateProps } from '../../../model';
|
||||
|
||||
/**
|
||||
* Request body parameters of the API route.
|
||||
*/
|
||||
export type BulkCreateRulesRequestBody = t.TypeOf<typeof BulkCreateRulesRequestBody>;
|
||||
export const BulkCreateRulesRequestBody = t.array(RuleCreateProps);
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 { RuleObjectId, RuleSignatureId } from '../../../model/rule_schema/common_attributes.gen';
|
||||
import { BulkCrudRulesResponse } from '../response_schema.gen';
|
||||
|
||||
export type BulkDeleteRulesRequestBody = z.infer<typeof BulkDeleteRulesRequestBody>;
|
||||
export const BulkDeleteRulesRequestBody = z.array(
|
||||
z.object({
|
||||
id: RuleObjectId.optional(),
|
||||
rule_id: RuleSignatureId.optional(),
|
||||
})
|
||||
);
|
||||
export type BulkDeleteRulesRequestBodyInput = z.input<typeof BulkDeleteRulesRequestBody>;
|
||||
|
||||
export type BulkDeleteRulesResponse = z.infer<typeof BulkDeleteRulesResponse>;
|
||||
export const BulkDeleteRulesResponse = BulkCrudRulesResponse;
|
|
@ -5,8 +5,8 @@ info:
|
|||
paths:
|
||||
/api/detection_engine/rules/_bulk_delete:
|
||||
delete:
|
||||
operationId: DeleteRulesBulk
|
||||
x-codegen-enabled: false
|
||||
operationId: BulkDeleteRules
|
||||
x-codegen-enabled: true
|
||||
deprecated: true
|
||||
description: Deletes multiple rules.
|
||||
tags:
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { exactCheck, formatErrors, foldLeftRight } from '@kbn/securitysolution-io-ts-utils';
|
||||
import { BulkDeleteRulesRequestBody } from './bulk_delete_rules_route';
|
||||
import { expectParseError, expectParseSuccess, stringifyZodError } from '@kbn/zod-helpers';
|
||||
import { BulkDeleteRulesRequestBody } from './bulk_delete_rules_route.gen';
|
||||
|
||||
// only the basics of testing are here.
|
||||
// see: query_rules_schema.test.ts for the bulk of the validation tests
|
||||
|
@ -15,11 +15,9 @@ describe('Bulk delete rules request schema', () => {
|
|||
test('can take an empty array and validate it', () => {
|
||||
const payload: BulkDeleteRulesRequestBody = [];
|
||||
|
||||
const decoded = BulkDeleteRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([]);
|
||||
expect(output.schema).toEqual([]);
|
||||
const result = BulkDeleteRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('non uuid being supplied to id does not validate', () => {
|
||||
|
@ -29,11 +27,9 @@ describe('Bulk delete rules request schema', () => {
|
|||
},
|
||||
];
|
||||
|
||||
const decoded = BulkDeleteRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual(['Invalid value "1" supplied to "id"']);
|
||||
expect(output.schema).toEqual({});
|
||||
const result = BulkDeleteRulesRequestBody.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0.id: Invalid uuid"`);
|
||||
});
|
||||
|
||||
test('both rule_id and id being supplied do validate', () => {
|
||||
|
@ -44,11 +40,9 @@ describe('Bulk delete rules request schema', () => {
|
|||
},
|
||||
];
|
||||
|
||||
const decoded = BulkDeleteRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([]);
|
||||
expect(output.schema).toEqual(payload);
|
||||
const result = BulkDeleteRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('only id validates with two elements', () => {
|
||||
|
@ -57,11 +51,9 @@ describe('Bulk delete rules request schema', () => {
|
|||
{ id: 'c1e1b359-7ac1-4e96-bc81-c683c092436f' },
|
||||
];
|
||||
|
||||
const decoded = BulkDeleteRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([]);
|
||||
expect(output.schema).toEqual(payload);
|
||||
const result = BulkDeleteRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('only rule_id validates', () => {
|
||||
|
@ -69,11 +61,9 @@ describe('Bulk delete rules request schema', () => {
|
|||
{ rule_id: 'c1e1b359-7ac1-4e96-bc81-c683c092436f' },
|
||||
];
|
||||
|
||||
const decoded = BulkDeleteRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([]);
|
||||
expect(output.schema).toEqual(payload);
|
||||
const result = BulkDeleteRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('only rule_id validates with two elements', () => {
|
||||
|
@ -82,11 +72,9 @@ describe('Bulk delete rules request schema', () => {
|
|||
{ rule_id: '2' },
|
||||
];
|
||||
|
||||
const decoded = BulkDeleteRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([]);
|
||||
expect(output.schema).toEqual(payload);
|
||||
const result = BulkDeleteRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('both id and rule_id validates with two separate elements', () => {
|
||||
|
@ -95,10 +83,8 @@ describe('Bulk delete rules request schema', () => {
|
|||
{ rule_id: '2' },
|
||||
];
|
||||
|
||||
const decoded = BulkDeleteRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([]);
|
||||
expect(output.schema).toEqual(payload);
|
||||
const result = BulkDeleteRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,15 +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 { QueryRuleByIds } from '../../model/query_rule_by_ids';
|
||||
|
||||
/**
|
||||
* Request body parameters of the API route.
|
||||
*/
|
||||
export type BulkDeleteRulesRequestBody = t.TypeOf<typeof BulkDeleteRulesRequestBody>;
|
||||
export const BulkDeleteRulesRequestBody = t.array(QueryRuleByIds);
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 { RulePatchProps } from '../../../model/rule_schema/rule_schemas.gen';
|
||||
import { BulkCrudRulesResponse } from '../response_schema.gen';
|
||||
|
||||
export type BulkPatchRulesRequestBody = z.infer<typeof BulkPatchRulesRequestBody>;
|
||||
export const BulkPatchRulesRequestBody = z.array(RulePatchProps);
|
||||
export type BulkPatchRulesRequestBodyInput = z.input<typeof BulkPatchRulesRequestBody>;
|
||||
|
||||
export type BulkPatchRulesResponse = z.infer<typeof BulkPatchRulesResponse>;
|
||||
export const BulkPatchRulesResponse = BulkCrudRulesResponse;
|
|
@ -5,8 +5,8 @@ info:
|
|||
paths:
|
||||
/api/detection_engine/rules/_bulk_update:
|
||||
patch:
|
||||
operationId: PatchRulesBulk
|
||||
x-codegen-enabled: false
|
||||
operationId: BulkPatchRules
|
||||
x-codegen-enabled: true
|
||||
deprecated: true
|
||||
description: Updates multiple rules using the `PATCH` method.
|
||||
tags:
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { exactCheck, formatErrors, foldLeftRight } from '@kbn/securitysolution-io-ts-utils';
|
||||
import type { PatchRuleRequestBody } from '../../crud/patch_rule/patch_rule_route';
|
||||
import { BulkPatchRulesRequestBody } from './bulk_patch_rules_route';
|
||||
import { expectParseError, expectParseSuccess, stringifyZodError } from '@kbn/zod-helpers';
|
||||
import type { PatchRuleRequestBody } from '../../crud/patch_rule/patch_rule_route.gen';
|
||||
import { BulkPatchRulesRequestBody } from './bulk_patch_rules_route.gen';
|
||||
|
||||
// only the basics of testing are here.
|
||||
// see: patch_rules_schema.test.ts for the bulk of the validation tests
|
||||
|
@ -16,21 +16,17 @@ describe('Bulk patch rules request schema', () => {
|
|||
test('can take an empty array and validate it', () => {
|
||||
const payload: BulkPatchRulesRequestBody = [];
|
||||
|
||||
const decoded = BulkPatchRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(output.errors).toEqual([]);
|
||||
expect(output.schema).toEqual([]);
|
||||
const result = BulkPatchRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('single array of [id] does validate', () => {
|
||||
const payload: BulkPatchRulesRequestBody = [{ id: '4125761e-51da-4de9-a0c8-42824f532ddb' }];
|
||||
|
||||
const decoded = BulkPatchRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([]);
|
||||
expect(output.schema).toEqual(payload);
|
||||
const result = BulkPatchRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('two arrays of [id] validate', () => {
|
||||
|
@ -39,11 +35,9 @@ describe('Bulk patch rules request schema', () => {
|
|||
{ id: '192f403d-b285-4251-9e8b-785fcfcf22e8' },
|
||||
];
|
||||
|
||||
const decoded = BulkPatchRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([]);
|
||||
expect(output.schema).toEqual(payload);
|
||||
const result = BulkPatchRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('can set "note" to be a string', () => {
|
||||
|
@ -52,11 +46,9 @@ describe('Bulk patch rules request schema', () => {
|
|||
{ note: 'hi' },
|
||||
];
|
||||
|
||||
const decoded = BulkPatchRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([]);
|
||||
expect(output.schema).toEqual(payload);
|
||||
const result = BulkPatchRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('can set "note" to be an empty string', () => {
|
||||
|
@ -65,11 +57,9 @@ describe('Bulk patch rules request schema', () => {
|
|||
{ note: '' },
|
||||
];
|
||||
|
||||
const decoded = BulkPatchRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([]);
|
||||
expect(output.schema).toEqual(payload);
|
||||
const result = BulkPatchRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('cannot set "note" to be anything other than a string', () => {
|
||||
|
@ -78,12 +68,8 @@ describe('Bulk patch rules request schema', () => {
|
|||
{ note: { someprop: 'some value here' } },
|
||||
];
|
||||
|
||||
const decoded = BulkPatchRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([
|
||||
'Invalid value "{"someprop":"some value here"}" supplied to "note"',
|
||||
]);
|
||||
expect(output.schema).toEqual({});
|
||||
const result = BulkPatchRulesRequestBody.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"1: Invalid input"`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,15 +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 { RulePatchProps } from '../../../model';
|
||||
|
||||
/**
|
||||
* Request body parameters of the API route.
|
||||
*/
|
||||
export type BulkPatchRulesRequestBody = t.TypeOf<typeof BulkPatchRulesRequestBody>;
|
||||
export const BulkPatchRulesRequestBody = t.array(RulePatchProps);
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 { RuleUpdateProps } from '../../../model/rule_schema/rule_schemas.gen';
|
||||
import { BulkCrudRulesResponse } from '../response_schema.gen';
|
||||
|
||||
export type BulkUpdateRulesRequestBody = z.infer<typeof BulkUpdateRulesRequestBody>;
|
||||
export const BulkUpdateRulesRequestBody = z.array(RuleUpdateProps);
|
||||
export type BulkUpdateRulesRequestBodyInput = z.input<typeof BulkUpdateRulesRequestBody>;
|
||||
|
||||
export type BulkUpdateRulesResponse = z.infer<typeof BulkUpdateRulesResponse>;
|
||||
export const BulkUpdateRulesResponse = BulkCrudRulesResponse;
|
|
@ -5,8 +5,8 @@ info:
|
|||
paths:
|
||||
/api/detection_engine/rules/_bulk_update:
|
||||
put:
|
||||
operationId: UpdateRulesBulk
|
||||
x-codegen-enabled: false
|
||||
operationId: BulkUpdateRules
|
||||
x-codegen-enabled: true
|
||||
deprecated: true
|
||||
description: Updates multiple rules using the `PUT` method.
|
||||
tags:
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { exactCheck, formatErrors, foldLeftRight } from '@kbn/securitysolution-io-ts-utils';
|
||||
import { expectParseError, expectParseSuccess, stringifyZodError } from '@kbn/zod-helpers';
|
||||
import type { RuleUpdateProps } from '../../../model';
|
||||
import { getUpdateRulesSchemaMock } from '../../../model/rule_schema/mocks';
|
||||
import { BulkUpdateRulesRequestBody } from './bulk_update_rules_route';
|
||||
import { BulkUpdateRulesRequestBody } from './bulk_update_rules_route.gen';
|
||||
|
||||
// only the basics of testing are here.
|
||||
// see: update_rules_schema.test.ts for the bulk of the validation tests
|
||||
|
@ -17,40 +17,25 @@ describe('Bulk update rules request schema', () => {
|
|||
test('can take an empty array and validate it', () => {
|
||||
const payload: BulkUpdateRulesRequestBody = [];
|
||||
|
||||
const decoded = BulkUpdateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(output.errors).toEqual([]);
|
||||
expect(output.schema).toEqual([]);
|
||||
const result = BulkUpdateRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('made up values do not validate for a single element', () => {
|
||||
const payload: Array<{ madeUp: string }> = [{ madeUp: 'hi' }];
|
||||
|
||||
const decoded = BulkUpdateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toContain(
|
||||
'Invalid value "undefined" supplied to "description"'
|
||||
);
|
||||
expect(formatErrors(output.errors)).toContain(
|
||||
'Invalid value "undefined" supplied to "risk_score"'
|
||||
);
|
||||
expect(formatErrors(output.errors)).toContain('Invalid value "undefined" supplied to "name"');
|
||||
expect(formatErrors(output.errors)).toContain(
|
||||
'Invalid value "undefined" supplied to "severity"'
|
||||
);
|
||||
expect(output.schema).toEqual({});
|
||||
const result = BulkUpdateRulesRequestBody.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`);
|
||||
});
|
||||
|
||||
test('single array element does validate', () => {
|
||||
const payload: BulkUpdateRulesRequestBody = [getUpdateRulesSchemaMock()];
|
||||
|
||||
const decoded = BulkUpdateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([]);
|
||||
expect(output.schema).toEqual(payload);
|
||||
const result = BulkUpdateRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('two array elements do validate', () => {
|
||||
|
@ -59,11 +44,9 @@ describe('Bulk update rules request schema', () => {
|
|||
getUpdateRulesSchemaMock(),
|
||||
];
|
||||
|
||||
const decoded = BulkUpdateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([]);
|
||||
expect(output.schema).toEqual(payload);
|
||||
const result = BulkUpdateRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('single array element with a missing value (risk_score) will not validate', () => {
|
||||
|
@ -72,13 +55,9 @@ describe('Bulk update rules request schema', () => {
|
|||
delete singleItem.risk_score;
|
||||
const payload: BulkUpdateRulesRequestBody = [singleItem];
|
||||
|
||||
const decoded = BulkUpdateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([
|
||||
'Invalid value "undefined" supplied to "risk_score"',
|
||||
]);
|
||||
expect(output.schema).toEqual({});
|
||||
const result = BulkUpdateRulesRequestBody.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`);
|
||||
});
|
||||
|
||||
test('two array elements where the first is valid but the second is invalid (risk_score) will not validate', () => {
|
||||
|
@ -88,13 +67,9 @@ describe('Bulk update rules request schema', () => {
|
|||
delete secondItem.risk_score;
|
||||
const payload: BulkUpdateRulesRequestBody = [singleItem, secondItem];
|
||||
|
||||
const decoded = BulkUpdateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([
|
||||
'Invalid value "undefined" supplied to "risk_score"',
|
||||
]);
|
||||
expect(output.schema).toEqual({});
|
||||
const result = BulkUpdateRulesRequestBody.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"1: Invalid input"`);
|
||||
});
|
||||
|
||||
test('two array elements where the first is invalid (risk_score) but the second is valid will not validate', () => {
|
||||
|
@ -104,13 +79,9 @@ describe('Bulk update rules request schema', () => {
|
|||
delete singleItem.risk_score;
|
||||
const payload: BulkUpdateRulesRequestBody = [singleItem, secondItem];
|
||||
|
||||
const decoded = BulkUpdateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([
|
||||
'Invalid value "undefined" supplied to "risk_score"',
|
||||
]);
|
||||
expect(output.schema).toEqual({});
|
||||
const result = BulkUpdateRulesRequestBody.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`);
|
||||
});
|
||||
|
||||
test('two array elements where both are invalid (risk_score) will not validate', () => {
|
||||
|
@ -122,46 +93,14 @@ describe('Bulk update rules request schema', () => {
|
|||
delete secondItem.risk_score;
|
||||
const payload: BulkUpdateRulesRequestBody = [singleItem, secondItem];
|
||||
|
||||
const decoded = BulkUpdateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([
|
||||
'Invalid value "undefined" supplied to "risk_score"',
|
||||
]);
|
||||
expect(output.schema).toEqual({});
|
||||
const result = BulkUpdateRulesRequestBody.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
|
||||
`"0: Invalid input, 1: Invalid input"`
|
||||
);
|
||||
});
|
||||
|
||||
test('two array elements where the first is invalid (extra key and value) but the second is valid will not validate', () => {
|
||||
const singleItem: RuleUpdateProps & { madeUpValue: string } = {
|
||||
...getUpdateRulesSchemaMock(),
|
||||
madeUpValue: 'something',
|
||||
};
|
||||
const secondItem = getUpdateRulesSchemaMock();
|
||||
const payload = [singleItem, secondItem];
|
||||
|
||||
const decoded = BulkUpdateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual(['invalid keys "madeUpValue"']);
|
||||
expect(output.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('two array elements where the second is invalid (extra key and value) but the first is valid will not validate', () => {
|
||||
const singleItem: RuleUpdateProps = getUpdateRulesSchemaMock();
|
||||
const secondItem: RuleUpdateProps & { madeUpValue: string } = {
|
||||
...getUpdateRulesSchemaMock(),
|
||||
madeUpValue: 'something',
|
||||
};
|
||||
const payload: BulkUpdateRulesRequestBody = [singleItem, secondItem];
|
||||
|
||||
const decoded = BulkUpdateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual(['invalid keys "madeUpValue"']);
|
||||
expect(output.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('two array elements where both are invalid (extra key and value) will not validate', () => {
|
||||
test('extra props will be omitted from the payload after validation', () => {
|
||||
const singleItem: RuleUpdateProps & { madeUpValue: string } = {
|
||||
...getUpdateRulesSchemaMock(),
|
||||
madeUpValue: 'something',
|
||||
|
@ -172,22 +111,18 @@ describe('Bulk update rules request schema', () => {
|
|||
};
|
||||
const payload: BulkUpdateRulesRequestBody = [singleItem, secondItem];
|
||||
|
||||
const decoded = BulkUpdateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual(['invalid keys "madeUpValue,madeUpValue"']);
|
||||
expect(output.schema).toEqual({});
|
||||
const result = BulkUpdateRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual([getUpdateRulesSchemaMock(), getUpdateRulesSchemaMock()]);
|
||||
});
|
||||
|
||||
test('You cannot set the severity to a value other than low, medium, high, or critical', () => {
|
||||
const badSeverity = { ...getUpdateRulesSchemaMock(), severity: 'madeup' };
|
||||
const payload = [badSeverity];
|
||||
|
||||
const decoded = BulkUpdateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual(['Invalid value "madeup" supplied to "severity"']);
|
||||
expect(output.schema).toEqual({});
|
||||
const result = BulkUpdateRulesRequestBody.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`);
|
||||
});
|
||||
|
||||
test('You can set "namespace" to a string', () => {
|
||||
|
@ -195,11 +130,9 @@ describe('Bulk update rules request schema', () => {
|
|||
{ ...getUpdateRulesSchemaMock(), namespace: 'a namespace' },
|
||||
];
|
||||
|
||||
const decoded = BulkUpdateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([]);
|
||||
expect(output.schema).toEqual(payload);
|
||||
const result = BulkUpdateRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('You can set "note" to a string', () => {
|
||||
|
@ -207,21 +140,17 @@ describe('Bulk update rules request schema', () => {
|
|||
{ ...getUpdateRulesSchemaMock(), note: '# test markdown' },
|
||||
];
|
||||
|
||||
const decoded = BulkUpdateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([]);
|
||||
expect(output.schema).toEqual(payload);
|
||||
const result = BulkUpdateRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('You can set "note" to an empty string', () => {
|
||||
const payload: BulkUpdateRulesRequestBody = [{ ...getUpdateRulesSchemaMock(), note: '' }];
|
||||
|
||||
const decoded = BulkUpdateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([]);
|
||||
expect(output.schema).toEqual(payload);
|
||||
const result = BulkUpdateRulesRequestBody.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('You cant set "note" to anything other than string', () => {
|
||||
|
@ -234,12 +163,8 @@ describe('Bulk update rules request schema', () => {
|
|||
},
|
||||
];
|
||||
|
||||
const decoded = BulkUpdateRulesRequestBody.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const output = foldLeftRight(checked);
|
||||
expect(formatErrors(output.errors)).toEqual([
|
||||
'Invalid value "{"something":"some object"}" supplied to "note"',
|
||||
]);
|
||||
expect(output.schema).toEqual({});
|
||||
const result = BulkUpdateRulesRequestBody.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,15 +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 { RuleUpdateProps } from '../../../model';
|
||||
|
||||
/**
|
||||
* Request body parameters of the API route.
|
||||
*/
|
||||
export type BulkUpdateRulesRequestBody = t.TypeOf<typeof BulkUpdateRulesRequestBody>;
|
||||
export const BulkUpdateRulesRequestBody = t.array(RuleUpdateProps);
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 { RuleResponse } from '../../model/rule_schema/rule_schemas.gen';
|
||||
import { ErrorSchema } from '../../model/error_schema.gen';
|
||||
|
||||
export type BulkCrudRulesResponse = z.infer<typeof BulkCrudRulesResponse>;
|
||||
export const BulkCrudRulesResponse = z.array(z.union([RuleResponse, ErrorSchema]));
|
|
@ -4,7 +4,7 @@ info:
|
|||
version: 8.9.0
|
||||
paths: {}
|
||||
components:
|
||||
x-codegen-enabled: false
|
||||
x-codegen-enabled: true
|
||||
schemas:
|
||||
BulkCrudRulesResponse:
|
||||
type: array
|
||||
|
|
|
@ -5,45 +5,36 @@
|
|||
* 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 type { RuleResponse, ErrorSchema } from '../../model';
|
||||
import { getRulesSchemaMock } from '../../model/rule_schema/mocks';
|
||||
import type { ErrorSchema, RuleResponse } from '../../model';
|
||||
import { getErrorSchemaMock } from '../../model/error_schema.mock';
|
||||
import { getRulesSchemaMock } from '../../model/rule_schema/mocks';
|
||||
|
||||
import { BulkCrudRulesResponse } from './response_schema';
|
||||
import { expectParseError, expectParseSuccess, stringifyZodError } from '@kbn/zod-helpers';
|
||||
import { BulkCrudRulesResponse } from './response_schema.gen';
|
||||
|
||||
describe('Bulk CRUD rules response schema', () => {
|
||||
test('it should validate a regular message and and error together with a uuid', () => {
|
||||
const payload: BulkCrudRulesResponse = [getRulesSchemaMock(), getErrorSchemaMock()];
|
||||
const decoded = BulkCrudRulesResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual([getRulesSchemaMock(), getErrorSchemaMock()]);
|
||||
const result = BulkCrudRulesResponse.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate a regular message and and error together when the error has a non UUID', () => {
|
||||
test('it should validate a regular message and error together when the error has a non UUID', () => {
|
||||
const payload: BulkCrudRulesResponse = [getRulesSchemaMock(), getErrorSchemaMock('fake id')];
|
||||
const decoded = BulkCrudRulesResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual([getRulesSchemaMock(), getErrorSchemaMock('fake id')]);
|
||||
const result = BulkCrudRulesResponse.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an error', () => {
|
||||
const payload: BulkCrudRulesResponse = [getErrorSchemaMock('fake id')];
|
||||
const decoded = BulkCrudRulesResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual([getErrorSchemaMock('fake id')]);
|
||||
const result = BulkCrudRulesResponse.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should NOT validate a rule with a deleted value', () => {
|
||||
|
@ -51,15 +42,10 @@ describe('Bulk CRUD rules response schema', () => {
|
|||
// @ts-expect-error
|
||||
delete rule.name;
|
||||
const payload: BulkCrudRulesResponse = [rule];
|
||||
const decoded = BulkCrudRulesResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "name"',
|
||||
'Invalid value "undefined" supplied to "error"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
const result = BulkCrudRulesResponse.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`);
|
||||
});
|
||||
|
||||
test('it should NOT validate an invalid error message with a deleted value', () => {
|
||||
|
@ -67,38 +53,30 @@ describe('Bulk CRUD rules response schema', () => {
|
|||
// @ts-expect-error
|
||||
delete error.error;
|
||||
const payload: BulkCrudRulesResponse = [error];
|
||||
const decoded = BulkCrudRulesResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toContain(
|
||||
'Invalid value "undefined" supplied to "error"'
|
||||
);
|
||||
expect(message.schema).toEqual({});
|
||||
const result = BulkCrudRulesResponse.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`);
|
||||
});
|
||||
|
||||
test('it should NOT validate a type of "query" when it has extra data', () => {
|
||||
test('it should omit any extra rule props', () => {
|
||||
const rule: RuleResponse & { invalid_extra_data?: string } = getRulesSchemaMock();
|
||||
rule.invalid_extra_data = 'invalid_extra_data';
|
||||
const payload: BulkCrudRulesResponse = [rule];
|
||||
const decoded = BulkCrudRulesResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']);
|
||||
expect(message.schema).toEqual({});
|
||||
const result = BulkCrudRulesResponse.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual([getRulesSchemaMock()]);
|
||||
});
|
||||
|
||||
test('it should NOT validate a type of "query" when it has extra data next to a valid error', () => {
|
||||
const rule: RuleResponse & { invalid_extra_data?: string } = getRulesSchemaMock();
|
||||
rule.invalid_extra_data = 'invalid_extra_data';
|
||||
const payload: BulkCrudRulesResponse = [getErrorSchemaMock(), rule];
|
||||
const decoded = BulkCrudRulesResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']);
|
||||
expect(message.schema).toEqual({});
|
||||
const result = BulkCrudRulesResponse.safeParse(payload);
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual([getErrorSchemaMock(), getRulesSchemaMock()]);
|
||||
});
|
||||
|
||||
test('it should NOT validate an error when it has extra data', () => {
|
||||
|
@ -106,12 +84,12 @@ describe('Bulk CRUD rules response schema', () => {
|
|||
const error: InvalidError = getErrorSchemaMock();
|
||||
error.invalid_extra_data = 'invalid';
|
||||
const payload: BulkCrudRulesResponse = [error];
|
||||
const decoded = BulkCrudRulesResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']);
|
||||
expect(message.schema).toEqual({});
|
||||
const result = BulkCrudRulesResponse.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
|
||||
`"0: Unrecognized key(s) in object: 'invalid_extra_data'"`
|
||||
);
|
||||
});
|
||||
|
||||
test('it should NOT validate an error when it has extra data next to a valid payload element', () => {
|
||||
|
@ -119,11 +97,11 @@ describe('Bulk CRUD rules response schema', () => {
|
|||
const error: InvalidError = getErrorSchemaMock();
|
||||
error.invalid_extra_data = 'invalid';
|
||||
const payload: BulkCrudRulesResponse = [getRulesSchemaMock(), error];
|
||||
const decoded = BulkCrudRulesResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']);
|
||||
expect(message.schema).toEqual({});
|
||||
const result = BulkCrudRulesResponse.safeParse(payload);
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
|
||||
`"1: Unrecognized key(s) in object: 'invalid_extra_data'"`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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 { RuleCreateProps, RuleResponse } from '../../../model/rule_schema/rule_schemas.gen';
|
||||
|
||||
export type CreateRuleRequestBody = z.infer<typeof CreateRuleRequestBody>;
|
||||
export const CreateRuleRequestBody = RuleCreateProps;
|
||||
export type CreateRuleRequestBodyInput = z.input<typeof CreateRuleRequestBody>;
|
||||
|
||||
export type CreateRuleResponse = z.infer<typeof CreateRuleResponse>;
|
||||
export const CreateRuleResponse = RuleResponse;
|
|
@ -6,7 +6,7 @@ paths:
|
|||
/api/detection_engine/rules:
|
||||
post:
|
||||
operationId: CreateRule
|
||||
x-codegen-enabled: false
|
||||
x-codegen-enabled: true
|
||||
description: Create a single detection rule
|
||||
tags:
|
||||
- Rules API
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type * as t from 'io-ts';
|
||||
import { RuleCreateProps, RuleResponse } from '../../../model';
|
||||
|
||||
export const CreateRuleRequestBody = RuleCreateProps;
|
||||
export type CreateRuleRequestBody = t.TypeOf<typeof CreateRuleRequestBody>;
|
||||
|
||||
export const CreateRuleResponse = RuleResponse;
|
||||
export type CreateRuleResponse = t.TypeOf<typeof CreateRuleResponse>;
|
|
@ -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.
|
||||
*/
|
||||
|
||||
import { RuleObjectId, RuleSignatureId } from '../../../model/rule_schema/common_attributes.gen';
|
||||
import { RuleResponse } from '../../../model/rule_schema/rule_schemas.gen';
|
||||
|
||||
export type DeleteRuleRequestQuery = z.infer<typeof DeleteRuleRequestQuery>;
|
||||
export const DeleteRuleRequestQuery = z.object({
|
||||
/**
|
||||
* The rule's `id` value.
|
||||
*/
|
||||
id: RuleObjectId.optional(),
|
||||
/**
|
||||
* The rule's `rule_id` value.
|
||||
*/
|
||||
rule_id: RuleSignatureId.optional(),
|
||||
});
|
||||
export type DeleteRuleRequestQueryInput = z.input<typeof DeleteRuleRequestQuery>;
|
||||
|
||||
export type DeleteRuleResponse = z.infer<typeof DeleteRuleResponse>;
|
||||
export const DeleteRuleResponse = RuleResponse;
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue