Skip to content

Commit

Permalink
[Security Solution][Platform] - Update rule exported counts to includ…
Browse files Browse the repository at this point in the history
…e total object count (elastic#116338)

### Summary

Addresses elastic#116330.
  • Loading branch information
yctercero authored and kibanamachine committed Nov 4, 2021
1 parent 1064adb commit e1093d7
Show file tree
Hide file tree
Showing 22 changed files with 379 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -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 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 { ExportExceptionDetails } from '.';

export interface ExportExceptionDetailsMock {
listCount?: number;
missingListsCount?: number;
missingLists?: Array<Record<'list_id', string>>;
itemCount?: number;
missingItemCount?: number;
missingItems?: Array<Record<'item_id', string>>;
}

export const getExceptionExportDetailsMock = (
details?: ExportExceptionDetailsMock
): ExportExceptionDetails => ({
exported_exception_list_count: details?.listCount ?? 0,
exported_exception_list_item_count: details?.itemCount ?? 0,
missing_exception_list_item_count: details?.missingItemCount ?? 0,
missing_exception_list_items: details?.missingItems ?? [],
missing_exception_lists: details?.missingLists ?? [],
missing_exception_lists_count: details?.missingListsCount ?? 0,
});
Original file line number Diff line number Diff line change
@@ -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 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 { pipe } from 'fp-ts/lib/pipeable';
import { left } from 'fp-ts/lib/Either';
import { getExceptionExportDetailsMock } from './index.mock';
import { exportExceptionDetailsSchema, ExportExceptionDetails } from '.';
import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils';

describe('exportExceptionDetails', () => {
test('it should validate export meta', () => {
const payload = getExceptionExportDetailsMock();
const decoded = exportExceptionDetailsSchema.decode(payload);
const message = pipe(decoded, foldLeftRight);

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

test('it should strip out extra keys', () => {
const payload: ExportExceptionDetails & {
extraKey?: string;
} = getExceptionExportDetailsMock();
payload.extraKey = 'some extra key';
const decoded = exportExceptionDetailsSchema.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(getExceptionExportDetailsMock());
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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';
import { NonEmptyString } from '@kbn/securitysolution-io-ts-types';

export const exportExceptionDetails = {
exported_exception_list_count: t.number,
exported_exception_list_item_count: t.number,
missing_exception_list_item_count: t.number,
missing_exception_list_items: t.array(
t.exact(
t.type({
item_id: NonEmptyString,
})
)
),
missing_exception_lists: t.array(
t.exact(
t.type({
list_id: NonEmptyString,
})
)
),
missing_exception_lists_count: t.number,
};

export const exportExceptionDetailsSchema = t.exact(t.type(exportExceptionDetails));

export type ExportExceptionDetails = t.TypeOf<typeof exportExceptionDetailsSchema>;
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export * from './entry_match';
export * from './entry_match_any';
export * from './entry_match_wildcard';
export * from './entry_nested';
export * from './exception_export_details';
export * from './exception_list';
export * from './exception_list_item_type';
export * from './filter';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { ExportExceptionDetails } from '@kbn/securitysolution-io-ts-list-types';

export interface ExportExceptionDetailsMock {
listCount?: number;
missingListsCount?: number;
missingLists?: Array<Record<'list_id', string>>;
itemCount?: number;
missingItemCount?: number;
missingItems?: Array<Record<'item_id', string>>;
}

export const getExceptionExportDetailsMock = (
details?: ExportExceptionDetailsMock
): ExportExceptionDetails => ({
exported_exception_list_count: details?.listCount ?? 0,
exported_exception_list_item_count: details?.itemCount ?? 0,
missing_exception_list_item_count: details?.missingItemCount ?? 0,
missing_exception_list_items: details?.missingItems ?? [],
missing_exception_lists: details?.missingLists ?? [],
missing_exception_lists_count: details?.missingListsCount ?? 0,
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {
ExceptionListItemTypeOrUndefined,
ExceptionListType,
ExceptionListTypeOrUndefined,
ExportExceptionDetails,
FilterOrUndefined,
Id,
IdOrUndefined,
Expand Down Expand Up @@ -229,12 +230,5 @@ export interface ExportExceptionListAndItemsOptions {

export interface ExportExceptionListAndItemsReturn {
exportData: string;
exportDetails: {
exported_exception_list_count: number;
exported_exception_list_item_count: number;
missing_exception_list_item_count: number;
missing_exception_list_items: string[];
missing_exception_lists: string[];
missing_exception_lists_count: number;
};
exportDetails: ExportExceptionDetails;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import type {
ExportExceptionDetails,
IdOrUndefined,
ListIdOrUndefined,
NamespaceType,
Expand All @@ -25,14 +26,7 @@ interface ExportExceptionListAndItemsOptions {

export interface ExportExceptionListAndItemsReturn {
exportData: string;
exportDetails: {
exported_exception_list_count: number;
exported_exception_list_item_count: number;
missing_exception_list_item_count: number;
missing_exception_list_items: string[];
missing_exception_lists: string[];
missing_exception_lists_count: number;
};
exportDetails: ExportExceptionDetails;
}

export const exportExceptionListAndItems = async ({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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 { ExportRulesDetails } from './export_rules_details_schema';
import {
ExportExceptionDetailsMock,
getExceptionExportDetailsMock,
} from '../../../../../lists/common/schemas/response/exception_export_details_schema.mock';

interface RuleDetailsMock {
totalCount?: number;
rulesCount?: number;
missingCount?: number;
missingRules?: Array<Record<'rule_id', string>>;
}

export const getOutputDetailsSample = (ruleDetails?: RuleDetailsMock): ExportRulesDetails => ({
exported_count: ruleDetails?.totalCount ?? 0,
exported_rules_count: ruleDetails?.rulesCount ?? 0,
missing_rules: ruleDetails?.missingRules ?? [],
missing_rules_count: ruleDetails?.missingCount ?? 0,
});

export const getOutputDetailsSampleWithExceptions = (
ruleDetails?: RuleDetailsMock,
exceptionDetails?: ExportExceptionDetailsMock
): ExportRulesDetails => ({
...getOutputDetailsSample(ruleDetails),
...getExceptionExportDetailsMock(exceptionDetails),
});

export const getSampleDetailsAsNdjson = (sample: ExportRulesDetails): string => {
return `${JSON.stringify(sample)}\n`;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

/*
* 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 { pipe } from 'fp-ts/lib/pipeable';
import { left } from 'fp-ts/lib/Either';
import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils';

import {
getOutputDetailsSample,
getOutputDetailsSampleWithExceptions,
} from './export_rules_details_schema.mock';
import {
ExportRulesDetails,
exportRulesDetailsWithExceptionsSchema,
} from './export_rules_details_schema';

describe('exportRulesDetailsWithExceptionsSchema', () => {
test('it should validate export details response', () => {
const payload = getOutputDetailsSample();
const decoded = exportRulesDetailsWithExceptionsSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);

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

test('it should validate export details with exceptions details response', () => {
const payload = getOutputDetailsSampleWithExceptions();
const decoded = exportRulesDetailsWithExceptionsSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);

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

test('it should strip out extra keys', () => {
const payload: ExportRulesDetails & {
extraKey?: string;
} = getOutputDetailsSample();
payload.extraKey = 'some extra key';
const decoded = exportRulesDetailsWithExceptionsSchema.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(getOutputDetailsSample());
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import * as t from 'io-ts';
import { exportExceptionDetails } from '@kbn/securitysolution-io-ts-list-types';
import { NonEmptyString } from '@kbn/securitysolution-io-ts-types';

const createSchema = <Required extends t.Props, Optional extends t.Props>(
requiredFields: Required,
optionalFields: Optional
) => {
return t.intersection([t.exact(t.type(requiredFields)), t.exact(t.partial(optionalFields))]);
};

export const exportRulesDetails = {
exported_count: t.number,
exported_rules_count: t.number,
missing_rules: t.array(
t.exact(
t.type({
rule_id: NonEmptyString,
})
)
),
missing_rules_count: t.number,
};

const exportRulesDetailsSchema = t.exact(t.type(exportRulesDetails));
export type ExportRulesDetailsSchema = t.TypeOf<typeof exportRulesDetailsSchema>;

// With exceptions
export const exportRulesDetailsWithExceptionsSchema = createSchema(
exportRulesDetails,
exportExceptionDetails
);

export type ExportRulesDetails = t.TypeOf<typeof exportRulesDetailsWithExceptionsSchema>;
62 changes: 59 additions & 3 deletions x-pack/plugins/security_solution/cypress/objects/rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,63 @@ export const getEditedRule = (): CustomRule => ({
});

export const expectedExportedRule = (ruleResponse: Cypress.Response<RulesSchema>): string => {
const jsonrule = ruleResponse.body;

return `{"id":"${jsonrule.id}","updated_at":"${jsonrule.updated_at}","updated_by":"elastic","created_at":"${jsonrule.created_at}","created_by":"elastic","name":"${jsonrule.name}","tags":[],"interval":"100m","enabled":false,"description":"${jsonrule.description}","risk_score":${jsonrule.risk_score},"severity":"${jsonrule.severity}","output_index":".siem-signals-default","author":[],"false_positives":[],"from":"now-50000h","rule_id":"rule_testing","max_signals":100,"risk_score_mapping":[],"severity_mapping":[],"threat":[],"to":"now","references":[],"version":1,"exceptions_list":[],"immutable":false,"type":"query","language":"kuery","index":["exceptions-*"],"query":"${jsonrule.query}","throttle":"no_actions","actions":[]}\n{"exported_rules_count":1,"missing_rules":[],"missing_rules_count":0,"exported_exception_list_count":0,"exported_exception_list_item_count":0,"missing_exception_list_item_count":0,"missing_exception_list_items":[],"missing_exception_lists":[],"missing_exception_lists_count":0}\n`;
const {
id,
updated_at: updatedAt,
updated_by: updatedBy,
created_at: createdAt,
description,
name,
risk_score: riskScore,
severity,
query,
} = ruleResponse.body;
const rule = {
id,
updated_at: updatedAt,
updated_by: updatedBy,
created_at: createdAt,
created_by: 'elastic',
name,
tags: [],
interval: '100m',
enabled: false,
description,
risk_score: riskScore,
severity,
output_index: '.siem-signals-default',
author: [],
false_positives: [],
from: 'now-50000h',
rule_id: 'rule_testing',
max_signals: 100,
risk_score_mapping: [],
severity_mapping: [],
threat: [],
to: 'now',
references: [],
version: 1,
exceptions_list: [],
immutable: false,
type: 'query',
language: 'kuery',
index: ['exceptions-*'],
query,
throttle: 'no_actions',
actions: [],
};
const details = {
exported_count: 1,
exported_rules_count: 1,
missing_rules: [],
missing_rules_count: 0,
exported_exception_list_count: 0,
exported_exception_list_item_count: 0,
missing_exception_list_item_count: 0,
missing_exception_list_items: [],
missing_exception_lists: [],
missing_exception_lists_count: 0,
};

return `${JSON.stringify(rule)}\n${JSON.stringify(details)}\n`;
};
Loading

0 comments on commit e1093d7

Please sign in to comment.