Skip to content

Commit

Permalink
Implement RuleExecutionLog (#103463)
Browse files Browse the repository at this point in the history
  • Loading branch information
xcrzx authored Aug 3, 2021
1 parent 4b4525a commit fddd9d7
Show file tree
Hide file tree
Showing 76 changed files with 1,383 additions and 395 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { left } from 'fp-ts/lib/Either';
import { enumeration } from '.';
import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils';

describe('enumeration', () => {
enum TestEnum {
'test' = 'test',
}

it('should validate a string from the enum', () => {
const input = TestEnum.test;
const codec = enumeration('TestEnum', TestEnum);
const decoded = codec.decode(input);
const message = foldLeftRight(decoded);

expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(input);
});

it('should NOT validate a random string', () => {
const input = 'some string';
const codec = enumeration('TestEnum', TestEnum);
const decoded = codec.decode(input);
const message = foldLeftRight(decoded);

expect(getPaths(left(message.errors))).toEqual([
'Invalid value "some string" supplied to "TestEnum"',
]);
expect(message.schema).toEqual({});
});
});
24 changes: 24 additions & 0 deletions packages/kbn-securitysolution-io-ts-types/src/enumeration/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import * as t from 'io-ts';

export function enumeration<EnumType extends string>(
name: string,
originalEnum: Record<string, EnumType>
): t.Type<EnumType, EnumType, unknown> {
const isEnumValue = (input: unknown): input is EnumType =>
Object.values<unknown>(originalEnum).includes(input);

return new t.Type<EnumType>(
name,
isEnumValue,
(input, context) => (isEnumValue(input) ? t.success(input) : t.failure(input, context)),
t.identity
);
}
7 changes: 4 additions & 3 deletions packages/kbn-securitysolution-io-ts-types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@ export * from './default_string_boolean_false';
export * from './default_uuid';
export * from './default_version_number';
export * from './empty_string_array';
export * from './enumeration';
export * from './iso_date_string';
export * from './non_empty_array';
export * from './non_empty_or_nullable_string_array';
export * from './non_empty_string';
export * from './non_empty_string_array';
export * from './operator';
export * from './non_empty_string';
export * from './only_false_allowed';
export * from './positive_integer';
export * from './operator';
export * from './positive_integer_greater_than_zero';
export * from './positive_integer';
export * from './string_to_positive_number';
export * from './uuid';
export * from './version';
3 changes: 2 additions & 1 deletion x-pack/plugins/rule_registry/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ export {
createLifecycleRuleTypeFactory,
LifecycleAlertService,
} from './utils/create_lifecycle_rule_type_factory';
export { RuleDataPluginService } from './rule_data_plugin_service';
export {
LifecycleRuleExecutor,
LifecycleAlertServices,
createLifecycleExecutor,
} from './utils/create_lifecycle_executor';
export { createPersistenceRuleTypeFactory } from './utils/create_persistence_rule_type_factory';
export type { AlertTypeWithExecutor } from './types';
export { AlertTypeWithExecutor } from './types';

export const plugin = (initContext: PluginInitializerContext) =>
new RuleRegistryPlugin(initContext);
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,12 @@ export class RuleDataPluginService {
return;
} catch (err) {
if (err.meta?.body?.error?.type !== 'illegal_argument_exception') {
/**
* We skip the rollover if we catch anything except for illegal_argument_exception - that's the error
* returned by ES when the mapping update contains a conflicting field definition (e.g., a field changes types).
* We expect to get that error for some mapping changes we might make, and in those cases,
* we want to continue to rollover the index. Other errors are unexpected.
*/
this.options.logger.error(`Failed to PUT mapping for alias ${alias}: ${err.message}`);
return;
}
Expand All @@ -161,6 +167,10 @@ export class RuleDataPluginService {
new_index: newIndexName,
});
} catch (e) {
/**
* If we catch resource_already_exists_exception, that means that the index has been
* rolled over already — nothing to do for us in this case.
*/
if (e?.meta?.body?.error?.type !== 'resource_already_exists_exception') {
this.options.logger.error(`Failed to rollover index for alias ${alias}: ${e.message}`);
}
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/security_solution/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export const DEFAULT_RULE_REFRESH_INTERVAL_VALUE = 60000; // ms
export const DEFAULT_RULE_REFRESH_IDLE_VALUE = 2700000; // ms
export const DEFAULT_RULE_NOTIFICATION_QUERY_SIZE = 100;
export const SAVED_OBJECTS_MANAGEMENT_FEATURE_ID = 'Saved Objects Management';
export const DEFAULT_SPACE_ID = 'default';

// Document path where threat indicator fields are expected. Fields are used
// to enrich signals, and are copied to threat.indicator.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@

/* eslint-disable @typescript-eslint/naming-convention */

import * as t from 'io-ts';

import {
UUID,
NonEmptyString,
enumeration,
IsoDateString,
PositiveIntegerGreaterThanZero,
NonEmptyString,
PositiveInteger,
PositiveIntegerGreaterThanZero,
UUID,
} from '@kbn/securitysolution-io-ts-types';
import * as t from 'io-ts';

export const author = t.array(t.string);
export type Author = t.TypeOf<typeof author>;
Expand Down Expand Up @@ -173,14 +173,18 @@ export type RuleNameOverrideOrUndefined = t.TypeOf<typeof ruleNameOverrideOrUnde
export const status = t.keyof({ open: null, closed: null, 'in-progress': null });
export type Status = t.TypeOf<typeof status>;

export const job_status = t.keyof({
succeeded: null,
failed: null,
'going to run': null,
'partial failure': null,
warning: null,
});
export type JobStatus = t.TypeOf<typeof job_status>;
export enum RuleExecutionStatus {
'succeeded' = 'succeeded',
'failed' = 'failed',
'going to run' = 'going to run',
'partial failure' = 'partial failure',
/**
* @deprecated 'partial failure' status should be used instead
*/
'warning' = 'warning',
}

export const ruleExecutionStatus = enumeration('RuleExecutionStatus', RuleExecutionStatus);

export const conflicts = t.keyof({ abort: null, proceed: null });
export type Conflicts = t.TypeOf<typeof conflicts>;
Expand Down Expand Up @@ -419,4 +423,4 @@ export enum BulkAction {
'duplicate' = 'duplicate',
}

export const bulkAction = t.keyof(BulkAction);
export const bulkAction = enumeration('BulkAction', BulkAction);
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ import {
updated_by,
created_at,
created_by,
job_status,
ruleExecutionStatus,
status_date,
last_success_at,
last_success_message,
Expand Down Expand Up @@ -405,7 +405,7 @@ const responseRequiredFields = {
created_by,
};
const responseOptionalFields = {
status: job_status,
status: ruleExecutionStatus,
status_date,
last_success_at,
last_success_message,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../constants';
import { RuleExecutionStatus } from '../common/schemas';
import { getListArrayMock } from '../types/lists.mock';

import { RulesSchema } from './rules_schema';
Expand Down Expand Up @@ -60,7 +61,7 @@ export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): RulesSchem
type: 'query',
threat: [],
version: 1,
status: 'succeeded',
status: RuleExecutionStatus.succeeded,
status_date: '2020-02-22T16:47:50.047Z',
last_success_at: '2020-02-22T16:47:50.047Z',
last_success_message: 'succeeded',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ import {
timeline_id,
timeline_title,
threshold,
job_status,
ruleExecutionStatus,
status_date,
last_success_at,
last_success_message,
Expand Down Expand Up @@ -164,7 +164,7 @@ export const partialRulesSchema = t.partial({
license,
throttle,
rule_name_override,
status: job_status,
status: ruleExecutionStatus,
status_date,
timestamp_override,
last_success_at,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type {
import { Type } from '@kbn/securitysolution-io-ts-alerting-types';
import { hasLargeValueList } from '@kbn/securitysolution-list-utils';

import { JobStatus, Threshold, ThresholdNormalized } from './schemas/common/schemas';
import { RuleExecutionStatus, Threshold, ThresholdNormalized } from './schemas/common/schemas';

export const hasLargeValueItem = (
exceptionItems: Array<ExceptionListItemSchema | CreateExceptionListItemSchema>
Expand Down Expand Up @@ -64,5 +64,11 @@ export const normalizeThresholdObject = (threshold: Threshold): ThresholdNormali
export const normalizeMachineLearningJobIds = (value: string | string[]): string[] =>
Array.isArray(value) ? value : [value];

export const getRuleStatusText = (value: JobStatus | null | undefined): JobStatus | null =>
value === 'partial failure' ? 'warning' : value != null ? value : null;
export const getRuleStatusText = (
value: RuleExecutionStatus | null | undefined
): RuleExecutionStatus | null =>
value === RuleExecutionStatus['partial failure']
? RuleExecutionStatus.warning
: value != null
? value
: null;
23 changes: 23 additions & 0 deletions x-pack/plugins/security_solution/common/utils/invariant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export class InvariantError extends Error {
name = 'Invariant violation';
}

/**
* Asserts that the provided condition is always true
* and throws an invariant violation error otherwise
*
* @param condition Condition to assert
* @param message Error message to throw if the condition is falsy
*/
export function invariant(condition: unknown, message: string): asserts condition {
if (!condition) {
throw new InvariantError(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
* 2.0.
*/

import { RuleStatusType } from '../../../containers/detection_engine/rules';
import { RuleExecutionStatus } from '../../../../../common/detection_engine/schemas/common/schemas';

export const getStatusColor = (status: RuleStatusType | string | null) =>
export const getStatusColor = (status: RuleExecutionStatus | string | null) =>
status == null
? 'subdued'
: status === 'succeeded'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@
import { EuiFlexItem, EuiHealth, EuiText } from '@elastic/eui';
import React, { memo } from 'react';

import { RuleStatusType } from '../../../containers/detection_engine/rules';
import { FormattedDate } from '../../../../common/components/formatted_date';
import { getEmptyTagValue } from '../../../../common/components/empty_value';
import { getStatusColor } from './helpers';
import * as i18n from './translations';
import { RuleExecutionStatus } from '../../../../../common/detection_engine/schemas/common/schemas';

interface RuleStatusProps {
children: React.ReactNode | null | undefined;
statusDate: string | null | undefined;
status: RuleStatusType | null | undefined;
status: RuleExecutionStatus | null | undefined;
}

const RuleStatusComponent: React.FC<RuleStatusProps> = ({ children, statusDate, status }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
import { savedRuleMock, rulesMock } from '../mock';
import { getRulesSchemaMock } from '../../../../../../common/detection_engine/schemas/response/rules_schema.mocks';
import { RulesSchema } from '../../../../../../common/detection_engine/schemas/response';
import { RuleExecutionStatus } from '../../../../../../common/detection_engine/schemas/common/schemas';

export const updateRule = async ({ rule, signal }: UpdateRulesProps): Promise<RulesSchema> =>
Promise.resolve(getRulesSchemaMock());
Expand Down Expand Up @@ -60,7 +61,7 @@ export const getRuleStatusById = async ({
current_status: {
alert_id: 'alertId',
status_date: 'mm/dd/yyyyTHH:MM:sssz',
status: 'succeeded',
status: RuleExecutionStatus.succeeded,
last_failure_at: null,
last_success_at: 'mm/dd/yyyyTHH:MM:sssz',
last_failure_message: null,
Expand All @@ -86,7 +87,7 @@ export const getRulesStatusByIds = async ({
current_status: {
alert_id: 'alertId',
status_date: 'mm/dd/yyyyTHH:MM:sssz',
status: 'succeeded',
status: RuleExecutionStatus.succeeded,
last_failure_at: null,
last_success_at: 'mm/dd/yyyyTHH:MM:sssz',
last_failure_message: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import {
timestamp_override,
threshold,
BulkAction,
ruleExecutionStatus,
RuleExecutionStatus,
} from '../../../../../common/detection_engine/schemas/common/schemas';
import {
CreateRulesSchema,
Expand Down Expand Up @@ -75,14 +77,6 @@ const MetaRule = t.intersection([
}),
]);

const StatusTypes = t.union([
t.literal('succeeded'),
t.literal('failed'),
t.literal('going to run'),
t.literal('partial failure'),
t.literal('warning'),
]);

// TODO: make a ticket
export const RuleSchema = t.intersection([
t.type({
Expand Down Expand Up @@ -130,7 +124,7 @@ export const RuleSchema = t.intersection([
query: t.string,
rule_name_override,
saved_id: t.string,
status: StatusTypes,
status: ruleExecutionStatus,
status_date: t.string,
threshold,
threat_query,
Expand Down Expand Up @@ -274,17 +268,10 @@ export interface RuleStatus {
current_status: RuleInfoStatus;
failures: RuleInfoStatus[];
}

export type RuleStatusType =
| 'failed'
| 'going to run'
| 'succeeded'
| 'partial failure'
| 'warning';
export interface RuleInfoStatus {
alert_id: string;
status_date: string;
status: RuleStatusType | null;
status: RuleExecutionStatus | null;
last_failure_at: string | null;
last_success_at: string | null;
last_failure_message: string | null;
Expand Down
Loading

0 comments on commit fddd9d7

Please sign in to comment.