Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution][Detections] Implementation of RuleExecutionLogClient #103463

Merged
merged 1 commit into from
Aug 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
xcrzx marked this conversation as resolved.
Show resolved Hide resolved
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> {
xcrzx marked this conversation as resolved.
Show resolved Hide resolved
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);
xcrzx marked this conversation as resolved.
Show resolved Hide resolved

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';
xcrzx marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* 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);
}
}
xcrzx marked this conversation as resolved.
Show resolved Hide resolved
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) =>
xcrzx marked this conversation as resolved.
Show resolved Hide resolved
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