mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
## Summary Main ticket ([Internal link](https://github.com/elastic/security-team/issues/12003)) To allow users to schedule Attack Discovery generations, we will use either [Alerting Framework](https://www.elastic.co/guide/en/kibana/current/alerting-getting-started.html). These changes add functionality to manage new alerts type - Attack Discovery Schedule. ### Introduced endpoints - **Create** AD scheduling rule route: `POST /internal/elastic_assistant/attack_discovery/schedules` - **Read/Get** AD scheduling rule by id route: `GET /internal/elastic_assistant/attack_discovery/schedules/{id}` - **Update** AD scheduling rule by id route: `PUT /internal/elastic_assistant/attack_discovery/schedules/{id}` - **Delete** AD scheduling rule by id route: `DELETE /internal/elastic_assistant/attack_discovery/schedules/{id}` - **Enable** AD scheduling rule by id route: `POST /internal/elastic_assistant/attack_discovery/schedules/{id}/_enable` - **Disable** AD scheduling rule by id route: `POST /internal/elastic_assistant/attack_discovery/schedules/{id}/_disable` - **Find** all existing AD scheduling rules route: `GET /internal/elastic_assistant/attack_discovery/schedules/_find` ## NOTES The feature is hidden behind the feature flag: > xpack.securitySolution.enableExperimental: ['assistantAttackDiscoverySchedulingEnabled'] ## cURL examples <details> <summary>Create AD scheduling rule route</summary> ```curl curl --location 'http://localhost:5601/internal/elastic_assistant/attack_discovery/schedules' \ --header 'kbn-xsrf: true' \ --header 'elastic-api-version: 1' \ --header 'x-elastic-internal-origin: security-solution' \ --header 'Content-Type: application/json' \ --data '{ "name": "Test Schedule", "schedule": { "interval": "10m" }, "params": { "alertsIndexPattern": ".alerts-security.alerts-default", "apiConfig": { "connectorId": "gpt-4o", "actionTypeId": ".gen-ai" }, "end": "now", "size": 100, "start": "now-24h" } }' ``` </details> <details> <summary>Read/Get AD scheduling rule by id route</summary> ```curl curl --location 'http://localhost:5601/internal/elastic_assistant/attack_discovery/schedules/{id}' \ --header 'kbn-xsrf: true' \ --header 'elastic-api-version: 1' \ --header 'x-elastic-internal-origin: security-solution' ``` </details> <details> <summary>Update AD scheduling rule by id route</summary> ```curl curl --location --request PUT 'http://localhost:5601/internal/elastic_assistant/attack_discovery/schedules/{id}' \ --header 'kbn-xsrf: true' \ --header 'elastic-api-version: 1' \ --header 'x-elastic-internal-origin: security-solution' \ --header 'Content-Type: application/json' \ --data '{ "name": "Test Schedule - Updated", "schedule": { "interval": "123m" }, "params": { "alertsIndexPattern": ".alerts-security.alerts-default", "apiConfig": { "connectorId": "gpt-4o", "actionTypeId": ".gen-ai" }, "end": "now", "size": 35, "start": "now-24h" }, "actions": [] }' ``` </details> <details> <summary>Delete AD scheduling rule by id route</summary> ```curl curl --location --request DELETE 'http://localhost:5601/internal/elastic_assistant/attack_discovery/schedules/{id}' \ --header 'kbn-xsrf: true' \ --header 'elastic-api-version: 1' \ --header 'x-elastic-internal-origin: security-solution' ``` </details> <details> <summary>Enable AD scheduling rule by id route</summary> ```curl curl --location --request POST 'http://localhost:5601/internal/elastic_assistant/attack_discovery/schedules/{id}/_enable' \ --header 'kbn-xsrf: true' \ --header 'elastic-api-version: 1' \ --header 'x-elastic-internal-origin: security-solution' ``` </details> <details> <summary>Disable AD scheduling rule by id route</summary> ```curl curl --location --request POST 'http://localhost:5601/internal/elastic_assistant/attack_discovery/schedules/{id}/_disable' \ --header 'kbn-xsrf: true' \ --header 'elastic-api-version: 1' \ --header 'x-elastic-internal-origin: security-solution' ``` </details> <details> <summary>Find all existing AD scheduling rules route</summary> ```curl curl --location 'http://localhost:5601/internal/elastic_assistant/attack_discovery/schedules/_find' \ --header 'kbn-xsrf: true' \ --header 'elastic-api-version: 1' \ --header 'x-elastic-internal-origin: security-solution' ``` </details> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
387e2d95ec
commit
fc11ca94f5
52 changed files with 3627 additions and 59 deletions
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
// ---------------------------------- WARNING ----------------------------------
|
||||
// this file was generated, and should not be edited by hand
|
||||
// ---------------------------------- WARNING ----------------------------------
|
||||
import * as rt from 'io-ts';
|
||||
import type { Either } from 'fp-ts/lib/Either';
|
||||
import { AlertSchema } from './alert_schema';
|
||||
import { EcsSchema } from './ecs_schema';
|
||||
const ISO_DATE_PATTERN = /^d{4}-d{2}-d{2}Td{2}:d{2}:d{2}.d{3}Z$/;
|
||||
export const IsoDateString = new rt.Type<string, string, unknown>(
|
||||
'IsoDateString',
|
||||
rt.string.is,
|
||||
(input, context): Either<rt.Errors, string> => {
|
||||
if (typeof input === 'string' && ISO_DATE_PATTERN.test(input)) {
|
||||
return rt.success(input);
|
||||
} else {
|
||||
return rt.failure(input, context);
|
||||
}
|
||||
},
|
||||
rt.identity
|
||||
);
|
||||
export type IsoDateStringC = typeof IsoDateString;
|
||||
export const schemaUnknown = rt.unknown;
|
||||
export const schemaUnknownArray = rt.array(rt.unknown);
|
||||
export const schemaString = rt.string;
|
||||
export const schemaStringArray = rt.array(schemaString);
|
||||
export const schemaNumber = rt.number;
|
||||
export const schemaNumberArray = rt.array(schemaNumber);
|
||||
export const schemaDate = rt.union([IsoDateString, schemaNumber]);
|
||||
export const schemaDateArray = rt.array(schemaDate);
|
||||
export const schemaDateRange = rt.partial({
|
||||
gte: schemaDate,
|
||||
lte: schemaDate,
|
||||
});
|
||||
export const schemaDateRangeArray = rt.array(schemaDateRange);
|
||||
export const schemaStringOrNumber = rt.union([schemaString, schemaNumber]);
|
||||
export const schemaStringOrNumberArray = rt.array(schemaStringOrNumber);
|
||||
export const schemaBoolean = rt.boolean;
|
||||
export const schemaBooleanArray = rt.array(schemaBoolean);
|
||||
const schemaGeoPointCoords = rt.type({
|
||||
type: schemaString,
|
||||
coordinates: schemaNumberArray,
|
||||
});
|
||||
const schemaGeoPointString = schemaString;
|
||||
const schemaGeoPointLatLon = rt.type({
|
||||
lat: schemaNumber,
|
||||
lon: schemaNumber,
|
||||
});
|
||||
const schemaGeoPointLocation = rt.type({
|
||||
location: schemaNumberArray,
|
||||
});
|
||||
const schemaGeoPointLocationString = rt.type({
|
||||
location: schemaString,
|
||||
});
|
||||
export const schemaGeoPoint = rt.union([
|
||||
schemaGeoPointCoords,
|
||||
schemaGeoPointString,
|
||||
schemaGeoPointLatLon,
|
||||
schemaGeoPointLocation,
|
||||
schemaGeoPointLocationString,
|
||||
]);
|
||||
export const schemaGeoPointArray = rt.array(schemaGeoPoint);
|
||||
// prettier-ignore
|
||||
const SecurityAttackDiscoveryAlertRequired = rt.type({
|
||||
'@timestamp': schemaDate,
|
||||
'kibana.alert.attack_discovery.alert_ids': schemaStringArray,
|
||||
'kibana.alert.attack_discovery.alerts_context_count': schemaNumber,
|
||||
'kibana.alert.attack_discovery.api_config': schemaUnknown,
|
||||
'kibana.alert.attack_discovery.details_markdown': schemaString,
|
||||
'kibana.alert.attack_discovery.details_markdown_with_replacements': schemaString,
|
||||
'kibana.alert.attack_discovery.summary_markdown': schemaString,
|
||||
'kibana.alert.attack_discovery.summary_markdown_with_replacements': schemaString,
|
||||
'kibana.alert.attack_discovery.title': schemaString,
|
||||
'kibana.alert.attack_discovery.title_with_replacements': schemaString,
|
||||
'kibana.alert.attack_discovery.users.id': schemaString,
|
||||
'kibana.alert.instance.id': schemaString,
|
||||
'kibana.alert.rule.category': schemaString,
|
||||
'kibana.alert.rule.consumer': schemaString,
|
||||
'kibana.alert.rule.name': schemaString,
|
||||
'kibana.alert.rule.producer': schemaString,
|
||||
'kibana.alert.rule.revision': schemaStringOrNumber,
|
||||
'kibana.alert.rule.rule_type_id': schemaString,
|
||||
'kibana.alert.rule.uuid': schemaString,
|
||||
'kibana.alert.status': schemaString,
|
||||
'kibana.alert.uuid': schemaString,
|
||||
'kibana.space_ids': schemaStringArray,
|
||||
});
|
||||
// prettier-ignore
|
||||
const SecurityAttackDiscoveryAlertOptional = rt.partial({
|
||||
'event.action': schemaString,
|
||||
'event.kind': schemaString,
|
||||
'event.original': schemaString,
|
||||
'kibana.alert.action_group': schemaString,
|
||||
'kibana.alert.attack_discovery.api_config.model': schemaString,
|
||||
'kibana.alert.attack_discovery.api_config.provider': schemaString,
|
||||
'kibana.alert.attack_discovery.entity_summary_markdown': schemaString,
|
||||
'kibana.alert.attack_discovery.entity_summary_markdown_with_replacements': schemaString,
|
||||
'kibana.alert.attack_discovery.mitre_attack_tactics': schemaStringArray,
|
||||
'kibana.alert.attack_discovery.replacements': schemaUnknown,
|
||||
'kibana.alert.attack_discovery.user.id': schemaString,
|
||||
'kibana.alert.attack_discovery.users': rt.array(
|
||||
rt.partial({
|
||||
name: schemaString,
|
||||
})
|
||||
),
|
||||
'kibana.alert.case_ids': schemaStringArray,
|
||||
'kibana.alert.consecutive_matches': schemaStringOrNumber,
|
||||
'kibana.alert.duration.us': schemaStringOrNumber,
|
||||
'kibana.alert.end': schemaDate,
|
||||
'kibana.alert.flapping': schemaBoolean,
|
||||
'kibana.alert.flapping_history': schemaBooleanArray,
|
||||
'kibana.alert.intended_timestamp': schemaDate,
|
||||
'kibana.alert.last_detected': schemaDate,
|
||||
'kibana.alert.maintenance_window_ids': schemaStringArray,
|
||||
'kibana.alert.pending_recovered_count': schemaStringOrNumber,
|
||||
'kibana.alert.previous_action_group': schemaString,
|
||||
'kibana.alert.reason': schemaString,
|
||||
'kibana.alert.risk_score': schemaNumber,
|
||||
'kibana.alert.rule.execution.timestamp': schemaDate,
|
||||
'kibana.alert.rule.execution.type': schemaString,
|
||||
'kibana.alert.rule.execution.uuid': schemaString,
|
||||
'kibana.alert.rule.parameters': schemaUnknown,
|
||||
'kibana.alert.rule.tags': schemaStringArray,
|
||||
'kibana.alert.severity_improving': schemaBoolean,
|
||||
'kibana.alert.start': schemaDate,
|
||||
'kibana.alert.time_range': schemaDateRange,
|
||||
'kibana.alert.url': schemaString,
|
||||
'kibana.alert.workflow_assignee_ids': schemaStringArray,
|
||||
'kibana.alert.workflow_status': schemaString,
|
||||
'kibana.alert.workflow_tags': schemaStringArray,
|
||||
'kibana.version': schemaString,
|
||||
tags: schemaStringArray,
|
||||
});
|
||||
|
||||
// prettier-ignore
|
||||
export const SecurityAttackDiscoveryAlertSchema = rt.intersection([SecurityAttackDiscoveryAlertRequired, SecurityAttackDiscoveryAlertOptional, AlertSchema, EcsSchema]);
|
||||
// prettier-ignore
|
||||
export type SecurityAttackDiscoveryAlert = rt.TypeOf<typeof SecurityAttackDiscoveryAlertSchema>;
|
|
@ -14,6 +14,7 @@ import type { ObservabilityMetricsAlert } from './generated/observability_metric
|
|||
import type { ObservabilitySloAlert } from './generated/observability_slo_schema';
|
||||
import type { ObservabilityUptimeAlert } from './generated/observability_uptime_schema';
|
||||
import type { SecurityAlert } from './generated/security_schema';
|
||||
import type { SecurityAttackDiscoveryAlert } from './generated/security_attack_discovery_schema';
|
||||
import type { MlAnomalyDetectionAlert } from './generated/ml_anomaly_detection_schema';
|
||||
import type { DefaultAlert } from './generated/default_schema';
|
||||
import type { MlAnomalyDetectionHealthAlert } from './generated/ml_anomaly_detection_health_schema';
|
||||
|
@ -28,6 +29,7 @@ export type { ObservabilityMetricsAlert } from './generated/observability_metric
|
|||
export type { ObservabilitySloAlert } from './generated/observability_slo_schema';
|
||||
export type { ObservabilityUptimeAlert } from './generated/observability_uptime_schema';
|
||||
export type { SecurityAlert } from './generated/security_schema';
|
||||
export type { SecurityAttackDiscoveryAlert } from './generated/security_attack_discovery_schema';
|
||||
export type { StackAlert } from './generated/stack_schema';
|
||||
export type { MlAnomalyDetectionAlert } from './generated/ml_anomaly_detection_schema';
|
||||
export type { MlAnomalyDetectionHealthAlert } from './generated/ml_anomaly_detection_health_schema';
|
||||
|
@ -42,6 +44,7 @@ export type AADAlert =
|
|||
| ObservabilitySloAlert
|
||||
| ObservabilityUptimeAlert
|
||||
| SecurityAlert
|
||||
| SecurityAttackDiscoveryAlert
|
||||
| MlAnomalyDetectionAlert
|
||||
| MlAnomalyDetectionHealthAlert
|
||||
| TransformHealthAlert
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
* NOTICE: Do not edit this file manually.
|
||||
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
|
||||
*
|
||||
* info:
|
||||
* title: Attack discovery scheduling API endpoint
|
||||
* version: 1
|
||||
*/
|
||||
|
||||
import { z } from '@kbn/zod';
|
||||
|
||||
import {
|
||||
AttackDiscoveryScheduleCreateProps,
|
||||
AttackDiscoverySchedule,
|
||||
AttackDiscoveryScheduleUpdateProps,
|
||||
} from './schedules.gen';
|
||||
import { NonEmptyString } from '../common_attributes.gen';
|
||||
|
||||
/**
|
||||
* Object containing Attack Discovery schedule.
|
||||
*/
|
||||
export type AttackDiscoveryGenericResponse = z.infer<typeof AttackDiscoveryGenericResponse>;
|
||||
export const AttackDiscoveryGenericResponse = z.object({}).catchall(z.unknown());
|
||||
|
||||
/**
|
||||
* An attack discovery generic error
|
||||
*/
|
||||
export type AttackDiscoveryGenericError = z.infer<typeof AttackDiscoveryGenericError>;
|
||||
export const AttackDiscoveryGenericError = z.object({
|
||||
statusCode: z.number().optional(),
|
||||
error: z.string().optional(),
|
||||
message: z.string().optional(),
|
||||
});
|
||||
|
||||
export type CreateAttackDiscoverySchedulesRequestBody = z.infer<
|
||||
typeof CreateAttackDiscoverySchedulesRequestBody
|
||||
>;
|
||||
export const CreateAttackDiscoverySchedulesRequestBody = AttackDiscoveryScheduleCreateProps;
|
||||
export type CreateAttackDiscoverySchedulesRequestBodyInput = z.input<
|
||||
typeof CreateAttackDiscoverySchedulesRequestBody
|
||||
>;
|
||||
|
||||
export type CreateAttackDiscoverySchedulesResponse = z.infer<
|
||||
typeof CreateAttackDiscoverySchedulesResponse
|
||||
>;
|
||||
export const CreateAttackDiscoverySchedulesResponse = AttackDiscoverySchedule;
|
||||
|
||||
export type DeleteAttackDiscoverySchedulesRequestParams = z.infer<
|
||||
typeof DeleteAttackDiscoverySchedulesRequestParams
|
||||
>;
|
||||
export const DeleteAttackDiscoverySchedulesRequestParams = z.object({
|
||||
/**
|
||||
* The Attack Discovery schedule's `id` value.
|
||||
*/
|
||||
id: NonEmptyString,
|
||||
});
|
||||
export type DeleteAttackDiscoverySchedulesRequestParamsInput = z.input<
|
||||
typeof DeleteAttackDiscoverySchedulesRequestParams
|
||||
>;
|
||||
|
||||
export type DeleteAttackDiscoverySchedulesResponse = z.infer<
|
||||
typeof DeleteAttackDiscoverySchedulesResponse
|
||||
>;
|
||||
export const DeleteAttackDiscoverySchedulesResponse = z.object({
|
||||
id: NonEmptyString,
|
||||
});
|
||||
|
||||
export type DisableAttackDiscoverySchedulesRequestParams = z.infer<
|
||||
typeof DisableAttackDiscoverySchedulesRequestParams
|
||||
>;
|
||||
export const DisableAttackDiscoverySchedulesRequestParams = z.object({
|
||||
/**
|
||||
* The Attack Discovery schedule's `id` value.
|
||||
*/
|
||||
id: NonEmptyString,
|
||||
});
|
||||
export type DisableAttackDiscoverySchedulesRequestParamsInput = z.input<
|
||||
typeof DisableAttackDiscoverySchedulesRequestParams
|
||||
>;
|
||||
|
||||
export type DisableAttackDiscoverySchedulesResponse = z.infer<
|
||||
typeof DisableAttackDiscoverySchedulesResponse
|
||||
>;
|
||||
export const DisableAttackDiscoverySchedulesResponse = z.object({
|
||||
id: NonEmptyString,
|
||||
});
|
||||
|
||||
export type EnableAttackDiscoverySchedulesRequestParams = z.infer<
|
||||
typeof EnableAttackDiscoverySchedulesRequestParams
|
||||
>;
|
||||
export const EnableAttackDiscoverySchedulesRequestParams = z.object({
|
||||
/**
|
||||
* The Attack Discovery schedule's `id` value.
|
||||
*/
|
||||
id: NonEmptyString,
|
||||
});
|
||||
export type EnableAttackDiscoverySchedulesRequestParamsInput = z.input<
|
||||
typeof EnableAttackDiscoverySchedulesRequestParams
|
||||
>;
|
||||
|
||||
export type EnableAttackDiscoverySchedulesResponse = z.infer<
|
||||
typeof EnableAttackDiscoverySchedulesResponse
|
||||
>;
|
||||
export const EnableAttackDiscoverySchedulesResponse = z.object({
|
||||
id: NonEmptyString,
|
||||
});
|
||||
|
||||
export type GetAttackDiscoverySchedulesRequestParams = z.infer<
|
||||
typeof GetAttackDiscoverySchedulesRequestParams
|
||||
>;
|
||||
export const GetAttackDiscoverySchedulesRequestParams = z.object({
|
||||
/**
|
||||
* The Attack Discovery schedule's `id` value.
|
||||
*/
|
||||
id: NonEmptyString,
|
||||
});
|
||||
export type GetAttackDiscoverySchedulesRequestParamsInput = z.input<
|
||||
typeof GetAttackDiscoverySchedulesRequestParams
|
||||
>;
|
||||
|
||||
export type GetAttackDiscoverySchedulesResponse = z.infer<
|
||||
typeof GetAttackDiscoverySchedulesResponse
|
||||
>;
|
||||
export const GetAttackDiscoverySchedulesResponse = AttackDiscoverySchedule;
|
||||
|
||||
export type UpdateAttackDiscoverySchedulesRequestParams = z.infer<
|
||||
typeof UpdateAttackDiscoverySchedulesRequestParams
|
||||
>;
|
||||
export const UpdateAttackDiscoverySchedulesRequestParams = z.object({
|
||||
/**
|
||||
* The Attack Discovery schedule's `id` value.
|
||||
*/
|
||||
id: NonEmptyString,
|
||||
});
|
||||
export type UpdateAttackDiscoverySchedulesRequestParamsInput = z.input<
|
||||
typeof UpdateAttackDiscoverySchedulesRequestParams
|
||||
>;
|
||||
|
||||
export type UpdateAttackDiscoverySchedulesRequestBody = z.infer<
|
||||
typeof UpdateAttackDiscoverySchedulesRequestBody
|
||||
>;
|
||||
export const UpdateAttackDiscoverySchedulesRequestBody = AttackDiscoveryScheduleUpdateProps;
|
||||
export type UpdateAttackDiscoverySchedulesRequestBodyInput = z.input<
|
||||
typeof UpdateAttackDiscoverySchedulesRequestBody
|
||||
>;
|
||||
|
||||
export type UpdateAttackDiscoverySchedulesResponse = z.infer<
|
||||
typeof UpdateAttackDiscoverySchedulesResponse
|
||||
>;
|
||||
export const UpdateAttackDiscoverySchedulesResponse = AttackDiscoverySchedule;
|
|
@ -0,0 +1,218 @@
|
|||
openapi: 3.0.0
|
||||
info:
|
||||
title: Attack discovery scheduling API endpoint
|
||||
version: '1'
|
||||
components:
|
||||
x-codegen-enabled: true
|
||||
schemas:
|
||||
AttackDiscoveryGenericResponse:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
description: Object containing Attack Discovery schedule.
|
||||
AttackDiscoveryGenericError:
|
||||
type: object
|
||||
description: An attack discovery generic error
|
||||
properties:
|
||||
statusCode:
|
||||
type: number
|
||||
error:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
|
||||
paths:
|
||||
/internal/elastic_assistant/attack_discovery/schedules:
|
||||
post:
|
||||
x-codegen-enabled: true
|
||||
x-labels: [ess, serverless]
|
||||
operationId: CreateAttackDiscoverySchedules
|
||||
description: Creates attack discovery schedule
|
||||
summary: Creates attack discovery schedule
|
||||
tags:
|
||||
- attack_discovery_schedule
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: './schedules.schema.yaml#/components/schemas/AttackDiscoveryScheduleCreateProps'
|
||||
responses:
|
||||
200:
|
||||
description: Successful response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: './schedules.schema.yaml#/components/schemas/AttackDiscoverySchedule'
|
||||
400:
|
||||
description: Bad request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AttackDiscoveryGenericError'
|
||||
|
||||
/internal/elastic_assistant/attack_discovery/schedules/{id}:
|
||||
get:
|
||||
x-codegen-enabled: true
|
||||
x-labels: [ess, serverless]
|
||||
operationId: GetAttackDiscoverySchedules
|
||||
description: Gets attack discovery schedule
|
||||
summary: Gets attack discovery schedule
|
||||
tags:
|
||||
- attack_discovery_schedule
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The Attack Discovery schedule's `id` value.
|
||||
schema:
|
||||
$ref: '../common_attributes.schema.yaml#/components/schemas/NonEmptyString'
|
||||
responses:
|
||||
200:
|
||||
description: Successful request returning an Attack Discovery schedule
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: './schedules.schema.yaml#/components/schemas/AttackDiscoverySchedule'
|
||||
400:
|
||||
description: Bad request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AttackDiscoveryGenericError'
|
||||
put:
|
||||
x-codegen-enabled: true
|
||||
x-labels: [ess, serverless]
|
||||
operationId: UpdateAttackDiscoverySchedules
|
||||
description: Updates attack discovery schedule
|
||||
summary: Updates attack discovery schedule
|
||||
tags:
|
||||
- attack_discovery_schedule
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The Attack Discovery schedule's `id` value.
|
||||
schema:
|
||||
$ref: '../common_attributes.schema.yaml#/components/schemas/NonEmptyString'
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: './schedules.schema.yaml#/components/schemas/AttackDiscoveryScheduleUpdateProps'
|
||||
responses:
|
||||
200:
|
||||
description: Successful request returning the updated Attack Discovery schedule
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: './schedules.schema.yaml#/components/schemas/AttackDiscoverySchedule'
|
||||
400:
|
||||
description: Generic Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AttackDiscoveryGenericError'
|
||||
delete:
|
||||
x-codegen-enabled: true
|
||||
x-labels: [ess, serverless]
|
||||
operationId: DeleteAttackDiscoverySchedules
|
||||
description: Deletes attack discovery schedule
|
||||
summary: Deletes attack discovery schedule
|
||||
tags:
|
||||
- attack_discovery_schedule
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The Attack Discovery schedule's `id` value.
|
||||
schema:
|
||||
$ref: '../common_attributes.schema.yaml#/components/schemas/NonEmptyString'
|
||||
responses:
|
||||
200:
|
||||
description: Successful request returning the deleted Attack Discovery schedule's ID
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- id
|
||||
properties:
|
||||
id:
|
||||
$ref: '../common_attributes.schema.yaml#/components/schemas/NonEmptyString'
|
||||
400:
|
||||
description: Generic Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AttackDiscoveryGenericError'
|
||||
|
||||
/internal/elastic_assistant/attack_discovery/schedules/{id}/_enable:
|
||||
put:
|
||||
x-codegen-enabled: true
|
||||
x-labels: [ess, serverless]
|
||||
operationId: EnableAttackDiscoverySchedules
|
||||
description: Enables attack discovery schedule
|
||||
summary: Enables attack discovery schedule
|
||||
tags:
|
||||
- attack_discovery_schedule
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The Attack Discovery schedule's `id` value.
|
||||
schema:
|
||||
$ref: '../common_attributes.schema.yaml#/components/schemas/NonEmptyString'
|
||||
responses:
|
||||
200:
|
||||
description: Successful request returning an Attack Discovery schedule
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- id
|
||||
properties:
|
||||
id:
|
||||
$ref: '../common_attributes.schema.yaml#/components/schemas/NonEmptyString'
|
||||
400:
|
||||
description: Bad request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AttackDiscoveryGenericError'
|
||||
|
||||
/internal/elastic_assistant/attack_discovery/schedules/{id}/_disable:
|
||||
put:
|
||||
x-codegen-enabled: true
|
||||
x-labels: [ess, serverless]
|
||||
operationId: DisableAttackDiscoverySchedules
|
||||
description: Disables attack discovery schedule
|
||||
summary: Disables attack discovery schedule
|
||||
tags:
|
||||
- attack_discovery_schedule
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The Attack Discovery schedule's `id` value.
|
||||
schema:
|
||||
$ref: '../common_attributes.schema.yaml#/components/schemas/NonEmptyString'
|
||||
responses:
|
||||
200:
|
||||
description: Successful request returning an Attack Discovery schedule
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- id
|
||||
properties:
|
||||
id:
|
||||
$ref: '../common_attributes.schema.yaml#/components/schemas/NonEmptyString'
|
||||
400:
|
||||
description: Bad request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AttackDiscoveryGenericError'
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
* NOTICE: Do not edit this file manually.
|
||||
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
|
||||
*
|
||||
* info:
|
||||
* title: Find Knowledge Base Entries API endpoint
|
||||
* version: 2023-10-31
|
||||
*/
|
||||
|
||||
import { z } from '@kbn/zod';
|
||||
|
||||
import { AttackDiscoverySchedule } from './schedules.gen';
|
||||
|
||||
export type FindAttackDiscoverySchedulesResponse = z.infer<
|
||||
typeof FindAttackDiscoverySchedulesResponse
|
||||
>;
|
||||
export const FindAttackDiscoverySchedulesResponse = z.object({
|
||||
page: z.number(),
|
||||
perPage: z.number(),
|
||||
total: z.number(),
|
||||
data: z.array(AttackDiscoverySchedule),
|
||||
});
|
|
@ -0,0 +1,50 @@
|
|||
openapi: 3.0.0
|
||||
info:
|
||||
title: Find Knowledge Base Entries API endpoint
|
||||
version: '2023-10-31'
|
||||
paths:
|
||||
/internal/elastic_assistant/attack_discovery/schedules/_find:
|
||||
get:
|
||||
x-codegen-enabled: true
|
||||
x-labels: [ess, serverless]
|
||||
operationId: FindAttackDiscoverySchedules
|
||||
description: Finds attack discovery schedules
|
||||
summary: Finds attack discovery schedules
|
||||
tags:
|
||||
- attack_discovery_schedule
|
||||
responses:
|
||||
200:
|
||||
description: Successful response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- page
|
||||
- perPage
|
||||
- total
|
||||
- data
|
||||
properties:
|
||||
page:
|
||||
type: number
|
||||
perPage:
|
||||
type: number
|
||||
total:
|
||||
type: number
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
$ref: './schedules.schema.yaml#/components/schemas/AttackDiscoverySchedule'
|
||||
400:
|
||||
description: Generic Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
statusCode:
|
||||
type: number
|
||||
error:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
|
@ -0,0 +1,264 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
* NOTICE: Do not edit this file manually.
|
||||
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
|
||||
*
|
||||
* info:
|
||||
* title: Common Attack Discovery Schedule Types
|
||||
* version: not applicable
|
||||
*/
|
||||
|
||||
import { z } from '@kbn/zod';
|
||||
|
||||
import { ApiConfig } from '../conversations/common_attributes.gen';
|
||||
import { NonEmptyString } from '../common_attributes.gen';
|
||||
|
||||
/**
|
||||
* An attack discovery schedule params
|
||||
*/
|
||||
export type AttackDiscoveryScheduleParams = z.infer<typeof AttackDiscoveryScheduleParams>;
|
||||
export const AttackDiscoveryScheduleParams = z.object({
|
||||
/**
|
||||
* The index pattern to get alerts from
|
||||
*/
|
||||
alertsIndexPattern: z.string(),
|
||||
/**
|
||||
* LLM API configuration.
|
||||
*/
|
||||
apiConfig: ApiConfig,
|
||||
end: z.string().optional(),
|
||||
filter: z.object({}).catchall(z.unknown()).optional(),
|
||||
size: z.number(),
|
||||
start: z.string().optional(),
|
||||
});
|
||||
|
||||
export type IntervalSchedule = z.infer<typeof IntervalSchedule>;
|
||||
export const IntervalSchedule = z.object({
|
||||
/**
|
||||
* The schedule interval
|
||||
*/
|
||||
interval: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Optionally groups actions by use cases. Use `default` for alert notifications.
|
||||
*/
|
||||
export type AttackDiscoveryScheduleActionGroup = z.infer<typeof AttackDiscoveryScheduleActionGroup>;
|
||||
export const AttackDiscoveryScheduleActionGroup = z.string();
|
||||
|
||||
/**
|
||||
* The connector ID.
|
||||
*/
|
||||
export type AttackDiscoveryScheduleActionId = z.infer<typeof AttackDiscoveryScheduleActionId>;
|
||||
export const AttackDiscoveryScheduleActionId = z.string();
|
||||
|
||||
/**
|
||||
* Object containing the allowed connector fields, which varies according to the connector type.
|
||||
*/
|
||||
export type AttackDiscoveryScheduleActionParams = z.infer<
|
||||
typeof AttackDiscoveryScheduleActionParams
|
||||
>;
|
||||
export const AttackDiscoveryScheduleActionParams = z.object({}).catchall(z.unknown());
|
||||
|
||||
export type AttackDiscoveryScheduleActionAlertsFilter = z.infer<
|
||||
typeof AttackDiscoveryScheduleActionAlertsFilter
|
||||
>;
|
||||
export const AttackDiscoveryScheduleActionAlertsFilter = z.object({}).catchall(z.unknown());
|
||||
|
||||
/**
|
||||
* The condition for throttling the notification: `onActionGroupChange`, `onActiveAlert`, or `onThrottleInterval`
|
||||
*/
|
||||
export type AttackDiscoveryScheduleActionNotifyWhen = z.infer<
|
||||
typeof AttackDiscoveryScheduleActionNotifyWhen
|
||||
>;
|
||||
export const AttackDiscoveryScheduleActionNotifyWhen = z.enum([
|
||||
'onActiveAlert',
|
||||
'onThrottleInterval',
|
||||
'onActionGroupChange',
|
||||
]);
|
||||
export type AttackDiscoveryScheduleActionNotifyWhenEnum =
|
||||
typeof AttackDiscoveryScheduleActionNotifyWhen.enum;
|
||||
export const AttackDiscoveryScheduleActionNotifyWhenEnum =
|
||||
AttackDiscoveryScheduleActionNotifyWhen.enum;
|
||||
|
||||
/**
|
||||
* Defines how often schedule actions are taken. Time interval in seconds, minutes, hours, or days.
|
||||
*/
|
||||
export type AttackDiscoveryScheduleActionThrottle = z.infer<
|
||||
typeof AttackDiscoveryScheduleActionThrottle
|
||||
>;
|
||||
export const AttackDiscoveryScheduleActionThrottle = z.string().regex(/^[1-9]\d*[smhd]$/);
|
||||
|
||||
/**
|
||||
* The action frequency defines when the action runs (for example, only on schedule execution or at specific time intervals).
|
||||
*/
|
||||
export type AttackDiscoveryScheduleActionFrequency = z.infer<
|
||||
typeof AttackDiscoveryScheduleActionFrequency
|
||||
>;
|
||||
export const AttackDiscoveryScheduleActionFrequency = 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: AttackDiscoveryScheduleActionNotifyWhen,
|
||||
throttle: AttackDiscoveryScheduleActionThrottle.nullable(),
|
||||
});
|
||||
|
||||
export type AttackDiscoveryScheduleAction = z.infer<typeof AttackDiscoveryScheduleAction>;
|
||||
export const AttackDiscoveryScheduleAction = z.object({
|
||||
/**
|
||||
* The action type used for sending notifications.
|
||||
*/
|
||||
actionTypeId: z.string(),
|
||||
group: AttackDiscoveryScheduleActionGroup,
|
||||
id: AttackDiscoveryScheduleActionId,
|
||||
params: AttackDiscoveryScheduleActionParams,
|
||||
uuid: NonEmptyString.optional(),
|
||||
alertsFilter: AttackDiscoveryScheduleActionAlertsFilter.optional(),
|
||||
frequency: AttackDiscoveryScheduleActionFrequency.optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* An attack discovery schedule execution status
|
||||
*/
|
||||
export type AttackDiscoveryScheduleExecutionStatus = z.infer<
|
||||
typeof AttackDiscoveryScheduleExecutionStatus
|
||||
>;
|
||||
export const AttackDiscoveryScheduleExecutionStatus = z.enum([
|
||||
'ok',
|
||||
'active',
|
||||
'error',
|
||||
'unknown',
|
||||
'warning',
|
||||
]);
|
||||
export type AttackDiscoveryScheduleExecutionStatusEnum =
|
||||
typeof AttackDiscoveryScheduleExecutionStatus.enum;
|
||||
export const AttackDiscoveryScheduleExecutionStatusEnum =
|
||||
AttackDiscoveryScheduleExecutionStatus.enum;
|
||||
|
||||
/**
|
||||
* An attack discovery schedule execution information
|
||||
*/
|
||||
export type AttackDiscoveryScheduleExecution = z.infer<typeof AttackDiscoveryScheduleExecution>;
|
||||
export const AttackDiscoveryScheduleExecution = z.object({
|
||||
/**
|
||||
* Date of the execution
|
||||
*/
|
||||
date: z.string().datetime(),
|
||||
/**
|
||||
* Duration of the execution
|
||||
*/
|
||||
duration: z.number().optional(),
|
||||
/**
|
||||
* Status of the execution
|
||||
*/
|
||||
status: AttackDiscoveryScheduleExecutionStatus,
|
||||
message: z.string().optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* An attack discovery schedule
|
||||
*/
|
||||
export type AttackDiscoverySchedule = z.infer<typeof AttackDiscoverySchedule>;
|
||||
export const AttackDiscoverySchedule = z.object({
|
||||
/**
|
||||
* UUID of attack discovery schedule
|
||||
*/
|
||||
id: z.string(),
|
||||
/**
|
||||
* The name of the schedule
|
||||
*/
|
||||
name: z.string(),
|
||||
/**
|
||||
* The name of the user that created the schedule
|
||||
*/
|
||||
createdBy: z.string(),
|
||||
/**
|
||||
* The name of the user that updated the schedule
|
||||
*/
|
||||
updatedBy: z.string(),
|
||||
/**
|
||||
* The date the schedule was created
|
||||
*/
|
||||
createdAt: z.string().datetime(),
|
||||
/**
|
||||
* The date the schedule was updated
|
||||
*/
|
||||
updatedAt: z.string().datetime(),
|
||||
/**
|
||||
* Indicates whether the schedule is enabled
|
||||
*/
|
||||
enabled: z.boolean(),
|
||||
/**
|
||||
* The attack discovery schedule configuration parameters
|
||||
*/
|
||||
params: AttackDiscoveryScheduleParams,
|
||||
/**
|
||||
* The attack discovery schedule interval
|
||||
*/
|
||||
schedule: IntervalSchedule,
|
||||
/**
|
||||
* The attack discovery schedule actions
|
||||
*/
|
||||
actions: z.array(AttackDiscoveryScheduleAction),
|
||||
/**
|
||||
* The attack discovery schedule last execution summary
|
||||
*/
|
||||
lastExecution: AttackDiscoveryScheduleExecution.optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* An attack discovery schedule create properties
|
||||
*/
|
||||
export type AttackDiscoveryScheduleCreateProps = z.infer<typeof AttackDiscoveryScheduleCreateProps>;
|
||||
export const AttackDiscoveryScheduleCreateProps = z.object({
|
||||
/**
|
||||
* The name of the schedule
|
||||
*/
|
||||
name: z.string(),
|
||||
/**
|
||||
* Indicates whether the schedule is enabled
|
||||
*/
|
||||
enabled: z.boolean().optional(),
|
||||
/**
|
||||
* The attack discovery schedule configuration parameters
|
||||
*/
|
||||
params: AttackDiscoveryScheduleParams,
|
||||
/**
|
||||
* The attack discovery schedule interval
|
||||
*/
|
||||
schedule: IntervalSchedule,
|
||||
/**
|
||||
* The attack discovery schedule actions
|
||||
*/
|
||||
actions: z.array(AttackDiscoveryScheduleAction).optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* An attack discovery schedule update properties
|
||||
*/
|
||||
export type AttackDiscoveryScheduleUpdateProps = z.infer<typeof AttackDiscoveryScheduleUpdateProps>;
|
||||
export const AttackDiscoveryScheduleUpdateProps = z.object({
|
||||
/**
|
||||
* The name of the schedule
|
||||
*/
|
||||
name: z.string(),
|
||||
/**
|
||||
* The attack discovery schedule configuration parameters
|
||||
*/
|
||||
params: AttackDiscoveryScheduleParams,
|
||||
/**
|
||||
* The attack discovery schedule interval
|
||||
*/
|
||||
schedule: IntervalSchedule,
|
||||
/**
|
||||
* The attack discovery schedule actions
|
||||
*/
|
||||
actions: z.array(AttackDiscoveryScheduleAction),
|
||||
});
|
|
@ -0,0 +1,246 @@
|
|||
openapi: 3.0.0
|
||||
info:
|
||||
title: Common Attack Discovery Schedule Types
|
||||
version: 'not applicable'
|
||||
paths: {}
|
||||
components:
|
||||
x-codegen-enabled: true
|
||||
schemas:
|
||||
AttackDiscoverySchedule:
|
||||
type: object
|
||||
description: An attack discovery schedule
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- createdBy
|
||||
- updatedBy
|
||||
- createdAt
|
||||
- updatedAt
|
||||
- enabled
|
||||
- params
|
||||
- schedule
|
||||
- actions
|
||||
properties:
|
||||
id:
|
||||
description: UUID of attack discovery schedule
|
||||
type: string
|
||||
name:
|
||||
description: The name of the schedule
|
||||
type: string
|
||||
createdBy:
|
||||
description: The name of the user that created the schedule
|
||||
type: string
|
||||
updatedBy:
|
||||
description: The name of the user that updated the schedule
|
||||
type: string
|
||||
createdAt:
|
||||
description: The date the schedule was created
|
||||
type: string
|
||||
format: date-time
|
||||
updatedAt:
|
||||
description: The date the schedule was updated
|
||||
type: string
|
||||
format: date-time
|
||||
enabled:
|
||||
description: Indicates whether the schedule is enabled
|
||||
type: boolean
|
||||
params:
|
||||
description: The attack discovery schedule configuration parameters
|
||||
$ref: '#/components/schemas/AttackDiscoveryScheduleParams'
|
||||
schedule:
|
||||
description: The attack discovery schedule interval
|
||||
$ref: '#/components/schemas/IntervalSchedule'
|
||||
actions:
|
||||
description: The attack discovery schedule actions
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/AttackDiscoveryScheduleAction'
|
||||
lastExecution:
|
||||
description: The attack discovery schedule last execution summary
|
||||
$ref: '#/components/schemas/AttackDiscoveryScheduleExecution'
|
||||
|
||||
AttackDiscoveryScheduleParams:
|
||||
type: object
|
||||
description: An attack discovery schedule params
|
||||
required:
|
||||
- alertsIndexPattern
|
||||
- apiConfig
|
||||
- size
|
||||
properties:
|
||||
alertsIndexPattern:
|
||||
description: The index pattern to get alerts from
|
||||
type: string
|
||||
apiConfig:
|
||||
description: LLM API configuration.
|
||||
$ref: '../conversations/common_attributes.schema.yaml#/components/schemas/ApiConfig'
|
||||
end:
|
||||
type: string
|
||||
filter:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
size:
|
||||
type: number
|
||||
start:
|
||||
type: string
|
||||
|
||||
IntervalSchedule:
|
||||
type: object
|
||||
required:
|
||||
- interval
|
||||
properties:
|
||||
interval:
|
||||
description: The schedule interval
|
||||
type: string
|
||||
|
||||
AttackDiscoveryScheduleActionThrottle:
|
||||
description: Defines how often schedule actions are taken. Time interval in seconds, minutes, hours, or days.
|
||||
type: string
|
||||
pattern: '^[1-9]\d*[smhd]$' # any number except zero followed by one of the suffixes 's', 'm', 'h', 'd'
|
||||
example: '1h'
|
||||
|
||||
AttackDiscoveryScheduleActionNotifyWhen:
|
||||
type: string
|
||||
enum:
|
||||
- 'onActiveAlert'
|
||||
- 'onThrottleInterval'
|
||||
- 'onActionGroupChange'
|
||||
description: 'The condition for throttling the notification: `onActionGroupChange`, `onActiveAlert`, or `onThrottleInterval`'
|
||||
|
||||
AttackDiscoveryScheduleActionFrequency:
|
||||
type: object
|
||||
description: The action frequency defines when the action runs (for example, only on schedule 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/AttackDiscoveryScheduleActionNotifyWhen'
|
||||
throttle:
|
||||
$ref: '#/components/schemas/AttackDiscoveryScheduleActionThrottle'
|
||||
nullable: true
|
||||
required:
|
||||
- summary
|
||||
- notifyWhen
|
||||
- throttle
|
||||
|
||||
AttackDiscoveryScheduleActionAlertsFilter:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
|
||||
AttackDiscoveryScheduleActionParams:
|
||||
type: object
|
||||
description: Object containing the allowed connector fields, which varies according to the connector type.
|
||||
additionalProperties: true
|
||||
|
||||
AttackDiscoveryScheduleActionGroup:
|
||||
type: string
|
||||
description: Optionally groups actions by use cases. Use `default` for alert notifications.
|
||||
|
||||
AttackDiscoveryScheduleActionId:
|
||||
type: string
|
||||
description: The connector ID.
|
||||
|
||||
AttackDiscoveryScheduleAction:
|
||||
type: object
|
||||
properties:
|
||||
actionTypeId:
|
||||
type: string
|
||||
description: The action type used for sending notifications.
|
||||
group:
|
||||
$ref: '#/components/schemas/AttackDiscoveryScheduleActionGroup'
|
||||
id:
|
||||
$ref: '#/components/schemas/AttackDiscoveryScheduleActionId'
|
||||
params:
|
||||
$ref: '#/components/schemas/AttackDiscoveryScheduleActionParams'
|
||||
uuid:
|
||||
$ref: '../common_attributes.schema.yaml#/components/schemas/NonEmptyString'
|
||||
alertsFilter:
|
||||
$ref: '#/components/schemas/AttackDiscoveryScheduleActionAlertsFilter'
|
||||
frequency:
|
||||
$ref: '#/components/schemas/AttackDiscoveryScheduleActionFrequency'
|
||||
required:
|
||||
- actionTypeId
|
||||
- id
|
||||
- group
|
||||
- params
|
||||
|
||||
AttackDiscoveryScheduleExecutionStatus:
|
||||
type: string
|
||||
description: An attack discovery schedule execution status
|
||||
enum:
|
||||
- ok
|
||||
- active
|
||||
- error
|
||||
- unknown
|
||||
- warning
|
||||
|
||||
AttackDiscoveryScheduleExecution:
|
||||
type: object
|
||||
description: An attack discovery schedule execution information
|
||||
required:
|
||||
- date
|
||||
- status
|
||||
- lastDuration
|
||||
properties:
|
||||
date:
|
||||
description: Date of the execution
|
||||
type: string
|
||||
format: date-time
|
||||
duration:
|
||||
description: Duration of the execution
|
||||
type: number
|
||||
status:
|
||||
description: Status of the execution
|
||||
$ref: '#/components/schemas/AttackDiscoveryScheduleExecutionStatus'
|
||||
message:
|
||||
type: string
|
||||
|
||||
AttackDiscoveryScheduleCreateProps:
|
||||
type: object
|
||||
description: An attack discovery schedule create properties
|
||||
required:
|
||||
- name
|
||||
- params
|
||||
- schedule
|
||||
properties:
|
||||
name:
|
||||
description: The name of the schedule
|
||||
type: string
|
||||
enabled:
|
||||
description: Indicates whether the schedule is enabled
|
||||
type: boolean
|
||||
params:
|
||||
description: The attack discovery schedule configuration parameters
|
||||
$ref: '#/components/schemas/AttackDiscoveryScheduleParams'
|
||||
schedule:
|
||||
description: The attack discovery schedule interval
|
||||
$ref: '#/components/schemas/IntervalSchedule'
|
||||
actions:
|
||||
description: The attack discovery schedule actions
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/AttackDiscoveryScheduleAction'
|
||||
|
||||
AttackDiscoveryScheduleUpdateProps:
|
||||
type: object
|
||||
description: An attack discovery schedule update properties
|
||||
required:
|
||||
- name
|
||||
- params
|
||||
- schedule
|
||||
- actions
|
||||
properties:
|
||||
name:
|
||||
description: The name of the schedule
|
||||
type: string
|
||||
params:
|
||||
description: The attack discovery schedule configuration parameters
|
||||
$ref: '#/components/schemas/AttackDiscoveryScheduleParams'
|
||||
schedule:
|
||||
description: The attack discovery schedule interval
|
||||
$ref: '#/components/schemas/IntervalSchedule'
|
||||
actions:
|
||||
description: The attack discovery schedule actions
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/AttackDiscoveryScheduleAction'
|
|
@ -26,6 +26,9 @@ export * from './attack_discovery/common_attributes.gen';
|
|||
export * from './attack_discovery/get_attack_discovery_route.gen';
|
||||
export * from './attack_discovery/post_attack_discovery_route.gen';
|
||||
export * from './attack_discovery/cancel_attack_discovery_route.gen';
|
||||
export * from './attack_discovery/crud_attack_discovery_schedules_route.gen';
|
||||
export * from './attack_discovery/find_attack_discovery_schedules_route.gen';
|
||||
export * from './attack_discovery/schedules.gen';
|
||||
|
||||
// Defend insight Schemas
|
||||
export * from './defend_insights';
|
||||
|
|
|
@ -8,10 +8,18 @@
|
|||
import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
|
||||
import { ATTACK_DISCOVERY_SCHEDULES_ALERT_TYPE_ID } from '@kbn/elastic-assistant-common/constants';
|
||||
|
||||
import { APP_ID, ATTACK_DISCOVERY_FEATURE_ID } from '../constants';
|
||||
import { APP_ID, ATTACK_DISCOVERY_FEATURE_ID, SERVER_APP_ID } from '../constants';
|
||||
import { type BaseKibanaFeatureConfig } from '../types';
|
||||
|
||||
const alertingFeatures = [
|
||||
{
|
||||
ruleTypeId: ATTACK_DISCOVERY_SCHEDULES_ALERT_TYPE_ID,
|
||||
consumers: [SERVER_APP_ID],
|
||||
},
|
||||
];
|
||||
|
||||
export const getAttackDiscoveryBaseKibanaFeature = (): BaseKibanaFeatureConfig => ({
|
||||
id: ATTACK_DISCOVERY_FEATURE_ID,
|
||||
name: i18n.translate(
|
||||
|
@ -26,6 +34,7 @@ export const getAttackDiscoveryBaseKibanaFeature = (): BaseKibanaFeatureConfig =
|
|||
app: [ATTACK_DISCOVERY_FEATURE_ID, 'kibana'],
|
||||
catalogue: [APP_ID],
|
||||
minimumLicense: 'enterprise',
|
||||
alerting: alertingFeatures,
|
||||
privileges: {
|
||||
all: {
|
||||
api: ['elasticAssistant'],
|
||||
|
@ -35,6 +44,10 @@ export const getAttackDiscoveryBaseKibanaFeature = (): BaseKibanaFeatureConfig =
|
|||
all: [],
|
||||
read: [],
|
||||
},
|
||||
alerting: {
|
||||
rule: { all: alertingFeatures },
|
||||
alert: { all: alertingFeatures },
|
||||
},
|
||||
ui: [],
|
||||
},
|
||||
read: {
|
||||
|
@ -44,6 +57,10 @@ export const getAttackDiscoveryBaseKibanaFeature = (): BaseKibanaFeatureConfig =
|
|||
all: [],
|
||||
read: [],
|
||||
},
|
||||
alerting: {
|
||||
rule: { read: alertingFeatures },
|
||||
alert: { all: alertingFeatures },
|
||||
},
|
||||
ui: [],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
"@kbn/cases-plugin",
|
||||
"@kbn/securitysolution-rules",
|
||||
"@kbn/securitysolution-list-constants",
|
||||
"@kbn/elastic-assistant-common",
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
|
@ -16,6 +16,11 @@ export const POST_ACTIONS_CONNECTOR_EXECUTE = `${BASE_PATH}/actions/connector/{c
|
|||
export const ATTACK_DISCOVERY = `${BASE_PATH}/attack_discovery`;
|
||||
export const ATTACK_DISCOVERY_BY_CONNECTOR_ID = `${ATTACK_DISCOVERY}/{connectorId}`;
|
||||
export const ATTACK_DISCOVERY_CANCEL_BY_CONNECTOR_ID = `${ATTACK_DISCOVERY}/cancel/{connectorId}`;
|
||||
export const ATTACK_DISCOVERY_SCHEDULES = `${ATTACK_DISCOVERY}/schedules`;
|
||||
export const ATTACK_DISCOVERY_SCHEDULES_BY_ID = `${ATTACK_DISCOVERY_SCHEDULES}/{id}`;
|
||||
export const ATTACK_DISCOVERY_SCHEDULES_BY_ID_ENABLE = `${ATTACK_DISCOVERY_SCHEDULES}/{id}/_enable`;
|
||||
export const ATTACK_DISCOVERY_SCHEDULES_BY_ID_DISABLE = `${ATTACK_DISCOVERY_SCHEDULES}/{id}/_disable`;
|
||||
export const ATTACK_DISCOVERY_SCHEDULES_FIND = `${ATTACK_DISCOVERY_SCHEDULES}/_find`;
|
||||
|
||||
export const CONVERSATIONS_TABLE_MAX_PAGE_SIZE = 100;
|
||||
export const ANONYMIZATION_FIELDS_TABLE_MAX_PAGE_SIZE = 100;
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"server": true,
|
||||
"requiredPlugins": [
|
||||
"actions",
|
||||
"alerting",
|
||||
"data",
|
||||
"ml",
|
||||
"taskManager",
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* 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 { CreateRuleData } from '@kbn/alerting-plugin/server/application/rule/methods/create';
|
||||
import { UpdateRuleData } from '@kbn/alerting-plugin/server/application/rule/methods/update';
|
||||
import {
|
||||
ATTACK_DISCOVERY_SCHEDULES_ALERT_TYPE_ID,
|
||||
AttackDiscoverySchedule,
|
||||
AttackDiscoveryScheduleCreateProps,
|
||||
AttackDiscoveryScheduleParams,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
|
||||
import { SanitizedRule, SanitizedRuleAction } from '@kbn/alerting-types';
|
||||
|
||||
export const getAttackDiscoveryCreateScheduleMock = (
|
||||
enabled = true
|
||||
): CreateRuleData<AttackDiscoveryScheduleParams> => {
|
||||
return {
|
||||
name: 'Test Schedule 1',
|
||||
schedule: {
|
||||
interval: '10m',
|
||||
},
|
||||
params: {
|
||||
alertsIndexPattern: '.alerts-security.alerts-default',
|
||||
apiConfig: {
|
||||
connectorId: 'gpt-4o',
|
||||
actionTypeId: '.gen-ai',
|
||||
},
|
||||
end: 'now',
|
||||
size: 100,
|
||||
start: 'now-24h',
|
||||
},
|
||||
actions: [],
|
||||
alertTypeId: ATTACK_DISCOVERY_SCHEDULES_ALERT_TYPE_ID,
|
||||
consumer: 'siem',
|
||||
enabled,
|
||||
tags: [],
|
||||
};
|
||||
};
|
||||
|
||||
export const getAttackDiscoveryUpdateScheduleMock = (
|
||||
id: string,
|
||||
overrides: Partial<CreateRuleData<AttackDiscoveryScheduleParams>>
|
||||
): UpdateRuleData<AttackDiscoveryScheduleParams> & { id: string } => {
|
||||
return {
|
||||
id,
|
||||
...getAttackDiscoveryCreateScheduleMock(),
|
||||
...overrides,
|
||||
};
|
||||
};
|
||||
|
||||
export const getInternalAttackDiscoveryScheduleMock = (
|
||||
createParams: AttackDiscoveryScheduleCreateProps,
|
||||
overrides?: Partial<SanitizedRule<AttackDiscoveryScheduleParams>>
|
||||
): SanitizedRule<AttackDiscoveryScheduleParams> => {
|
||||
const { actions = [], params, ...restAttributes } = createParams;
|
||||
return {
|
||||
id: '54fc45a4-9d1e-4228-8fec-dbf91ea15171',
|
||||
enabled: false,
|
||||
tags: [],
|
||||
alertTypeId: 'attack-discovery',
|
||||
consumer: 'siem',
|
||||
actions: (actions as SanitizedRuleAction[]) ?? [],
|
||||
systemActions: [],
|
||||
params,
|
||||
createdBy: 'elastic',
|
||||
updatedBy: 'elastic',
|
||||
createdAt: new Date('2025-03-31T17:38:03.544Z'),
|
||||
updatedAt: new Date('2025-03-31T17:38:03.544Z'),
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
throttle: null,
|
||||
muteAll: false,
|
||||
notifyWhen: null,
|
||||
mutedInstanceIds: [],
|
||||
executionStatus: {
|
||||
status: 'pending',
|
||||
lastExecutionDate: new Date('2025-03-31T17:38:03.544Z'),
|
||||
},
|
||||
revision: 0,
|
||||
running: false,
|
||||
...restAttributes,
|
||||
...overrides,
|
||||
};
|
||||
};
|
||||
|
||||
export const getAttackDiscoveryScheduleMock = (
|
||||
overrides?: Partial<AttackDiscoverySchedule>
|
||||
): AttackDiscoverySchedule => {
|
||||
return {
|
||||
id: '31db8de1-65f2-4da2-a3e6-d15d9931817e',
|
||||
name: 'Test Schedule',
|
||||
createdBy: 'elastic',
|
||||
updatedBy: 'elastic',
|
||||
createdAt: '2025-03-31T09:57:42.194Z',
|
||||
updatedAt: '2025-03-31T09:57:42.194Z',
|
||||
enabled: false,
|
||||
params: {
|
||||
alertsIndexPattern: '.alerts-security.alerts-default',
|
||||
apiConfig: {
|
||||
connectorId: 'gpt-4o',
|
||||
actionTypeId: '.gen-ai',
|
||||
},
|
||||
end: 'now',
|
||||
size: 100,
|
||||
start: 'now-24h',
|
||||
},
|
||||
schedule: {
|
||||
interval: '10m',
|
||||
},
|
||||
actions: [],
|
||||
...overrides,
|
||||
};
|
||||
};
|
||||
|
||||
export const getInternalFindAttackDiscoverySchedulesMock = (
|
||||
schedules: Array<SanitizedRule<AttackDiscoveryScheduleParams>>
|
||||
) => {
|
||||
return {
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
total: schedules.length,
|
||||
data: schedules,
|
||||
};
|
||||
};
|
|
@ -10,11 +10,15 @@ import { AIAssistantConversationsDataClient } from '../ai_assistant_data_clients
|
|||
import { AIAssistantKnowledgeBaseDataClient } from '../ai_assistant_data_clients/knowledge_base';
|
||||
import { AIAssistantDataClient } from '../ai_assistant_data_clients';
|
||||
import { AttackDiscoveryDataClient } from '../lib/attack_discovery/persistence';
|
||||
import { AttackDiscoveryScheduleDataClient } from '../lib/attack_discovery/schedules/data_client';
|
||||
|
||||
type ConversationsDataClientContract = PublicMethodsOf<AIAssistantConversationsDataClient>;
|
||||
export type ConversationsDataClientMock = jest.Mocked<ConversationsDataClientContract>;
|
||||
type AttackDiscoveryDataClientContract = PublicMethodsOf<AttackDiscoveryDataClient>;
|
||||
export type AttackDiscoveryDataClientMock = jest.Mocked<AttackDiscoveryDataClientContract>;
|
||||
type AttackDiscoveryScheduleDataClientContract = PublicMethodsOf<AttackDiscoveryScheduleDataClient>;
|
||||
export type AttackDiscoveryScheduleDataClientMock =
|
||||
jest.Mocked<AttackDiscoveryScheduleDataClientContract>;
|
||||
type KnowledgeBaseDataClientContract = PublicMethodsOf<AIAssistantKnowledgeBaseDataClient> & {
|
||||
isSetupInProgress: AIAssistantKnowledgeBaseDataClient['isSetupInProgress'];
|
||||
};
|
||||
|
@ -57,6 +61,22 @@ export const attackDiscoveryDataClientMock: {
|
|||
create: createAttackDiscoveryDataClientMock,
|
||||
};
|
||||
|
||||
const createAttackDiscoveryScheduleDataClientMock = (): AttackDiscoveryScheduleDataClientMock => ({
|
||||
findSchedules: jest.fn(),
|
||||
getSchedule: jest.fn(),
|
||||
createSchedule: jest.fn(),
|
||||
updateSchedule: jest.fn(),
|
||||
deleteSchedule: jest.fn(),
|
||||
enableSchedule: jest.fn(),
|
||||
disableSchedule: jest.fn(),
|
||||
});
|
||||
|
||||
export const attackDiscoveryScheduleDataClientMock: {
|
||||
create: () => AttackDiscoveryScheduleDataClientMock;
|
||||
} = {
|
||||
create: createAttackDiscoveryScheduleDataClientMock,
|
||||
};
|
||||
|
||||
const createKnowledgeBaseDataClientMock = () => {
|
||||
const mocked: KnowledgeBaseDataClientMock = {
|
||||
addKnowledgeBaseDocuments: jest.fn(),
|
||||
|
|
|
@ -9,13 +9,20 @@ import {
|
|||
ATTACK_DISCOVERY,
|
||||
ATTACK_DISCOVERY_BY_CONNECTOR_ID,
|
||||
ATTACK_DISCOVERY_CANCEL_BY_CONNECTOR_ID,
|
||||
ATTACK_DISCOVERY_SCHEDULES,
|
||||
ATTACK_DISCOVERY_SCHEDULES_BY_ID,
|
||||
ATTACK_DISCOVERY_SCHEDULES_BY_ID_DISABLE,
|
||||
ATTACK_DISCOVERY_SCHEDULES_BY_ID_ENABLE,
|
||||
ATTACK_DISCOVERY_SCHEDULES_FIND,
|
||||
CAPABILITIES,
|
||||
} from '../../common/constants';
|
||||
import type {
|
||||
CreateAttackDiscoverySchedulesRequestBody,
|
||||
DefendInsightsGetRequestQuery,
|
||||
DefendInsightsPostRequestBody,
|
||||
DeleteKnowledgeBaseEntryRequestParams,
|
||||
KnowledgeBaseEntryUpdateProps,
|
||||
UpdateAttackDiscoverySchedulesRequestBody,
|
||||
UpdateKnowledgeBaseEntryRequestParams,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
import {
|
||||
|
@ -296,3 +303,57 @@ export const postDefendInsightsRequest = (body: DefendInsightsPostRequestBody) =
|
|||
path: DEFEND_INSIGHTS,
|
||||
body,
|
||||
});
|
||||
|
||||
export const findAttackDiscoverySchedulesRequest = () =>
|
||||
requestMock.create({
|
||||
method: 'get',
|
||||
path: ATTACK_DISCOVERY_SCHEDULES_FIND,
|
||||
});
|
||||
|
||||
export const createAttackDiscoverySchedulesRequest = (
|
||||
body: CreateAttackDiscoverySchedulesRequestBody
|
||||
) =>
|
||||
requestMock.create({
|
||||
method: 'post',
|
||||
path: ATTACK_DISCOVERY_SCHEDULES,
|
||||
body,
|
||||
});
|
||||
|
||||
export const deleteAttackDiscoverySchedulesRequest = (id: string) =>
|
||||
requestMock.create({
|
||||
method: 'delete',
|
||||
path: ATTACK_DISCOVERY_SCHEDULES_BY_ID,
|
||||
params: { id },
|
||||
});
|
||||
|
||||
export const getAttackDiscoverySchedulesRequest = (id: string) =>
|
||||
requestMock.create({
|
||||
method: 'get',
|
||||
path: ATTACK_DISCOVERY_SCHEDULES_BY_ID,
|
||||
params: { id },
|
||||
});
|
||||
|
||||
export const updateAttackDiscoverySchedulesRequest = (
|
||||
id: string,
|
||||
body: UpdateAttackDiscoverySchedulesRequestBody
|
||||
) =>
|
||||
requestMock.create({
|
||||
method: 'put',
|
||||
path: ATTACK_DISCOVERY_SCHEDULES_BY_ID,
|
||||
params: { id },
|
||||
body,
|
||||
});
|
||||
|
||||
export const enableAttackDiscoverySchedulesRequest = (id: string) =>
|
||||
requestMock.create({
|
||||
method: 'post',
|
||||
path: ATTACK_DISCOVERY_SCHEDULES_BY_ID_ENABLE,
|
||||
params: { id },
|
||||
});
|
||||
|
||||
export const disableAttackDiscoverySchedulesRequest = (id: string) =>
|
||||
requestMock.create({
|
||||
method: 'put',
|
||||
path: ATTACK_DISCOVERY_SCHEDULES_BY_ID_DISABLE,
|
||||
params: { id },
|
||||
});
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
import { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server';
|
||||
import {
|
||||
attackDiscoveryDataClientMock,
|
||||
attackDiscoveryScheduleDataClientMock,
|
||||
conversationsDataClientMock,
|
||||
dataClientMock,
|
||||
knowledgeBaseDataClientMock,
|
||||
|
@ -31,6 +32,7 @@ import { defaultAssistantFeatures } from '@kbn/elastic-assistant-common';
|
|||
import { AttackDiscoveryDataClient } from '../lib/attack_discovery/persistence';
|
||||
import { DefendInsightsDataClient } from '../lib/defend_insights/persistence';
|
||||
import { authenticatedUser } from './user';
|
||||
import { AttackDiscoveryScheduleDataClient } from '../lib/attack_discovery/schedules/data_client';
|
||||
|
||||
export const createMockClients = () => {
|
||||
const core = coreMock.createRequestHandlerContext();
|
||||
|
@ -49,6 +51,7 @@ export const createMockClients = () => {
|
|||
getAIAssistantKnowledgeBaseDataClient: knowledgeBaseDataClientMock.create(),
|
||||
getAIAssistantPromptsDataClient: dataClientMock.create(),
|
||||
getAttackDiscoveryDataClient: attackDiscoveryDataClientMock.create(),
|
||||
getAttackDiscoverySchedulingDataClient: attackDiscoveryScheduleDataClientMock.create(),
|
||||
getDefendInsightsDataClient: dataClientMock.create(),
|
||||
getAIAssistantAnonymizationFieldsDataClient: dataClientMock.create(),
|
||||
getSpaceId: jest.fn(),
|
||||
|
@ -129,6 +132,14 @@ const createElasticAssistantRequestContextMock = (
|
|||
() => clients.elasticAssistant.getAttackDiscoveryDataClient
|
||||
) as unknown as jest.MockInstance<Promise<AttackDiscoveryDataClient | null>, [], unknown> &
|
||||
(() => Promise<AttackDiscoveryDataClient | null>),
|
||||
getAttackDiscoverySchedulingDataClient: jest.fn(
|
||||
() => clients.elasticAssistant.getAttackDiscoverySchedulingDataClient
|
||||
) as unknown as jest.MockInstance<
|
||||
Promise<AttackDiscoveryScheduleDataClient | null>,
|
||||
[],
|
||||
unknown
|
||||
> &
|
||||
(() => Promise<AttackDiscoveryScheduleDataClient | null>),
|
||||
getDefendInsightsDataClient: jest.fn(
|
||||
() => clients.elasticAssistant.getDefendInsightsDataClient
|
||||
) as unknown as jest.MockInstance<Promise<DefendInsightsDataClient | null>, [], unknown> &
|
||||
|
|
|
@ -52,6 +52,10 @@ import { AttackDiscoveryDataClient } from '../lib/attack_discovery/persistence';
|
|||
import { DefendInsightsDataClient } from '../lib/defend_insights/persistence';
|
||||
import { createGetElserId, ensureProductDocumentationInstalled } from './helpers';
|
||||
import { hasAIAssistantLicense } from '../routes/helpers';
|
||||
import {
|
||||
AttackDiscoveryScheduleDataClient,
|
||||
CreateAttackDiscoveryScheduleDataClientParams,
|
||||
} from '../lib/attack_discovery/schedules/data_client';
|
||||
|
||||
const TOTAL_FIELDS_LIMIT = 2500;
|
||||
|
||||
|
@ -610,6 +614,14 @@ export class AIAssistantService {
|
|||
});
|
||||
}
|
||||
|
||||
public async createAttackDiscoverySchedulingDataClient(
|
||||
opts: CreateAttackDiscoveryScheduleDataClientParams
|
||||
): Promise<AttackDiscoveryScheduleDataClient | null> {
|
||||
return new AttackDiscoveryScheduleDataClient({
|
||||
rulesClient: opts.rulesClient,
|
||||
});
|
||||
}
|
||||
|
||||
public async createDefendInsightsDataClient(
|
||||
opts: CreateAIAssistantClientParams
|
||||
): Promise<DefendInsightsDataClient | null> {
|
||||
|
|
|
@ -6,12 +6,15 @@
|
|||
*/
|
||||
|
||||
import { IRuleTypeAlerts } from '@kbn/alerting-plugin/server';
|
||||
import { AttackDiscoveryAlert } from './types';
|
||||
import { SecurityAttackDiscoveryAlert } from '@kbn/alerts-as-data-utils';
|
||||
import { attackDiscoveryAlertFieldMap } from './fields';
|
||||
|
||||
export const ATTACK_DISCOVERY_ALERTS_AAD_CONFIG: IRuleTypeAlerts<AttackDiscoveryAlert> = {
|
||||
context: 'security.attack.discovery',
|
||||
export const ATTACK_DISCOVERY_ALERTS_CONTEXT = 'security.attack.discovery' as const;
|
||||
|
||||
export const ATTACK_DISCOVERY_ALERTS_AAD_CONFIG: IRuleTypeAlerts<SecurityAttackDiscoveryAlert> = {
|
||||
context: ATTACK_DISCOVERY_ALERTS_CONTEXT,
|
||||
mappings: { fieldMap: attackDiscoveryAlertFieldMap },
|
||||
isSpaceAware: true,
|
||||
shouldWrite: true,
|
||||
useEcs: true,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* 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 { rulesClientMock } from '@kbn/alerting-plugin/server/rules_client.mock';
|
||||
|
||||
import { AttackDiscoveryScheduleDataClient, AttackDiscoveryScheduleDataClientParams } from '.';
|
||||
import {
|
||||
getAttackDiscoveryCreateScheduleMock,
|
||||
getAttackDiscoveryUpdateScheduleMock,
|
||||
} from '../../../../__mocks__/attack_discovery_schedules.mock';
|
||||
|
||||
describe('AttackDiscoveryScheduleDataClient', () => {
|
||||
let scheduleDataClientParams: AttackDiscoveryScheduleDataClientParams;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
scheduleDataClientParams = {
|
||||
rulesClient: rulesClientMock.create(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('findSchedules', () => {
|
||||
it('should call `rulesClient.find` with the correct filter', async () => {
|
||||
const scheduleDataClient = new AttackDiscoveryScheduleDataClient(scheduleDataClientParams);
|
||||
await scheduleDataClient.findSchedules();
|
||||
|
||||
expect(scheduleDataClientParams.rulesClient.find).toHaveBeenCalledWith({
|
||||
options: { filter: `alert.attributes.alertTypeId: attack-discovery` },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSchedule', () => {
|
||||
it('should call `rulesClient.get` with the schedule id', async () => {
|
||||
const scheduleId = 'schedule-1';
|
||||
const scheduleDataClient = new AttackDiscoveryScheduleDataClient(scheduleDataClientParams);
|
||||
await scheduleDataClient.getSchedule(scheduleId);
|
||||
|
||||
expect(scheduleDataClientParams.rulesClient.get).toHaveBeenCalledWith({ id: scheduleId });
|
||||
});
|
||||
});
|
||||
|
||||
describe('createSchedule', () => {
|
||||
it('should call `rulesClient.create` with the schedule to create', async () => {
|
||||
const scheduleCreateData = getAttackDiscoveryCreateScheduleMock();
|
||||
|
||||
const scheduleDataClient = new AttackDiscoveryScheduleDataClient(scheduleDataClientParams);
|
||||
await scheduleDataClient.createSchedule(scheduleCreateData);
|
||||
|
||||
expect(scheduleDataClientParams.rulesClient.create).toHaveBeenCalledWith({
|
||||
data: scheduleCreateData,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateSchedule', () => {
|
||||
it('should call `rulesClient.update` with the update attributes', async () => {
|
||||
const scheduleId = 'schedule-5';
|
||||
const scheduleUpdateData = getAttackDiscoveryUpdateScheduleMock(scheduleId, {
|
||||
name: 'Updated schedule 5',
|
||||
});
|
||||
|
||||
const scheduleDataClient = new AttackDiscoveryScheduleDataClient(scheduleDataClientParams);
|
||||
await scheduleDataClient.updateSchedule(scheduleUpdateData);
|
||||
|
||||
expect(scheduleDataClientParams.rulesClient.update).toHaveBeenCalledWith({
|
||||
id: scheduleId,
|
||||
data: { ...getAttackDiscoveryCreateScheduleMock(), name: 'Updated schedule 5' },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteSchedule', () => {
|
||||
it('should call `rulesClient.delete` with the schedule id to delete', async () => {
|
||||
const scheduleId = 'schedule-3';
|
||||
|
||||
const scheduleDataClient = new AttackDiscoveryScheduleDataClient(scheduleDataClientParams);
|
||||
await scheduleDataClient.deleteSchedule({ id: scheduleId });
|
||||
|
||||
expect(scheduleDataClientParams.rulesClient.delete).toHaveBeenCalledWith({ id: scheduleId });
|
||||
});
|
||||
});
|
||||
|
||||
describe('enableSchedule', () => {
|
||||
it('should call `rulesClient.enableRule` with the schedule id to delete', async () => {
|
||||
const scheduleId = 'schedule-7';
|
||||
|
||||
const scheduleDataClient = new AttackDiscoveryScheduleDataClient(scheduleDataClientParams);
|
||||
await scheduleDataClient.enableSchedule({ id: scheduleId });
|
||||
|
||||
expect(scheduleDataClientParams.rulesClient.enableRule).toHaveBeenCalledWith({
|
||||
id: scheduleId,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('disableSchedule', () => {
|
||||
it('should call `rulesClient.disableRule` with the schedule id to delete', async () => {
|
||||
const scheduleId = 'schedule-8';
|
||||
|
||||
const scheduleDataClient = new AttackDiscoveryScheduleDataClient(scheduleDataClientParams);
|
||||
await scheduleDataClient.disableSchedule({ id: scheduleId });
|
||||
|
||||
expect(scheduleDataClientParams.rulesClient.disableRule).toHaveBeenCalledWith({
|
||||
id: scheduleId,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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 { RulesClient } from '@kbn/alerting-plugin/server';
|
||||
import { CreateRuleData } from '@kbn/alerting-plugin/server/application/rule/methods/create';
|
||||
import { UpdateRuleData } from '@kbn/alerting-plugin/server/application/rule/methods/update';
|
||||
import {
|
||||
ATTACK_DISCOVERY_SCHEDULES_ALERT_TYPE_ID,
|
||||
AttackDiscoveryScheduleParams,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
|
||||
/**
|
||||
* Params for when creating AttackDiscoveryScheduleDataClient in Request Context Factory. Useful if needing to modify
|
||||
* configuration after initial plugin start
|
||||
*/
|
||||
export interface CreateAttackDiscoveryScheduleDataClientParams {
|
||||
rulesClient: RulesClient;
|
||||
}
|
||||
|
||||
export interface AttackDiscoveryScheduleDataClientParams {
|
||||
rulesClient: RulesClient;
|
||||
}
|
||||
|
||||
export class AttackDiscoveryScheduleDataClient {
|
||||
constructor(public readonly options: AttackDiscoveryScheduleDataClientParams) {}
|
||||
|
||||
public findSchedules = async () => {
|
||||
// TODO: add filtering
|
||||
// TODO: add sorting
|
||||
// TODO: add pagination
|
||||
const rules = await this.options.rulesClient.find<AttackDiscoveryScheduleParams>({
|
||||
options: {
|
||||
filter: `alert.attributes.alertTypeId: ${ATTACK_DISCOVERY_SCHEDULES_ALERT_TYPE_ID}`,
|
||||
},
|
||||
});
|
||||
return rules;
|
||||
};
|
||||
|
||||
public getSchedule = async (id: string) => {
|
||||
const rule = await this.options.rulesClient.get<AttackDiscoveryScheduleParams>({ id });
|
||||
return rule;
|
||||
};
|
||||
|
||||
public createSchedule = async (ruleToCreate: CreateRuleData<AttackDiscoveryScheduleParams>) => {
|
||||
const rule = await this.options.rulesClient.create<AttackDiscoveryScheduleParams>({
|
||||
data: ruleToCreate,
|
||||
});
|
||||
return rule;
|
||||
};
|
||||
|
||||
public updateSchedule = async (
|
||||
ruleToUpdate: UpdateRuleData<AttackDiscoveryScheduleParams> & { id: string }
|
||||
) => {
|
||||
const { id, ...updatePayload } = ruleToUpdate;
|
||||
const rule = await this.options.rulesClient.update<AttackDiscoveryScheduleParams>({
|
||||
id,
|
||||
data: updatePayload,
|
||||
});
|
||||
return rule;
|
||||
};
|
||||
|
||||
public deleteSchedule = async (ruleToDelete: { id: string }) => {
|
||||
await this.options.rulesClient.delete(ruleToDelete);
|
||||
};
|
||||
|
||||
public enableSchedule = async (ruleToEnable: { id: string }) => {
|
||||
await this.options.rulesClient.enableRule(ruleToEnable);
|
||||
};
|
||||
|
||||
public disableSchedule = async (ruleToDisable: { id: string }) => {
|
||||
await this.options.rulesClient.disableRule(ruleToDisable);
|
||||
};
|
||||
}
|
|
@ -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 { loggerMock } from '@kbn/logging-mocks';
|
||||
import {
|
||||
ATTACK_DISCOVERY_SCHEDULES_ALERT_TYPE_ID,
|
||||
AttackDiscoveryScheduleParams,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
|
||||
import { getAttackDiscoveryScheduleType } from '.';
|
||||
import { ATTACK_DISCOVERY_ALERTS_AAD_CONFIG } from '../constants';
|
||||
|
||||
describe('getAttackDiscoveryScheduleType', () => {
|
||||
const mockLogger = loggerMock.create();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return schedule type definition', async () => {
|
||||
const scheduleType = getAttackDiscoveryScheduleType({ logger: mockLogger });
|
||||
|
||||
expect(scheduleType).toEqual(
|
||||
expect.objectContaining({
|
||||
id: ATTACK_DISCOVERY_SCHEDULES_ALERT_TYPE_ID,
|
||||
name: 'Attack Discovery Schedule',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
defaultActionGroupId: 'default',
|
||||
category: 'securitySolution',
|
||||
producer: 'assistant',
|
||||
solution: 'security',
|
||||
schemas: {
|
||||
params: { type: 'zod', schema: AttackDiscoveryScheduleParams },
|
||||
},
|
||||
actionVariables: {
|
||||
context: [{ name: 'server', description: 'the server' }],
|
||||
},
|
||||
minimumLicenseRequired: 'basic',
|
||||
isExportable: false,
|
||||
autoRecoverAlerts: false,
|
||||
alerts: ATTACK_DISCOVERY_ALERTS_AAD_CONFIG,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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 { DEFAULT_APP_CATEGORIES, Logger } from '@kbn/core/server';
|
||||
import {
|
||||
ATTACK_DISCOVERY_SCHEDULES_ALERT_TYPE_ID,
|
||||
AttackDiscoveryScheduleParams,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
|
||||
import { ATTACK_DISCOVERY_ALERTS_AAD_CONFIG } from '../constants';
|
||||
import { AttackDiscoveryExecutorOptions, AttackDiscoveryScheduleType } from '../types';
|
||||
import { attackDiscoveryScheduleExecutor } from './executor';
|
||||
|
||||
export interface GetAttackDiscoveryScheduleParams {
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
export const getAttackDiscoveryScheduleType = ({
|
||||
logger,
|
||||
}: GetAttackDiscoveryScheduleParams): AttackDiscoveryScheduleType => {
|
||||
return {
|
||||
id: ATTACK_DISCOVERY_SCHEDULES_ALERT_TYPE_ID,
|
||||
name: 'Attack Discovery Schedule',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
defaultActionGroupId: 'default',
|
||||
category: DEFAULT_APP_CATEGORIES.security.id,
|
||||
producer: 'assistant',
|
||||
solution: 'security',
|
||||
validate: {
|
||||
params: {
|
||||
validate: (object: unknown) => {
|
||||
return AttackDiscoveryScheduleParams.parse(object);
|
||||
},
|
||||
},
|
||||
},
|
||||
schemas: {
|
||||
params: { type: 'zod', schema: AttackDiscoveryScheduleParams },
|
||||
},
|
||||
actionVariables: {
|
||||
context: [{ name: 'server', description: 'the server' }],
|
||||
},
|
||||
minimumLicenseRequired: 'basic',
|
||||
isExportable: false,
|
||||
autoRecoverAlerts: false,
|
||||
alerts: ATTACK_DISCOVERY_ALERTS_AAD_CONFIG,
|
||||
executor: async (options: AttackDiscoveryExecutorOptions) => {
|
||||
return attackDiscoveryScheduleExecutor({
|
||||
options,
|
||||
logger,
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { loggerMock } from '@kbn/logging-mocks';
|
||||
import { AlertsClientError, RuleExecutorOptions } from '@kbn/alerting-plugin/server';
|
||||
|
||||
import { attackDiscoveryScheduleExecutor } from './executor';
|
||||
|
||||
describe('attackDiscoveryScheduleExecutor', () => {
|
||||
const mockLogger = loggerMock.create();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return execution state', async () => {
|
||||
const results = await attackDiscoveryScheduleExecutor({
|
||||
logger: mockLogger,
|
||||
options: { services: { alertsClient: {} } } as RuleExecutorOptions,
|
||||
});
|
||||
|
||||
expect(results).toEqual({ state: {} });
|
||||
});
|
||||
|
||||
it('should throw `AlertsClientError` error if actions client is not available', async () => {
|
||||
const attackDiscoveryScheduleExecutorPromise = attackDiscoveryScheduleExecutor({
|
||||
logger: mockLogger,
|
||||
options: { services: {} } as RuleExecutorOptions,
|
||||
});
|
||||
|
||||
await expect(attackDiscoveryScheduleExecutorPromise).rejects.toBeInstanceOf(AlertsClientError);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 { Logger } from '@kbn/core/server';
|
||||
import { AlertsClientError } from '@kbn/alerting-plugin/server';
|
||||
import { ATTACK_DISCOVERY_SCHEDULES_ALERT_TYPE_ID } from '@kbn/elastic-assistant-common';
|
||||
|
||||
import { AttackDiscoveryExecutorOptions } from '../types';
|
||||
|
||||
export interface AttackDiscoveryScheduleExecutorParams {
|
||||
options: AttackDiscoveryExecutorOptions;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
export const attackDiscoveryScheduleExecutor = async ({
|
||||
options,
|
||||
logger,
|
||||
}: AttackDiscoveryScheduleExecutorParams) => {
|
||||
const { services } = options;
|
||||
const { alertsClient } = services;
|
||||
if (!alertsClient) {
|
||||
throw new AlertsClientError();
|
||||
}
|
||||
|
||||
// TODO: implement "attack discovery schedule" executor handler
|
||||
|
||||
logger.info(
|
||||
`Attack discovery schedule "[${ATTACK_DISCOVERY_SCHEDULES_ALERT_TYPE_ID}]" executing...`
|
||||
);
|
||||
|
||||
return { state: {} };
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './definition';
|
|
@ -5,74 +5,26 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { DefaultAlert } from '@kbn/alerts-as-data-utils';
|
||||
import { RuleExecutorOptions, RuleType, RuleTypeState } from '@kbn/alerting-plugin/server';
|
||||
import {
|
||||
ALERT_ATTACK_DISCOVERY_ALERTS_CONTEXT_COUNT,
|
||||
ALERT_ATTACK_DISCOVERY_ALERT_IDS,
|
||||
ALERT_ATTACK_DISCOVERY_API_CONFIG,
|
||||
ALERT_ATTACK_DISCOVERY_DETAILS_MARKDOWN,
|
||||
ALERT_ATTACK_DISCOVERY_DETAILS_MARKDOWN_WITH_REPLACEMENTS,
|
||||
ALERT_ATTACK_DISCOVERY_ENTITY_SUMMARY_MARKDOWN,
|
||||
ALERT_ATTACK_DISCOVERY_ENTITY_SUMMARY_MARKDOWN_WITH_REPLACEMENTS,
|
||||
ALERT_ATTACK_DISCOVERY_MITRE_ATTACK_TACTICS,
|
||||
ALERT_ATTACK_DISCOVERY_REPLACEMENTS,
|
||||
ALERT_ATTACK_DISCOVERY_SUMMARY_MARKDOWN,
|
||||
ALERT_ATTACK_DISCOVERY_SUMMARY_MARKDOWN_WITH_REPLACEMENTS,
|
||||
ALERT_ATTACK_DISCOVERY_TITLE,
|
||||
ALERT_ATTACK_DISCOVERY_TITLE_WITH_REPLACEMENTS,
|
||||
ALERT_ATTACK_DISCOVERY_USERS,
|
||||
ALERT_ATTACK_DISCOVERY_USER_ID,
|
||||
ALERT_RISK_SCORE,
|
||||
} from './fields';
|
||||
|
||||
export type AttackDiscoveryAlert = DefaultAlert & {
|
||||
[ALERT_ATTACK_DISCOVERY_ALERTS_CONTEXT_COUNT]?: number;
|
||||
[ALERT_ATTACK_DISCOVERY_ALERT_IDS]: string[];
|
||||
[ALERT_ATTACK_DISCOVERY_API_CONFIG]: {
|
||||
action_type_id: string;
|
||||
connector_id: string;
|
||||
model?: string;
|
||||
name: string;
|
||||
provider?: string;
|
||||
};
|
||||
[ALERT_ATTACK_DISCOVERY_DETAILS_MARKDOWN]: string;
|
||||
[ALERT_ATTACK_DISCOVERY_DETAILS_MARKDOWN_WITH_REPLACEMENTS]: string;
|
||||
[ALERT_ATTACK_DISCOVERY_ENTITY_SUMMARY_MARKDOWN]?: string;
|
||||
[ALERT_ATTACK_DISCOVERY_ENTITY_SUMMARY_MARKDOWN_WITH_REPLACEMENTS]?: string;
|
||||
[ALERT_ATTACK_DISCOVERY_MITRE_ATTACK_TACTICS]?: string[];
|
||||
[ALERT_ATTACK_DISCOVERY_REPLACEMENTS]?: Array<{
|
||||
value?: string;
|
||||
uuid?: string;
|
||||
}>;
|
||||
[ALERT_ATTACK_DISCOVERY_SUMMARY_MARKDOWN]: string;
|
||||
[ALERT_ATTACK_DISCOVERY_SUMMARY_MARKDOWN_WITH_REPLACEMENTS]: string;
|
||||
[ALERT_ATTACK_DISCOVERY_TITLE]: string;
|
||||
[ALERT_ATTACK_DISCOVERY_TITLE_WITH_REPLACEMENTS]: string;
|
||||
[ALERT_ATTACK_DISCOVERY_USER_ID]?: string;
|
||||
[ALERT_ATTACK_DISCOVERY_USERS]: Array<{
|
||||
id?: string;
|
||||
name?: string;
|
||||
}>;
|
||||
[ALERT_RISK_SCORE]?: number;
|
||||
};
|
||||
import { SecurityAttackDiscoveryAlert } from '@kbn/alerts-as-data-utils';
|
||||
import { AttackDiscoveryScheduleParams } from '@kbn/elastic-assistant-common';
|
||||
|
||||
export type AttackDiscoveryExecutorOptions = RuleExecutorOptions<
|
||||
{},
|
||||
AttackDiscoveryScheduleParams,
|
||||
RuleTypeState,
|
||||
{},
|
||||
{},
|
||||
'default',
|
||||
AttackDiscoveryAlert
|
||||
SecurityAttackDiscoveryAlert
|
||||
>;
|
||||
|
||||
export type AttackDiscoveryScheduleType = RuleType<
|
||||
{},
|
||||
AttackDiscoveryScheduleParams,
|
||||
never,
|
||||
RuleTypeState,
|
||||
{},
|
||||
{},
|
||||
'default',
|
||||
never,
|
||||
AttackDiscoveryAlert
|
||||
SecurityAttackDiscoveryAlert
|
||||
>;
|
||||
|
|
|
@ -29,6 +29,7 @@ import { PLUGIN_ID } from '../common/constants';
|
|||
import { registerRoutes } from './routes/register_routes';
|
||||
import { CallbackIds, appContextService } from './services/app_context';
|
||||
import { createGetElserId, removeLegacyQuickPrompt } from './ai_assistant_service/helpers';
|
||||
import { getAttackDiscoveryScheduleType } from './lib/attack_discovery/schedules/register_schedule/definition';
|
||||
|
||||
export class ElasticAssistantPlugin
|
||||
implements
|
||||
|
@ -104,7 +105,14 @@ export class ElasticAssistantPlugin
|
|||
featureFlags.getBooleanValue(ATTACK_DISCOVERY_SCHEDULES_ENABLED_FEATURE_FLAG, false),
|
||||
// add more feature flags here
|
||||
]).then(([assistantAttackDiscoverySchedulingEnabled]) => {
|
||||
// TODO: use `assistantAttackDiscoverySchedulingEnabled` to conditionally create alerts index
|
||||
if (assistantAttackDiscoverySchedulingEnabled) {
|
||||
// Register Attack Discovery Schedule type
|
||||
plugins.alerting.registerType(
|
||||
getAttackDiscoveryScheduleType({
|
||||
logger: this.logger,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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 { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks';
|
||||
import { CreateAttackDiscoverySchedulesRequestBody } from '@kbn/elastic-assistant-common';
|
||||
import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants';
|
||||
|
||||
import { createAttackDiscoverySchedulesRoute } from './create';
|
||||
import { serverMock } from '../../../__mocks__/server';
|
||||
import { requestContextMock } from '../../../__mocks__/request_context';
|
||||
import { createAttackDiscoverySchedulesRequest } from '../../../__mocks__/request';
|
||||
import { getInternalAttackDiscoveryScheduleMock } from '../../../__mocks__/attack_discovery_schedules.mock';
|
||||
import { AttackDiscoveryScheduleDataClient } from '../../../lib/attack_discovery/schedules/data_client';
|
||||
|
||||
const { clients, context } = requestContextMock.createTools();
|
||||
const server: ReturnType<typeof serverMock.create> = serverMock.create();
|
||||
clients.core.elasticsearch.client = elasticsearchServiceMock.createScopedClusterClient();
|
||||
|
||||
const createAttackDiscoverySchedule = jest.fn();
|
||||
const mockSchedulingDataClient = {
|
||||
findSchedules: jest.fn(),
|
||||
getSchedule: jest.fn(),
|
||||
createSchedule: createAttackDiscoverySchedule,
|
||||
updateSchedule: jest.fn(),
|
||||
deleteSchedule: jest.fn(),
|
||||
enableSchedule: jest.fn(),
|
||||
disableSchedule: jest.fn(),
|
||||
} as unknown as AttackDiscoveryScheduleDataClient;
|
||||
const mockApiConfig = {
|
||||
connectorId: 'connector-id',
|
||||
actionTypeId: '.bedrock',
|
||||
model: 'model',
|
||||
provider: OpenAiProviderType.OpenAi,
|
||||
};
|
||||
const mockRequestBody: CreateAttackDiscoverySchedulesRequestBody = {
|
||||
name: 'Test Schedule 1',
|
||||
schedule: {
|
||||
interval: '10m',
|
||||
},
|
||||
params: {
|
||||
alertsIndexPattern: '.alerts-security.alerts-default',
|
||||
apiConfig: mockApiConfig,
|
||||
end: 'now',
|
||||
size: 25,
|
||||
start: 'now-24h',
|
||||
},
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
describe('createAttackDiscoverySchedulesRoute', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
context.elasticAssistant.getAttackDiscoverySchedulingDataClient.mockResolvedValue(
|
||||
mockSchedulingDataClient
|
||||
);
|
||||
context.core.featureFlags.getBooleanValue.mockResolvedValue(true);
|
||||
createAttackDiscoverySchedulesRoute(server.router);
|
||||
createAttackDiscoverySchedule.mockResolvedValue(
|
||||
getInternalAttackDiscoveryScheduleMock(mockRequestBody)
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle successful request', async () => {
|
||||
const response = await server.inject(
|
||||
createAttackDiscoverySchedulesRequest(mockRequestBody),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual(expect.objectContaining({ ...mockRequestBody }));
|
||||
});
|
||||
|
||||
it('should handle missing data client', async () => {
|
||||
context.elasticAssistant.getAttackDiscoverySchedulingDataClient.mockResolvedValue(null);
|
||||
const response = await server.inject(
|
||||
createAttackDiscoverySchedulesRequest(mockRequestBody),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
|
||||
expect(response.status).toEqual(500);
|
||||
expect(response.body).toEqual({
|
||||
message: 'Attack discovery data client not initialized',
|
||||
status_code: 500,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle `dataClient.createSchedule` error', async () => {
|
||||
(createAttackDiscoverySchedule as jest.Mock).mockRejectedValue(new Error('Oh no!'));
|
||||
const response = await server.inject(
|
||||
createAttackDiscoverySchedulesRequest(mockRequestBody),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(500);
|
||||
expect(response.body).toEqual({
|
||||
message: {
|
||||
error: 'Oh no!',
|
||||
success: false,
|
||||
},
|
||||
status_code: 500,
|
||||
});
|
||||
});
|
||||
|
||||
describe('Disabled feature flag', () => {
|
||||
it('should return a 404 if scheduling feature is not registered', async () => {
|
||||
context.core.featureFlags.getBooleanValue.mockResolvedValue(false);
|
||||
const response = await server.inject(
|
||||
createAttackDiscoverySchedulesRequest(mockRequestBody),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(404);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* 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 { IKibanaResponse, IRouter, Logger } from '@kbn/core/server';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
|
||||
import {
|
||||
API_VERSIONS,
|
||||
ATTACK_DISCOVERY_SCHEDULES_ALERT_TYPE_ID,
|
||||
CreateAttackDiscoverySchedulesRequestBody,
|
||||
CreateAttackDiscoverySchedulesResponse,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
|
||||
import { buildResponse } from '../../../lib/build_response';
|
||||
import { ATTACK_DISCOVERY_SCHEDULES } from '../../../../common/constants';
|
||||
import { ElasticAssistantRequestHandlerContext } from '../../../types';
|
||||
import { convertAlertingRuleToSchedule } from './utils/convert_alerting_rule_to_schedule';
|
||||
import { performChecks } from '../../helpers';
|
||||
import { isFeatureAvailable } from './utils/is_feature_available';
|
||||
|
||||
export const createAttackDiscoverySchedulesRoute = (
|
||||
router: IRouter<ElasticAssistantRequestHandlerContext>
|
||||
): void => {
|
||||
router.versioned
|
||||
.post({
|
||||
access: 'internal',
|
||||
path: ATTACK_DISCOVERY_SCHEDULES,
|
||||
security: {
|
||||
authz: {
|
||||
requiredPrivileges: ['elasticAssistant'],
|
||||
},
|
||||
},
|
||||
})
|
||||
.addVersion(
|
||||
{
|
||||
version: API_VERSIONS.internal.v1,
|
||||
validate: {
|
||||
request: {
|
||||
body: buildRouteValidationWithZod(CreateAttackDiscoverySchedulesRequestBody),
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
body: {
|
||||
custom: buildRouteValidationWithZod(CreateAttackDiscoverySchedulesResponse),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (
|
||||
context,
|
||||
request,
|
||||
response
|
||||
): Promise<IKibanaResponse<CreateAttackDiscoverySchedulesResponse>> => {
|
||||
const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
|
||||
const resp = buildResponse(response);
|
||||
const assistantContext = await context.elasticAssistant;
|
||||
const logger: Logger = assistantContext.logger;
|
||||
|
||||
// Check if scheduling feature available
|
||||
if (!(await isFeatureAvailable(ctx))) {
|
||||
return response.notFound();
|
||||
}
|
||||
|
||||
// Perform license and authenticated user
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
});
|
||||
|
||||
if (!checkResponse.isSuccess) {
|
||||
return checkResponse.response;
|
||||
}
|
||||
|
||||
const { actions = [], enabled = false, ...restScheduleAttributes } = request.body;
|
||||
|
||||
try {
|
||||
const dataClient = await assistantContext.getAttackDiscoverySchedulingDataClient();
|
||||
if (!dataClient) {
|
||||
return resp.error({
|
||||
body: `Attack discovery data client not initialized`,
|
||||
statusCode: 500,
|
||||
});
|
||||
}
|
||||
|
||||
const alertingRule = await dataClient.createSchedule({
|
||||
actions,
|
||||
alertTypeId: ATTACK_DISCOVERY_SCHEDULES_ALERT_TYPE_ID,
|
||||
consumer: 'siem',
|
||||
enabled,
|
||||
tags: [],
|
||||
...restScheduleAttributes,
|
||||
});
|
||||
const schedule = convertAlertingRuleToSchedule(alertingRule);
|
||||
|
||||
return response.ok({ body: schedule });
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
const error = transformError(err);
|
||||
|
||||
return resp.error({
|
||||
body: { success: false, error: error.message },
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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 { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks';
|
||||
|
||||
import { deleteAttackDiscoverySchedulesRoute } from './delete';
|
||||
import { serverMock } from '../../../__mocks__/server';
|
||||
import { requestContextMock } from '../../../__mocks__/request_context';
|
||||
import { deleteAttackDiscoverySchedulesRequest } from '../../../__mocks__/request';
|
||||
import { AttackDiscoveryScheduleDataClient } from '../../../lib/attack_discovery/schedules/data_client';
|
||||
|
||||
const { clients, context } = requestContextMock.createTools();
|
||||
const server: ReturnType<typeof serverMock.create> = serverMock.create();
|
||||
clients.core.elasticsearch.client = elasticsearchServiceMock.createScopedClusterClient();
|
||||
|
||||
const deleteAttackDiscoverySchedule = jest.fn();
|
||||
const mockSchedulingDataClient = {
|
||||
findSchedules: jest.fn(),
|
||||
getSchedule: jest.fn(),
|
||||
createSchedule: jest.fn(),
|
||||
updateSchedule: jest.fn(),
|
||||
deleteSchedule: deleteAttackDiscoverySchedule,
|
||||
enableSchedule: jest.fn(),
|
||||
disableSchedule: jest.fn(),
|
||||
} as unknown as AttackDiscoveryScheduleDataClient;
|
||||
|
||||
describe('deleteAttackDiscoverySchedulesRoute', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
context.elasticAssistant.getAttackDiscoverySchedulingDataClient.mockResolvedValue(
|
||||
mockSchedulingDataClient
|
||||
);
|
||||
context.core.featureFlags.getBooleanValue.mockResolvedValue(true);
|
||||
deleteAttackDiscoverySchedulesRoute(server.router);
|
||||
});
|
||||
|
||||
it('should handle successful request', async () => {
|
||||
const response = await server.inject(
|
||||
deleteAttackDiscoverySchedulesRequest('schedule-1'),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual({ id: 'schedule-1' });
|
||||
});
|
||||
|
||||
it('should handle missing data client', async () => {
|
||||
context.elasticAssistant.getAttackDiscoverySchedulingDataClient.mockResolvedValue(null);
|
||||
const response = await server.inject(
|
||||
deleteAttackDiscoverySchedulesRequest('schedule-2'),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
|
||||
expect(response.status).toEqual(500);
|
||||
expect(response.body).toEqual({
|
||||
message: 'Attack discovery data client not initialized',
|
||||
status_code: 500,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle `dataClient.deleteSchedule` error', async () => {
|
||||
(deleteAttackDiscoverySchedule as jest.Mock).mockRejectedValue(new Error('Oh no!'));
|
||||
const response = await server.inject(
|
||||
deleteAttackDiscoverySchedulesRequest('schedule-3'),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(500);
|
||||
expect(response.body).toEqual({
|
||||
message: {
|
||||
error: 'Oh no!',
|
||||
success: false,
|
||||
},
|
||||
status_code: 500,
|
||||
});
|
||||
});
|
||||
|
||||
describe('Disabled feature flag', () => {
|
||||
it('should return a 404 if scheduling feature is not registered', async () => {
|
||||
context.core.featureFlags.getBooleanValue.mockResolvedValue(false);
|
||||
const response = await server.inject(
|
||||
deleteAttackDiscoverySchedulesRequest('schedule-4'),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(404);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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 { IKibanaResponse, IRouter, Logger } from '@kbn/core/server';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
|
||||
|
||||
import {
|
||||
API_VERSIONS,
|
||||
DeleteAttackDiscoverySchedulesRequestParams,
|
||||
DeleteAttackDiscoverySchedulesResponse,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
import { buildResponse } from '../../../lib/build_response';
|
||||
import { ATTACK_DISCOVERY_SCHEDULES_BY_ID } from '../../../../common/constants';
|
||||
import { ElasticAssistantRequestHandlerContext } from '../../../types';
|
||||
import { performChecks } from '../../helpers';
|
||||
import { isFeatureAvailable } from './utils/is_feature_available';
|
||||
|
||||
export const deleteAttackDiscoverySchedulesRoute = (
|
||||
router: IRouter<ElasticAssistantRequestHandlerContext>
|
||||
): void => {
|
||||
router.versioned
|
||||
.delete({
|
||||
access: 'internal',
|
||||
path: ATTACK_DISCOVERY_SCHEDULES_BY_ID,
|
||||
security: {
|
||||
authz: {
|
||||
requiredPrivileges: ['elasticAssistant'],
|
||||
},
|
||||
},
|
||||
})
|
||||
.addVersion(
|
||||
{
|
||||
version: API_VERSIONS.internal.v1,
|
||||
validate: {
|
||||
request: {
|
||||
params: buildRouteValidationWithZod(DeleteAttackDiscoverySchedulesRequestParams),
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
body: {
|
||||
custom: buildRouteValidationWithZod(DeleteAttackDiscoverySchedulesResponse),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (
|
||||
context,
|
||||
request,
|
||||
response
|
||||
): Promise<IKibanaResponse<DeleteAttackDiscoverySchedulesResponse>> => {
|
||||
const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
|
||||
const resp = buildResponse(response);
|
||||
const assistantContext = await context.elasticAssistant;
|
||||
const logger: Logger = assistantContext.logger;
|
||||
|
||||
// Check if scheduling feature available
|
||||
if (!(await isFeatureAvailable(ctx))) {
|
||||
return response.notFound();
|
||||
}
|
||||
|
||||
// Perform license and authenticated user
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
});
|
||||
|
||||
if (!checkResponse.isSuccess) {
|
||||
return checkResponse.response;
|
||||
}
|
||||
|
||||
const { id } = request.params;
|
||||
|
||||
try {
|
||||
const dataClient = await assistantContext.getAttackDiscoverySchedulingDataClient();
|
||||
if (!dataClient) {
|
||||
return resp.error({
|
||||
body: `Attack discovery data client not initialized`,
|
||||
statusCode: 500,
|
||||
});
|
||||
}
|
||||
|
||||
await dataClient.deleteSchedule({ id });
|
||||
|
||||
return response.ok({ body: { id } });
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
const error = transformError(err);
|
||||
|
||||
return resp.error({
|
||||
body: { success: false, error: error.message },
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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 { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks';
|
||||
|
||||
import { disableAttackDiscoverySchedulesRoute } from './disable';
|
||||
import { serverMock } from '../../../__mocks__/server';
|
||||
import { requestContextMock } from '../../../__mocks__/request_context';
|
||||
import { disableAttackDiscoverySchedulesRequest } from '../../../__mocks__/request';
|
||||
import { AttackDiscoveryScheduleDataClient } from '../../../lib/attack_discovery/schedules/data_client';
|
||||
|
||||
const { clients, context } = requestContextMock.createTools();
|
||||
const server: ReturnType<typeof serverMock.create> = serverMock.create();
|
||||
clients.core.elasticsearch.client = elasticsearchServiceMock.createScopedClusterClient();
|
||||
|
||||
const disableAttackDiscoverySchedule = jest.fn();
|
||||
const mockSchedulingDataClient = {
|
||||
findSchedules: jest.fn(),
|
||||
getSchedule: jest.fn(),
|
||||
createSchedule: jest.fn(),
|
||||
updateSchedule: jest.fn(),
|
||||
deleteSchedule: jest.fn(),
|
||||
enableSchedule: jest.fn(),
|
||||
disableSchedule: disableAttackDiscoverySchedule,
|
||||
} as unknown as AttackDiscoveryScheduleDataClient;
|
||||
|
||||
describe('disableAttackDiscoverySchedulesRoute', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
context.elasticAssistant.getAttackDiscoverySchedulingDataClient.mockResolvedValue(
|
||||
mockSchedulingDataClient
|
||||
);
|
||||
context.core.featureFlags.getBooleanValue.mockResolvedValue(true);
|
||||
disableAttackDiscoverySchedulesRoute(server.router);
|
||||
});
|
||||
|
||||
it('should handle successful request', async () => {
|
||||
const response = await server.inject(
|
||||
disableAttackDiscoverySchedulesRequest('schedule-1'),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual({ id: 'schedule-1' });
|
||||
});
|
||||
|
||||
it('should handle missing data client', async () => {
|
||||
context.elasticAssistant.getAttackDiscoverySchedulingDataClient.mockResolvedValue(null);
|
||||
const response = await server.inject(
|
||||
disableAttackDiscoverySchedulesRequest('schedule-2'),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
|
||||
expect(response.status).toEqual(500);
|
||||
expect(response.body).toEqual({
|
||||
message: 'Attack discovery data client not initialized',
|
||||
status_code: 500,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle `dataClient.disableSchedule` error', async () => {
|
||||
(disableAttackDiscoverySchedule as jest.Mock).mockRejectedValue(new Error('Oh no!'));
|
||||
const response = await server.inject(
|
||||
disableAttackDiscoverySchedulesRequest('schedule-3'),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(500);
|
||||
expect(response.body).toEqual({
|
||||
message: {
|
||||
error: 'Oh no!',
|
||||
success: false,
|
||||
},
|
||||
status_code: 500,
|
||||
});
|
||||
});
|
||||
|
||||
describe('Disabled feature flag', () => {
|
||||
it('should return a 404 if scheduling feature is not registered', async () => {
|
||||
context.core.featureFlags.getBooleanValue.mockResolvedValue(false);
|
||||
const response = await server.inject(
|
||||
disableAttackDiscoverySchedulesRequest('schedule-4'),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(404);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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 { IKibanaResponse, IRouter, Logger } from '@kbn/core/server';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
|
||||
|
||||
import {
|
||||
API_VERSIONS,
|
||||
DisableAttackDiscoverySchedulesRequestParams,
|
||||
DisableAttackDiscoverySchedulesResponse,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
import { buildResponse } from '../../../lib/build_response';
|
||||
import { ATTACK_DISCOVERY_SCHEDULES_BY_ID_DISABLE } from '../../../../common/constants';
|
||||
import { ElasticAssistantRequestHandlerContext } from '../../../types';
|
||||
import { performChecks } from '../../helpers';
|
||||
import { isFeatureAvailable } from './utils/is_feature_available';
|
||||
|
||||
export const disableAttackDiscoverySchedulesRoute = (
|
||||
router: IRouter<ElasticAssistantRequestHandlerContext>
|
||||
): void => {
|
||||
router.versioned
|
||||
.post({
|
||||
access: 'internal',
|
||||
path: ATTACK_DISCOVERY_SCHEDULES_BY_ID_DISABLE,
|
||||
security: {
|
||||
authz: {
|
||||
requiredPrivileges: ['elasticAssistant'],
|
||||
},
|
||||
},
|
||||
})
|
||||
.addVersion(
|
||||
{
|
||||
version: API_VERSIONS.internal.v1,
|
||||
validate: {
|
||||
request: {
|
||||
params: buildRouteValidationWithZod(DisableAttackDiscoverySchedulesRequestParams),
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
body: {
|
||||
custom: buildRouteValidationWithZod(DisableAttackDiscoverySchedulesResponse),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (
|
||||
context,
|
||||
request,
|
||||
response
|
||||
): Promise<IKibanaResponse<DisableAttackDiscoverySchedulesResponse>> => {
|
||||
const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
|
||||
const resp = buildResponse(response);
|
||||
const assistantContext = await context.elasticAssistant;
|
||||
const logger: Logger = assistantContext.logger;
|
||||
|
||||
// Check if scheduling feature available
|
||||
if (!(await isFeatureAvailable(ctx))) {
|
||||
return response.notFound();
|
||||
}
|
||||
|
||||
// Perform license and authenticated user
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
});
|
||||
|
||||
if (!checkResponse.isSuccess) {
|
||||
return checkResponse.response;
|
||||
}
|
||||
|
||||
const { id } = request.params;
|
||||
|
||||
try {
|
||||
const dataClient = await assistantContext.getAttackDiscoverySchedulingDataClient();
|
||||
if (!dataClient) {
|
||||
return resp.error({
|
||||
body: `Attack discovery data client not initialized`,
|
||||
statusCode: 500,
|
||||
});
|
||||
}
|
||||
|
||||
await dataClient.disableSchedule({ id });
|
||||
|
||||
return response.ok({ body: { id } });
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
const error = transformError(err);
|
||||
|
||||
return resp.error({
|
||||
body: { success: false, error: error.message },
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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 { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks';
|
||||
|
||||
import { enableAttackDiscoverySchedulesRoute } from './enable';
|
||||
import { serverMock } from '../../../__mocks__/server';
|
||||
import { requestContextMock } from '../../../__mocks__/request_context';
|
||||
import { enableAttackDiscoverySchedulesRequest } from '../../../__mocks__/request';
|
||||
import { AttackDiscoveryScheduleDataClient } from '../../../lib/attack_discovery/schedules/data_client';
|
||||
|
||||
const { clients, context } = requestContextMock.createTools();
|
||||
const server: ReturnType<typeof serverMock.create> = serverMock.create();
|
||||
clients.core.elasticsearch.client = elasticsearchServiceMock.createScopedClusterClient();
|
||||
|
||||
const enableAttackDiscoverySchedule = jest.fn();
|
||||
const mockSchedulingDataClient = {
|
||||
findSchedules: jest.fn(),
|
||||
getSchedule: jest.fn(),
|
||||
createSchedule: jest.fn(),
|
||||
updateSchedule: jest.fn(),
|
||||
deleteSchedule: jest.fn(),
|
||||
enableSchedule: enableAttackDiscoverySchedule,
|
||||
disableSchedule: jest.fn(),
|
||||
} as unknown as AttackDiscoveryScheduleDataClient;
|
||||
|
||||
describe('enableAttackDiscoverySchedulesRoute', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
context.elasticAssistant.getAttackDiscoverySchedulingDataClient.mockResolvedValue(
|
||||
mockSchedulingDataClient
|
||||
);
|
||||
context.core.featureFlags.getBooleanValue.mockResolvedValue(true);
|
||||
enableAttackDiscoverySchedulesRoute(server.router);
|
||||
});
|
||||
|
||||
it('should handle successful request', async () => {
|
||||
const response = await server.inject(
|
||||
enableAttackDiscoverySchedulesRequest('schedule-1'),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual({ id: 'schedule-1' });
|
||||
});
|
||||
|
||||
it('should handle missing data client', async () => {
|
||||
context.elasticAssistant.getAttackDiscoverySchedulingDataClient.mockResolvedValue(null);
|
||||
const response = await server.inject(
|
||||
enableAttackDiscoverySchedulesRequest('schedule-2'),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
|
||||
expect(response.status).toEqual(500);
|
||||
expect(response.body).toEqual({
|
||||
message: 'Attack discovery data client not initialized',
|
||||
status_code: 500,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle `dataClient.enableSchedule` error', async () => {
|
||||
(enableAttackDiscoverySchedule as jest.Mock).mockRejectedValue(new Error('Oh no!'));
|
||||
const response = await server.inject(
|
||||
enableAttackDiscoverySchedulesRequest('schedule-3'),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(500);
|
||||
expect(response.body).toEqual({
|
||||
message: {
|
||||
error: 'Oh no!',
|
||||
success: false,
|
||||
},
|
||||
status_code: 500,
|
||||
});
|
||||
});
|
||||
|
||||
describe('Disabled feature flag', () => {
|
||||
it('should return a 404 if scheduling feature is not registered', async () => {
|
||||
context.core.featureFlags.getBooleanValue.mockResolvedValue(false);
|
||||
const response = await server.inject(
|
||||
enableAttackDiscoverySchedulesRequest('schedule-4'),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(404);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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 { IKibanaResponse, IRouter, Logger } from '@kbn/core/server';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
|
||||
|
||||
import {
|
||||
API_VERSIONS,
|
||||
EnableAttackDiscoverySchedulesRequestParams,
|
||||
EnableAttackDiscoverySchedulesResponse,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
import { buildResponse } from '../../../lib/build_response';
|
||||
import { ATTACK_DISCOVERY_SCHEDULES_BY_ID_ENABLE } from '../../../../common/constants';
|
||||
import { ElasticAssistantRequestHandlerContext } from '../../../types';
|
||||
import { performChecks } from '../../helpers';
|
||||
import { isFeatureAvailable } from './utils/is_feature_available';
|
||||
|
||||
export const enableAttackDiscoverySchedulesRoute = (
|
||||
router: IRouter<ElasticAssistantRequestHandlerContext>
|
||||
): void => {
|
||||
router.versioned
|
||||
.post({
|
||||
access: 'internal',
|
||||
path: ATTACK_DISCOVERY_SCHEDULES_BY_ID_ENABLE,
|
||||
security: {
|
||||
authz: {
|
||||
requiredPrivileges: ['elasticAssistant'],
|
||||
},
|
||||
},
|
||||
})
|
||||
.addVersion(
|
||||
{
|
||||
version: API_VERSIONS.internal.v1,
|
||||
validate: {
|
||||
request: {
|
||||
params: buildRouteValidationWithZod(EnableAttackDiscoverySchedulesRequestParams),
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
body: {
|
||||
custom: buildRouteValidationWithZod(EnableAttackDiscoverySchedulesResponse),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (
|
||||
context,
|
||||
request,
|
||||
response
|
||||
): Promise<IKibanaResponse<EnableAttackDiscoverySchedulesResponse>> => {
|
||||
const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
|
||||
const resp = buildResponse(response);
|
||||
const assistantContext = await context.elasticAssistant;
|
||||
const logger: Logger = assistantContext.logger;
|
||||
|
||||
// Check if scheduling feature available
|
||||
if (!(await isFeatureAvailable(ctx))) {
|
||||
return response.notFound();
|
||||
}
|
||||
|
||||
// Perform license and authenticated user
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
});
|
||||
|
||||
if (!checkResponse.isSuccess) {
|
||||
return checkResponse.response;
|
||||
}
|
||||
|
||||
const { id } = request.params;
|
||||
|
||||
try {
|
||||
const dataClient = await assistantContext.getAttackDiscoverySchedulingDataClient();
|
||||
if (!dataClient) {
|
||||
return resp.error({
|
||||
body: `Attack discovery data client not initialized`,
|
||||
statusCode: 500,
|
||||
});
|
||||
}
|
||||
|
||||
await dataClient.enableSchedule({ id });
|
||||
|
||||
return response.ok({ body: { id } });
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
const error = transformError(err);
|
||||
|
||||
return resp.error({
|
||||
body: { success: false, error: error.message },
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* 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 { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks';
|
||||
import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants';
|
||||
|
||||
import { findAttackDiscoverySchedulesRoute } from './find';
|
||||
import { serverMock } from '../../../__mocks__/server';
|
||||
import { requestContextMock } from '../../../__mocks__/request_context';
|
||||
import { findAttackDiscoverySchedulesRequest } from '../../../__mocks__/request';
|
||||
import {
|
||||
getInternalFindAttackDiscoverySchedulesMock,
|
||||
getInternalAttackDiscoveryScheduleMock,
|
||||
} from '../../../__mocks__/attack_discovery_schedules.mock';
|
||||
import { AttackDiscoveryScheduleDataClient } from '../../../lib/attack_discovery/schedules/data_client';
|
||||
|
||||
const { clients, context } = requestContextMock.createTools();
|
||||
const server: ReturnType<typeof serverMock.create> = serverMock.create();
|
||||
clients.core.elasticsearch.client = elasticsearchServiceMock.createScopedClusterClient();
|
||||
|
||||
const findAttackDiscoverySchedule = jest.fn();
|
||||
const mockSchedulingDataClient = {
|
||||
findSchedules: findAttackDiscoverySchedule,
|
||||
getSchedule: jest.fn(),
|
||||
createSchedule: jest.fn(),
|
||||
updateSchedule: jest.fn(),
|
||||
deleteSchedule: jest.fn(),
|
||||
enableSchedule: jest.fn(),
|
||||
disableSchedule: jest.fn(),
|
||||
} as unknown as AttackDiscoveryScheduleDataClient;
|
||||
const mockApiConfig = {
|
||||
connectorId: 'connector-id',
|
||||
actionTypeId: '.bedrock',
|
||||
model: 'model',
|
||||
provider: OpenAiProviderType.OpenAi,
|
||||
};
|
||||
const basicAttackDiscoveryScheduleMock = {
|
||||
name: 'Test Schedule',
|
||||
schedule: {
|
||||
interval: '100m',
|
||||
},
|
||||
params: {
|
||||
alertsIndexPattern: '.alerts-security.alerts-default',
|
||||
apiConfig: mockApiConfig,
|
||||
end: 'now',
|
||||
size: 25,
|
||||
start: 'now-24h',
|
||||
},
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
describe('findAttackDiscoverySchedulesRoute', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
context.elasticAssistant.getAttackDiscoverySchedulingDataClient.mockResolvedValue(
|
||||
mockSchedulingDataClient
|
||||
);
|
||||
context.core.featureFlags.getBooleanValue.mockResolvedValue(true);
|
||||
findAttackDiscoverySchedulesRoute(server.router);
|
||||
findAttackDiscoverySchedule.mockResolvedValue(
|
||||
getInternalFindAttackDiscoverySchedulesMock([
|
||||
getInternalAttackDiscoveryScheduleMock(basicAttackDiscoveryScheduleMock),
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle successful request', async () => {
|
||||
const response = await server.inject(
|
||||
findAttackDiscoverySchedulesRequest(),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual({
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
total: 1,
|
||||
data: [expect.objectContaining(basicAttackDiscoveryScheduleMock)],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle missing data client', async () => {
|
||||
context.elasticAssistant.getAttackDiscoverySchedulingDataClient.mockResolvedValue(null);
|
||||
const response = await server.inject(
|
||||
findAttackDiscoverySchedulesRequest(),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
|
||||
expect(response.status).toEqual(500);
|
||||
expect(response.body).toEqual({
|
||||
message: 'Attack discovery data client not initialized',
|
||||
status_code: 500,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle `dataClient.findSchedules` error', async () => {
|
||||
(findAttackDiscoverySchedule as jest.Mock).mockRejectedValue(new Error('Oh no!'));
|
||||
const response = await server.inject(
|
||||
findAttackDiscoverySchedulesRequest(),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(500);
|
||||
expect(response.body).toEqual({
|
||||
message: {
|
||||
error: 'Oh no!',
|
||||
success: false,
|
||||
},
|
||||
status_code: 500,
|
||||
});
|
||||
});
|
||||
|
||||
describe('Disabled feature flag', () => {
|
||||
it('should return a 404 if scheduling feature is not registered', async () => {
|
||||
context.core.featureFlags.getBooleanValue.mockResolvedValue(false);
|
||||
const response = await server.inject(
|
||||
findAttackDiscoverySchedulesRequest(),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(404);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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 { IKibanaResponse, IRouter, Logger } from '@kbn/core/server';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
|
||||
|
||||
import { API_VERSIONS, FindAttackDiscoverySchedulesResponse } from '@kbn/elastic-assistant-common';
|
||||
import { buildResponse } from '../../../lib/build_response';
|
||||
import { ATTACK_DISCOVERY_SCHEDULES_FIND } from '../../../../common/constants';
|
||||
import { ElasticAssistantRequestHandlerContext } from '../../../types';
|
||||
import { convertAlertingRuleToSchedule } from './utils/convert_alerting_rule_to_schedule';
|
||||
import { performChecks } from '../../helpers';
|
||||
import { isFeatureAvailable } from './utils/is_feature_available';
|
||||
|
||||
export const findAttackDiscoverySchedulesRoute = (
|
||||
router: IRouter<ElasticAssistantRequestHandlerContext>
|
||||
): void => {
|
||||
router.versioned
|
||||
.get({
|
||||
access: 'internal',
|
||||
path: ATTACK_DISCOVERY_SCHEDULES_FIND,
|
||||
security: {
|
||||
authz: {
|
||||
requiredPrivileges: ['elasticAssistant'],
|
||||
},
|
||||
},
|
||||
})
|
||||
.addVersion(
|
||||
{
|
||||
version: API_VERSIONS.internal.v1,
|
||||
validate: {
|
||||
response: {
|
||||
200: {
|
||||
body: {
|
||||
custom: buildRouteValidationWithZod(FindAttackDiscoverySchedulesResponse),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (
|
||||
context,
|
||||
request,
|
||||
response
|
||||
): Promise<IKibanaResponse<FindAttackDiscoverySchedulesResponse>> => {
|
||||
const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
|
||||
const resp = buildResponse(response);
|
||||
const assistantContext = await context.elasticAssistant;
|
||||
const logger: Logger = assistantContext.logger;
|
||||
|
||||
// Check if scheduling feature available
|
||||
if (!(await isFeatureAvailable(ctx))) {
|
||||
return response.notFound();
|
||||
}
|
||||
|
||||
// Perform license and authenticated user
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
});
|
||||
|
||||
if (!checkResponse.isSuccess) {
|
||||
return checkResponse.response;
|
||||
}
|
||||
|
||||
try {
|
||||
const dataClient = await assistantContext.getAttackDiscoverySchedulingDataClient();
|
||||
if (!dataClient) {
|
||||
return resp.error({
|
||||
body: `Attack discovery data client not initialized`,
|
||||
statusCode: 500,
|
||||
});
|
||||
}
|
||||
|
||||
const results = await dataClient.findSchedules();
|
||||
const { page, perPage, total, data } = results;
|
||||
|
||||
const schedules = data.map(convertAlertingRuleToSchedule);
|
||||
|
||||
return response.ok({ body: { page, perPage, total, data: schedules } });
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
const error = transformError(err);
|
||||
|
||||
return resp.error({
|
||||
body: { success: false, error: error.message },
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* 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 { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks';
|
||||
import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants';
|
||||
|
||||
import { getAttackDiscoverySchedulesRoute } from './get';
|
||||
import { serverMock } from '../../../__mocks__/server';
|
||||
import { requestContextMock } from '../../../__mocks__/request_context';
|
||||
import { getAttackDiscoverySchedulesRequest } from '../../../__mocks__/request';
|
||||
import { getInternalAttackDiscoveryScheduleMock } from '../../../__mocks__/attack_discovery_schedules.mock';
|
||||
import { AttackDiscoveryScheduleDataClient } from '../../../lib/attack_discovery/schedules/data_client';
|
||||
|
||||
const { clients, context } = requestContextMock.createTools();
|
||||
const server: ReturnType<typeof serverMock.create> = serverMock.create();
|
||||
clients.core.elasticsearch.client = elasticsearchServiceMock.createScopedClusterClient();
|
||||
|
||||
const getAttackDiscoverySchedule = jest.fn();
|
||||
const mockSchedulingDataClient = {
|
||||
findSchedules: jest.fn(),
|
||||
getSchedule: getAttackDiscoverySchedule,
|
||||
createSchedule: jest.fn(),
|
||||
updateSchedule: jest.fn(),
|
||||
deleteSchedule: jest.fn(),
|
||||
enableSchedule: jest.fn(),
|
||||
disableSchedule: jest.fn(),
|
||||
} as unknown as AttackDiscoveryScheduleDataClient;
|
||||
const mockApiConfig = {
|
||||
connectorId: 'connector-id',
|
||||
actionTypeId: '.bedrock',
|
||||
model: 'model',
|
||||
provider: OpenAiProviderType.OpenAi,
|
||||
};
|
||||
const basicAttackDiscoveryScheduleMock = {
|
||||
name: 'Test Schedule',
|
||||
schedule: {
|
||||
interval: '100m',
|
||||
},
|
||||
params: {
|
||||
alertsIndexPattern: '.alerts-security.alerts-default',
|
||||
apiConfig: mockApiConfig,
|
||||
end: 'now',
|
||||
size: 25,
|
||||
start: 'now-24h',
|
||||
},
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
describe('getAttackDiscoverySchedulesRoute', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
context.elasticAssistant.getAttackDiscoverySchedulingDataClient.mockResolvedValue(
|
||||
mockSchedulingDataClient
|
||||
);
|
||||
context.core.featureFlags.getBooleanValue.mockResolvedValue(true);
|
||||
getAttackDiscoverySchedulesRoute(server.router);
|
||||
getAttackDiscoverySchedule.mockResolvedValue(
|
||||
getInternalAttackDiscoveryScheduleMock(basicAttackDiscoveryScheduleMock)
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle successful request', async () => {
|
||||
const response = await server.inject(
|
||||
getAttackDiscoverySchedulesRequest('schedule-1'),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual(expect.objectContaining(basicAttackDiscoveryScheduleMock));
|
||||
});
|
||||
|
||||
it('should handle missing data client', async () => {
|
||||
context.elasticAssistant.getAttackDiscoverySchedulingDataClient.mockResolvedValue(null);
|
||||
const response = await server.inject(
|
||||
getAttackDiscoverySchedulesRequest('schedule-2'),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
|
||||
expect(response.status).toEqual(500);
|
||||
expect(response.body).toEqual({
|
||||
message: 'Attack discovery data client not initialized',
|
||||
status_code: 500,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle `dataClient.getSchedule` error', async () => {
|
||||
(getAttackDiscoverySchedule as jest.Mock).mockRejectedValue(new Error('Oh no!'));
|
||||
const response = await server.inject(
|
||||
getAttackDiscoverySchedulesRequest('schedule-3'),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(500);
|
||||
expect(response.body).toEqual({
|
||||
message: {
|
||||
error: 'Oh no!',
|
||||
success: false,
|
||||
},
|
||||
status_code: 500,
|
||||
});
|
||||
});
|
||||
|
||||
describe('Disabled feature flag', () => {
|
||||
it('should return a 404 if scheduling feature is not registered', async () => {
|
||||
context.core.featureFlags.getBooleanValue.mockResolvedValue(false);
|
||||
const response = await server.inject(
|
||||
getAttackDiscoverySchedulesRequest('schedule-4'),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(404);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* 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 { IKibanaResponse, IRouter, Logger } from '@kbn/core/server';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
|
||||
|
||||
import {
|
||||
API_VERSIONS,
|
||||
GetAttackDiscoverySchedulesRequestParams,
|
||||
GetAttackDiscoverySchedulesResponse,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
import { buildResponse } from '../../../lib/build_response';
|
||||
import { ATTACK_DISCOVERY_SCHEDULES_BY_ID } from '../../../../common/constants';
|
||||
import { ElasticAssistantRequestHandlerContext } from '../../../types';
|
||||
import { convertAlertingRuleToSchedule } from './utils/convert_alerting_rule_to_schedule';
|
||||
import { performChecks } from '../../helpers';
|
||||
import { isFeatureAvailable } from './utils/is_feature_available';
|
||||
|
||||
export const getAttackDiscoverySchedulesRoute = (
|
||||
router: IRouter<ElasticAssistantRequestHandlerContext>
|
||||
): void => {
|
||||
router.versioned
|
||||
.get({
|
||||
access: 'internal',
|
||||
path: ATTACK_DISCOVERY_SCHEDULES_BY_ID,
|
||||
security: {
|
||||
authz: {
|
||||
requiredPrivileges: ['elasticAssistant'],
|
||||
},
|
||||
},
|
||||
})
|
||||
.addVersion(
|
||||
{
|
||||
version: API_VERSIONS.internal.v1,
|
||||
validate: {
|
||||
request: {
|
||||
params: buildRouteValidationWithZod(GetAttackDiscoverySchedulesRequestParams),
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
body: {
|
||||
custom: buildRouteValidationWithZod(GetAttackDiscoverySchedulesResponse),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (
|
||||
context,
|
||||
request,
|
||||
response
|
||||
): Promise<IKibanaResponse<GetAttackDiscoverySchedulesResponse>> => {
|
||||
const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
|
||||
const resp = buildResponse(response);
|
||||
const assistantContext = await context.elasticAssistant;
|
||||
const logger: Logger = assistantContext.logger;
|
||||
|
||||
// Check if scheduling feature available
|
||||
if (!(await isFeatureAvailable(ctx))) {
|
||||
return response.notFound();
|
||||
}
|
||||
|
||||
// Perform license and authenticated user
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
});
|
||||
|
||||
if (!checkResponse.isSuccess) {
|
||||
return checkResponse.response;
|
||||
}
|
||||
|
||||
const { id } = request.params;
|
||||
|
||||
try {
|
||||
const dataClient = await assistantContext.getAttackDiscoverySchedulingDataClient();
|
||||
if (!dataClient) {
|
||||
return resp.error({
|
||||
body: `Attack discovery data client not initialized`,
|
||||
statusCode: 500,
|
||||
});
|
||||
}
|
||||
|
||||
const alertingRule = await dataClient.getSchedule(id);
|
||||
|
||||
const schedule = convertAlertingRuleToSchedule(alertingRule);
|
||||
|
||||
return response.ok({ body: schedule });
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
const error = transformError(err);
|
||||
|
||||
return resp.error({
|
||||
body: { success: false, error: error.message },
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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 { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks';
|
||||
import { UpdateAttackDiscoverySchedulesRequestBody } from '@kbn/elastic-assistant-common';
|
||||
import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants';
|
||||
|
||||
import { updateAttackDiscoverySchedulesRoute } from './update';
|
||||
import { serverMock } from '../../../__mocks__/server';
|
||||
import { requestContextMock } from '../../../__mocks__/request_context';
|
||||
import { updateAttackDiscoverySchedulesRequest } from '../../../__mocks__/request';
|
||||
import { getInternalAttackDiscoveryScheduleMock } from '../../../__mocks__/attack_discovery_schedules.mock';
|
||||
import { AttackDiscoveryScheduleDataClient } from '../../../lib/attack_discovery/schedules/data_client';
|
||||
|
||||
const { clients, context } = requestContextMock.createTools();
|
||||
const server: ReturnType<typeof serverMock.create> = serverMock.create();
|
||||
clients.core.elasticsearch.client = elasticsearchServiceMock.createScopedClusterClient();
|
||||
|
||||
const updateAttackDiscoverySchedule = jest.fn();
|
||||
const mockSchedulingDataClient = {
|
||||
findSchedules: jest.fn(),
|
||||
getSchedule: jest.fn(),
|
||||
createSchedule: jest.fn(),
|
||||
updateSchedule: updateAttackDiscoverySchedule,
|
||||
deleteSchedule: jest.fn(),
|
||||
enableSchedule: jest.fn(),
|
||||
disableSchedule: jest.fn(),
|
||||
} as unknown as AttackDiscoveryScheduleDataClient;
|
||||
const mockApiConfig = {
|
||||
connectorId: 'connector-id',
|
||||
actionTypeId: '.bedrock',
|
||||
model: 'model',
|
||||
provider: OpenAiProviderType.OpenAi,
|
||||
};
|
||||
const mockRequestBody: UpdateAttackDiscoverySchedulesRequestBody = {
|
||||
name: 'Test Schedule 2',
|
||||
schedule: {
|
||||
interval: '15m',
|
||||
},
|
||||
params: {
|
||||
alertsIndexPattern: '.alerts-security.alerts-default',
|
||||
apiConfig: mockApiConfig,
|
||||
end: 'now',
|
||||
size: 50,
|
||||
start: 'now-24h',
|
||||
},
|
||||
actions: [],
|
||||
};
|
||||
|
||||
describe('updateAttackDiscoverySchedulesRoute', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
context.elasticAssistant.getAttackDiscoverySchedulingDataClient.mockResolvedValue(
|
||||
mockSchedulingDataClient
|
||||
);
|
||||
context.core.featureFlags.getBooleanValue.mockResolvedValue(true);
|
||||
updateAttackDiscoverySchedulesRoute(server.router);
|
||||
updateAttackDiscoverySchedule.mockResolvedValue(
|
||||
getInternalAttackDiscoveryScheduleMock(mockRequestBody)
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle successful request', async () => {
|
||||
const response = await server.inject(
|
||||
updateAttackDiscoverySchedulesRequest('schedule-1', mockRequestBody),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual(expect.objectContaining({ ...mockRequestBody }));
|
||||
});
|
||||
|
||||
it('should handle missing data client', async () => {
|
||||
context.elasticAssistant.getAttackDiscoverySchedulingDataClient.mockResolvedValue(null);
|
||||
const response = await server.inject(
|
||||
updateAttackDiscoverySchedulesRequest('schedule-2', mockRequestBody),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
|
||||
expect(response.status).toEqual(500);
|
||||
expect(response.body).toEqual({
|
||||
message: 'Attack discovery data client not initialized',
|
||||
status_code: 500,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle `dataClient.updateSchedule` error', async () => {
|
||||
(updateAttackDiscoverySchedule as jest.Mock).mockRejectedValue(new Error('Oh no!'));
|
||||
const response = await server.inject(
|
||||
updateAttackDiscoverySchedulesRequest('schedule-3', mockRequestBody),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(500);
|
||||
expect(response.body).toEqual({
|
||||
message: {
|
||||
error: 'Oh no!',
|
||||
success: false,
|
||||
},
|
||||
status_code: 500,
|
||||
});
|
||||
});
|
||||
|
||||
describe('Disabled feature flag', () => {
|
||||
it('should return a 404 if scheduling feature is not registered', async () => {
|
||||
context.core.featureFlags.getBooleanValue.mockResolvedValue(false);
|
||||
const response = await server.inject(
|
||||
updateAttackDiscoverySchedulesRequest('schedule-4', mockRequestBody),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(404);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* 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 { IKibanaResponse, IRouter, Logger } from '@kbn/core/server';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
|
||||
|
||||
import {
|
||||
API_VERSIONS,
|
||||
UpdateAttackDiscoverySchedulesRequestBody,
|
||||
UpdateAttackDiscoverySchedulesRequestParams,
|
||||
UpdateAttackDiscoverySchedulesResponse,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
import { buildResponse } from '../../../lib/build_response';
|
||||
import { ATTACK_DISCOVERY_SCHEDULES_BY_ID } from '../../../../common/constants';
|
||||
import { ElasticAssistantRequestHandlerContext } from '../../../types';
|
||||
import { convertAlertingRuleToSchedule } from './utils/convert_alerting_rule_to_schedule';
|
||||
import { performChecks } from '../../helpers';
|
||||
import { isFeatureAvailable } from './utils/is_feature_available';
|
||||
|
||||
export const updateAttackDiscoverySchedulesRoute = (
|
||||
router: IRouter<ElasticAssistantRequestHandlerContext>
|
||||
): void => {
|
||||
router.versioned
|
||||
.put({
|
||||
access: 'internal',
|
||||
path: ATTACK_DISCOVERY_SCHEDULES_BY_ID,
|
||||
security: {
|
||||
authz: {
|
||||
requiredPrivileges: ['elasticAssistant'],
|
||||
},
|
||||
},
|
||||
})
|
||||
.addVersion(
|
||||
{
|
||||
version: API_VERSIONS.internal.v1,
|
||||
validate: {
|
||||
request: {
|
||||
params: buildRouteValidationWithZod(UpdateAttackDiscoverySchedulesRequestParams),
|
||||
body: buildRouteValidationWithZod(UpdateAttackDiscoverySchedulesRequestBody),
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
body: {
|
||||
custom: buildRouteValidationWithZod(UpdateAttackDiscoverySchedulesResponse),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (
|
||||
context,
|
||||
request,
|
||||
response
|
||||
): Promise<IKibanaResponse<UpdateAttackDiscoverySchedulesResponse>> => {
|
||||
const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
|
||||
const resp = buildResponse(response);
|
||||
const assistantContext = await context.elasticAssistant;
|
||||
const logger: Logger = assistantContext.logger;
|
||||
|
||||
// Check if scheduling feature available
|
||||
if (!(await isFeatureAvailable(ctx))) {
|
||||
return response.notFound();
|
||||
}
|
||||
|
||||
// Perform license and authenticated user
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
});
|
||||
|
||||
if (!checkResponse.isSuccess) {
|
||||
return checkResponse.response;
|
||||
}
|
||||
|
||||
const { id } = request.params;
|
||||
const scheduleAttributes = request.body;
|
||||
|
||||
try {
|
||||
const dataClient = await assistantContext.getAttackDiscoverySchedulingDataClient();
|
||||
if (!dataClient) {
|
||||
return resp.error({
|
||||
body: `Attack discovery data client not initialized`,
|
||||
statusCode: 500,
|
||||
});
|
||||
}
|
||||
|
||||
const alertingRule = await dataClient.updateSchedule({
|
||||
id,
|
||||
tags: [],
|
||||
...scheduleAttributes,
|
||||
});
|
||||
const schedule = convertAlertingRuleToSchedule(alertingRule);
|
||||
|
||||
return response.ok({ body: schedule });
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
const error = transformError(err);
|
||||
|
||||
return resp.error({
|
||||
body: { success: false, error: error.message },
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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 { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants';
|
||||
|
||||
import { convertAlertingRuleToSchedule } from './convert_alerting_rule_to_schedule';
|
||||
import { getInternalAttackDiscoveryScheduleMock } from '../../../../__mocks__/attack_discovery_schedules.mock';
|
||||
|
||||
const mockApiConfig = {
|
||||
connectorId: 'connector-id',
|
||||
actionTypeId: '.bedrock',
|
||||
model: 'model',
|
||||
provider: OpenAiProviderType.OpenAi,
|
||||
};
|
||||
const basicAttackDiscoveryScheduleMock = {
|
||||
name: 'Test Schedule',
|
||||
schedule: {
|
||||
interval: '10m',
|
||||
},
|
||||
params: {
|
||||
alertsIndexPattern: '.alerts-security.alerts-default',
|
||||
apiConfig: mockApiConfig,
|
||||
end: 'now',
|
||||
size: 25,
|
||||
start: 'now-24h',
|
||||
},
|
||||
enabled: true,
|
||||
actions: [],
|
||||
};
|
||||
|
||||
describe('convertAlertingRuleToSchedule', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should convert basic internal schedule', async () => {
|
||||
const internalRule = getInternalAttackDiscoveryScheduleMock(basicAttackDiscoveryScheduleMock);
|
||||
const { id, createdBy, updatedBy, createdAt, updatedAt } = internalRule;
|
||||
const schedule = convertAlertingRuleToSchedule(internalRule);
|
||||
|
||||
expect(schedule).toEqual({
|
||||
id,
|
||||
createdBy,
|
||||
updatedBy,
|
||||
createdAt: createdAt.toISOString(),
|
||||
updatedAt: updatedAt.toISOString(),
|
||||
...basicAttackDiscoveryScheduleMock,
|
||||
});
|
||||
});
|
||||
|
||||
it('should default to `elastic` as a user if `createdBy` and/or `updatedBy` set to null', async () => {
|
||||
const internalRule = getInternalAttackDiscoveryScheduleMock(basicAttackDiscoveryScheduleMock);
|
||||
const { createdBy: _, updatedBy: __, ...restInternalRule } = internalRule;
|
||||
const { id, createdAt, updatedAt } = internalRule;
|
||||
const schedule = convertAlertingRuleToSchedule({
|
||||
...restInternalRule,
|
||||
createdBy: null,
|
||||
updatedBy: null,
|
||||
});
|
||||
|
||||
expect(schedule).toEqual({
|
||||
id,
|
||||
createdBy: 'elastic',
|
||||
updatedBy: 'elastic',
|
||||
createdAt: createdAt.toISOString(),
|
||||
updatedAt: updatedAt.toISOString(),
|
||||
...basicAttackDiscoveryScheduleMock,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 { SanitizedRule } from '@kbn/alerting-plugin/common';
|
||||
import {
|
||||
AttackDiscoverySchedule,
|
||||
AttackDiscoveryScheduleParams,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
import { createScheduleExecutionSummary } from './create_schedule_execution_summary';
|
||||
|
||||
export const convertAlertingRuleToSchedule = (
|
||||
rule: SanitizedRule<AttackDiscoveryScheduleParams>
|
||||
): AttackDiscoverySchedule => {
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
createdBy,
|
||||
updatedBy,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
enabled,
|
||||
params,
|
||||
schedule,
|
||||
actions,
|
||||
} = rule;
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
createdBy: createdBy ?? 'elastic',
|
||||
updatedBy: updatedBy ?? 'elastic',
|
||||
createdAt: createdAt.toISOString(),
|
||||
updatedAt: updatedAt.toISOString(),
|
||||
enabled,
|
||||
params,
|
||||
schedule,
|
||||
actions,
|
||||
lastExecution: createScheduleExecutionSummary(rule),
|
||||
};
|
||||
};
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* 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 { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants';
|
||||
|
||||
import { createScheduleExecutionSummary } from './create_schedule_execution_summary';
|
||||
import { getInternalAttackDiscoveryScheduleMock } from '../../../../__mocks__/attack_discovery_schedules.mock';
|
||||
import {
|
||||
RuleExecutionStatusErrorReasons,
|
||||
RuleExecutionStatusWarningReasons,
|
||||
} from '@kbn/alerting-types';
|
||||
|
||||
const mockApiConfig = {
|
||||
connectorId: 'connector-id',
|
||||
actionTypeId: '.bedrock',
|
||||
model: 'model',
|
||||
provider: OpenAiProviderType.OpenAi,
|
||||
};
|
||||
const basicAttackDiscoveryScheduleMock = {
|
||||
name: 'Test Schedule',
|
||||
schedule: {
|
||||
interval: '10m',
|
||||
},
|
||||
params: {
|
||||
alertsIndexPattern: '.alerts-security.alerts-default',
|
||||
apiConfig: mockApiConfig,
|
||||
end: 'now',
|
||||
size: 25,
|
||||
start: 'now-24h',
|
||||
},
|
||||
enabled: true,
|
||||
actions: [],
|
||||
};
|
||||
|
||||
describe('createScheduleExecutionSummary', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should not return execution summary if internal status is set to `pending`', async () => {
|
||||
const now = new Date();
|
||||
const internalRule = getInternalAttackDiscoveryScheduleMock(basicAttackDiscoveryScheduleMock, {
|
||||
executionStatus: {
|
||||
status: 'pending',
|
||||
lastExecutionDate: now,
|
||||
},
|
||||
});
|
||||
const execution = createScheduleExecutionSummary(internalRule);
|
||||
|
||||
expect(execution).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return status of the schedule execution', async () => {
|
||||
const now = new Date();
|
||||
const internalRule = getInternalAttackDiscoveryScheduleMock(basicAttackDiscoveryScheduleMock, {
|
||||
executionStatus: {
|
||||
status: 'ok',
|
||||
lastExecutionDate: now,
|
||||
lastDuration: 22,
|
||||
},
|
||||
});
|
||||
const execution = createScheduleExecutionSummary(internalRule);
|
||||
|
||||
expect(execution?.status).toEqual('ok');
|
||||
});
|
||||
|
||||
it('should return data of the schedule execution', async () => {
|
||||
const now = new Date();
|
||||
const internalRule = getInternalAttackDiscoveryScheduleMock(basicAttackDiscoveryScheduleMock, {
|
||||
executionStatus: {
|
||||
status: 'ok',
|
||||
lastExecutionDate: now,
|
||||
lastDuration: 22,
|
||||
},
|
||||
});
|
||||
const execution = createScheduleExecutionSummary(internalRule);
|
||||
|
||||
expect(execution?.date).toEqual(now.toISOString());
|
||||
});
|
||||
|
||||
it('should return duration of the schedule execution', async () => {
|
||||
const now = new Date();
|
||||
const internalRule = getInternalAttackDiscoveryScheduleMock(basicAttackDiscoveryScheduleMock, {
|
||||
executionStatus: {
|
||||
status: 'ok',
|
||||
lastExecutionDate: now,
|
||||
lastDuration: 22,
|
||||
},
|
||||
});
|
||||
const execution = createScheduleExecutionSummary(internalRule);
|
||||
|
||||
expect(execution?.duration).toEqual(22);
|
||||
});
|
||||
|
||||
it('should return empty message if neither error nor warning are specified', async () => {
|
||||
const now = new Date();
|
||||
const internalRule = getInternalAttackDiscoveryScheduleMock(basicAttackDiscoveryScheduleMock, {
|
||||
executionStatus: {
|
||||
status: 'ok',
|
||||
lastExecutionDate: now,
|
||||
lastDuration: 22,
|
||||
},
|
||||
});
|
||||
const execution = createScheduleExecutionSummary(internalRule);
|
||||
|
||||
expect(execution?.message).toEqual('');
|
||||
});
|
||||
|
||||
it('should return error message if specified', async () => {
|
||||
const now = new Date();
|
||||
const internalRule = getInternalAttackDiscoveryScheduleMock(basicAttackDiscoveryScheduleMock, {
|
||||
executionStatus: {
|
||||
status: 'error',
|
||||
lastExecutionDate: now,
|
||||
lastDuration: 22,
|
||||
error: {
|
||||
reason: RuleExecutionStatusErrorReasons.Execute,
|
||||
message: 'Test Error Message',
|
||||
},
|
||||
},
|
||||
});
|
||||
const execution = createScheduleExecutionSummary(internalRule);
|
||||
|
||||
expect(execution?.message).toEqual('Test Error Message');
|
||||
});
|
||||
|
||||
it('should return warning message if specified', async () => {
|
||||
const now = new Date();
|
||||
const internalRule = getInternalAttackDiscoveryScheduleMock(basicAttackDiscoveryScheduleMock, {
|
||||
executionStatus: {
|
||||
status: 'error',
|
||||
lastExecutionDate: now,
|
||||
lastDuration: 22,
|
||||
warning: {
|
||||
reason: RuleExecutionStatusWarningReasons.MAX_ALERTS,
|
||||
message: 'Test Warning Message',
|
||||
},
|
||||
},
|
||||
});
|
||||
const execution = createScheduleExecutionSummary(internalRule);
|
||||
|
||||
expect(execution?.message).toEqual('Test Warning Message');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 { SanitizedRule } from '@kbn/alerting-plugin/common';
|
||||
import {
|
||||
AttackDiscoveryScheduleExecution,
|
||||
AttackDiscoveryScheduleParams,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
|
||||
export const createScheduleExecutionSummary = (
|
||||
rule: SanitizedRule<AttackDiscoveryScheduleParams>
|
||||
): AttackDiscoveryScheduleExecution | undefined => {
|
||||
const { executionStatus } = rule;
|
||||
if (executionStatus.status === 'pending') {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
date: executionStatus.lastExecutionDate.toISOString(),
|
||||
status: executionStatus.status,
|
||||
duration: executionStatus.lastDuration,
|
||||
message: executionStatus.error?.message ?? executionStatus.warning?.message ?? '',
|
||||
};
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 { AwaitedProperties } from '@kbn/utility-types';
|
||||
import { ElasticAssistantRequestHandlerContext } from '../../../../types';
|
||||
import { isFeatureAvailable } from './is_feature_available';
|
||||
|
||||
const getBooleanValueMock = jest.fn();
|
||||
const mockContext = {
|
||||
core: {
|
||||
featureFlags: {
|
||||
getBooleanValue: getBooleanValueMock,
|
||||
},
|
||||
},
|
||||
} as unknown as AwaitedProperties<ElasticAssistantRequestHandlerContext>;
|
||||
|
||||
describe('isFeatureAvailable', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should call feature flags service with the correct attributes', async () => {
|
||||
void isFeatureAvailable(mockContext);
|
||||
|
||||
expect(getBooleanValueMock).toHaveBeenCalledWith(
|
||||
'securitySolution.assistantAttackDiscoverySchedulingEnabled',
|
||||
false
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { ATTACK_DISCOVERY_SCHEDULES_ENABLED_FEATURE_FLAG } from '@kbn/elastic-assistant-common';
|
||||
import { AwaitedProperties } from '@kbn/utility-types';
|
||||
|
||||
import { ElasticAssistantRequestHandlerContext } from '../../../../types';
|
||||
|
||||
export const isFeatureAvailable = async (
|
||||
context: AwaitedProperties<Pick<ElasticAssistantRequestHandlerContext, 'core'>>
|
||||
): Promise<boolean> => {
|
||||
return context.core.featureFlags.getBooleanValue(
|
||||
ATTACK_DISCOVERY_SCHEDULES_ENABLED_FEATURE_FLAG,
|
||||
false
|
||||
);
|
||||
};
|
|
@ -41,6 +41,13 @@ import {
|
|||
import { deleteKnowledgeBaseEntryRoute } from './knowledge_base/entries/delete_route';
|
||||
import { updateKnowledgeBaseEntryRoute } from './knowledge_base/entries/update_route';
|
||||
import { getKnowledgeBaseEntryRoute } from './knowledge_base/entries/get_route';
|
||||
import { createAttackDiscoverySchedulesRoute } from './attack_discovery/schedules/create';
|
||||
import { getAttackDiscoverySchedulesRoute } from './attack_discovery/schedules/get';
|
||||
import { updateAttackDiscoverySchedulesRoute } from './attack_discovery/schedules/update';
|
||||
import { deleteAttackDiscoverySchedulesRoute } from './attack_discovery/schedules/delete';
|
||||
import { findAttackDiscoverySchedulesRoute } from './attack_discovery/schedules/find';
|
||||
import { disableAttackDiscoverySchedulesRoute } from './attack_discovery/schedules/disable';
|
||||
import { enableAttackDiscoverySchedulesRoute } from './attack_discovery/schedules/enable';
|
||||
|
||||
export const registerRoutes = (
|
||||
router: ElasticAssistantPluginRouter,
|
||||
|
@ -101,6 +108,15 @@ export const registerRoutes = (
|
|||
postAttackDiscoveryRoute(router);
|
||||
cancelAttackDiscoveryRoute(router);
|
||||
|
||||
// Attack Discovery Schedules
|
||||
createAttackDiscoverySchedulesRoute(router);
|
||||
getAttackDiscoverySchedulesRoute(router);
|
||||
findAttackDiscoverySchedulesRoute(router);
|
||||
updateAttackDiscoverySchedulesRoute(router);
|
||||
deleteAttackDiscoverySchedulesRoute(router);
|
||||
disableAttackDiscoverySchedulesRoute(router);
|
||||
enableAttackDiscoverySchedulesRoute(router);
|
||||
|
||||
// Defend insights
|
||||
getDefendInsightRoute(router);
|
||||
getDefendInsightsRoute(router);
|
||||
|
|
|
@ -78,6 +78,7 @@ export class RequestContextFactory implements IRequestContextFactory {
|
|||
};
|
||||
|
||||
const savedObjectsClient = coreStart.savedObjects.getScopedClient(request);
|
||||
const rulesClient = await startPlugins.alerting.getRulesClientWithRequest(request);
|
||||
|
||||
return {
|
||||
core: coreContext,
|
||||
|
@ -142,6 +143,12 @@ export class RequestContextFactory implements IRequestContextFactory {
|
|||
});
|
||||
}),
|
||||
|
||||
getAttackDiscoverySchedulingDataClient: memoize(async () => {
|
||||
return this.assistantService.createAttackDiscoverySchedulingDataClient({
|
||||
rulesClient,
|
||||
});
|
||||
}),
|
||||
|
||||
getDefendInsightsDataClient: memoize(async () => {
|
||||
const currentUser = await getCurrentUser();
|
||||
return this.assistantService.createDefendInsightsDataClient({
|
||||
|
|
|
@ -50,6 +50,7 @@ import {
|
|||
import type { InferenceServerStart } from '@kbn/inference-plugin/server';
|
||||
|
||||
import { ProductDocBaseStartContract } from '@kbn/product-doc-base-plugin/server';
|
||||
import { AlertingServerSetup, AlertingServerStart } from '@kbn/alerting-plugin/server';
|
||||
import type { GetAIAssistantKnowledgeBaseDataClientParams } from './ai_assistant_data_clients/knowledge_base';
|
||||
import { AttackDiscoveryDataClient } from './lib/attack_discovery/persistence';
|
||||
import {
|
||||
|
@ -61,6 +62,7 @@ import { CallbackIds } from './services/app_context';
|
|||
import { AIAssistantDataClient } from './ai_assistant_data_clients';
|
||||
import { AIAssistantKnowledgeBaseDataClient } from './ai_assistant_data_clients/knowledge_base';
|
||||
import type { DefendInsightsDataClient } from './lib/defend_insights/persistence';
|
||||
import { AttackDiscoveryScheduleDataClient } from './lib/attack_discovery/schedules/data_client';
|
||||
|
||||
export const PLUGIN_ID = 'elasticAssistant' as const;
|
||||
export { CallbackIds };
|
||||
|
@ -120,12 +122,14 @@ export interface ElasticAssistantPluginStart {
|
|||
|
||||
export interface ElasticAssistantPluginSetupDependencies {
|
||||
actions: ActionsPluginSetup;
|
||||
alerting: AlertingServerSetup;
|
||||
ml: MlPluginSetup;
|
||||
taskManager: TaskManagerSetupContract;
|
||||
spaces?: SpacesPluginSetup;
|
||||
}
|
||||
export interface ElasticAssistantPluginStartDependencies {
|
||||
actions: ActionsPluginStart;
|
||||
alerting: AlertingServerStart;
|
||||
llmTasks: LlmTasksPluginStart;
|
||||
inference: InferenceServerStart;
|
||||
spaces?: SpacesPluginStart;
|
||||
|
@ -150,6 +154,7 @@ export interface ElasticAssistantApiRequestHandlerContext {
|
|||
params?: GetAIAssistantKnowledgeBaseDataClientParams
|
||||
) => Promise<AIAssistantKnowledgeBaseDataClient | null>;
|
||||
getAttackDiscoveryDataClient: () => Promise<AttackDiscoveryDataClient | null>;
|
||||
getAttackDiscoverySchedulingDataClient: () => Promise<AttackDiscoveryScheduleDataClient | null>;
|
||||
getDefendInsightsDataClient: () => Promise<DefendInsightsDataClient | null>;
|
||||
getAIAssistantPromptsDataClient: () => Promise<AIAssistantDataClient | null>;
|
||||
getAIAssistantAnonymizationFieldsDataClient: () => Promise<AIAssistantDataClient | null>;
|
||||
|
|
|
@ -59,6 +59,8 @@
|
|||
"@kbn/alerts-as-data-utils",
|
||||
"@kbn/alerting-plugin",
|
||||
"@kbn/rule-data-utils",
|
||||
"@kbn/alerting-types",
|
||||
"@kbn/zod-helpers",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue