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] Improves indicator match Cypress tests #94913

Merged
merged 11 commits into from
Mar 25, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
ALERT_RULE_VERSION,
NUMBER_OF_ALERTS,
} from '../../screens/alerts';
import { JSON_LINES } from '../../screens/alerts_details';
import {
CUSTOM_RULES_BTN,
RISK_SCORE,
Expand Down Expand Up @@ -50,14 +51,17 @@ import {
SCHEDULE_DETAILS,
SEVERITY_DETAILS,
TAGS_DETAILS,
TIMELINE_FIELD,
TIMELINE_TEMPLATE_DETAILS,
} from '../../screens/rule_details';

import {
expandFirstAlert,
goToManageAlertsDetectionRules,
waitForAlertsIndexToBeCreated,
waitForAlertsPanelToBeLoaded,
} from '../../tasks/alerts';
import { openJsonView, scrollJsonViewToBottom } from '../../tasks/alerts_details';
import {
changeRowsPerPageTo300,
duplicateFirstRule,
Expand Down Expand Up @@ -98,7 +102,7 @@ import {
import { waitForKibana } from '../../tasks/edit_rule';
import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver';
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
import { goBackToAllRulesTable } from '../../tasks/rule_details';
import { addsFieldsToTimeline, goBackToAllRulesTable } from '../../tasks/rule_details';

import { DETECTIONS_URL, RULE_CREATION } from '../../urls/navigation';

Expand All @@ -114,11 +118,11 @@ describe('indicator match', () => {
before(() => {
cleanKibana();
esArchiverLoad('threat_indicator');
esArchiverLoad('threat_data');
esArchiverLoad('suspicious_source_event');
});
after(() => {
esArchiverUnload('threat_indicator');
esArchiverUnload('threat_data');
esArchiverUnload('suspicious_source_event');
});

describe('Creating new indicator match rules', () => {
Expand Down Expand Up @@ -216,7 +220,7 @@ describe('indicator match', () => {

it('Does NOT show invalidation text when there is a valid "index field" and a valid "indicator index field"', () => {
fillIndicatorMatchRow({
indexField: newThreatIndicatorRule.indicatorMapping,
indexField: newThreatIndicatorRule.indicatorMappingField,
indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
});
getDefineContinueButton().click();
Expand All @@ -235,7 +239,7 @@ describe('indicator match', () => {

it('Shows invalidation text when there is a valid "index field" and an invalid "indicator index field"', () => {
fillIndicatorMatchRow({
indexField: newThreatIndicatorRule.indicatorMapping,
indexField: newThreatIndicatorRule.indicatorMappingField,
indicatorIndexField: 'non-existent-value',
validColumns: 'indexField',
});
Expand All @@ -245,7 +249,7 @@ describe('indicator match', () => {

it('Deletes the first row when you have two rows. Both rows valid rows of "index fields" and valid "indicator index fields". The second row should become the first row', () => {
fillIndicatorMatchRow({
indexField: newThreatIndicatorRule.indicatorMapping,
indexField: newThreatIndicatorRule.indicatorMappingField,
indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
});
getIndicatorAndButton().click();
Expand All @@ -267,14 +271,14 @@ describe('indicator match', () => {

it('Deletes the first row when you have two rows. Both rows have valid "index fields" and invalid "indicator index fields". The second row should become the first row', () => {
fillIndicatorMatchRow({
indexField: newThreatIndicatorRule.indicatorMapping,
indexField: newThreatIndicatorRule.indicatorMappingField,
indicatorIndexField: 'non-existent-value',
validColumns: 'indexField',
});
getIndicatorAndButton().click();
fillIndicatorMatchRow({
rowNumber: 2,
indexField: newThreatIndicatorRule.indicatorMapping,
indexField: newThreatIndicatorRule.indicatorMappingField,
indicatorIndexField: 'second-non-existent-value',
validColumns: 'indexField',
});
Expand Down Expand Up @@ -305,7 +309,7 @@ describe('indicator match', () => {

it('Deletes the first row of data but not the UI elements and the text defaults back to the placeholder of Search', () => {
fillIndicatorMatchRow({
indexField: newThreatIndicatorRule.indicatorMapping,
indexField: newThreatIndicatorRule.indicatorMappingField,
indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
});
getIndicatorDeleteButton().click();
Expand All @@ -317,7 +321,7 @@ describe('indicator match', () => {

it('Deletes the second row when you have three rows. The first row is valid data, the second row is invalid data, and the third row is valid data. Third row should shift up correctly', () => {
fillIndicatorMatchRow({
indexField: newThreatIndicatorRule.indicatorMapping,
indexField: newThreatIndicatorRule.indicatorMappingField,
indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
});
getIndicatorAndButton().click();
Expand All @@ -330,16 +334,22 @@ describe('indicator match', () => {
getIndicatorAndButton().click();
fillIndicatorMatchRow({
rowNumber: 3,
indexField: newThreatIndicatorRule.indicatorMapping,
indexField: newThreatIndicatorRule.indicatorMappingField,
indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
});
getIndicatorDeleteButton(2).click();
getIndicatorIndexComboField(1).should('text', newThreatIndicatorRule.indicatorMapping);
getIndicatorIndexComboField(1).should(
'text',
newThreatIndicatorRule.indicatorMappingField
);
getIndicatorMappingComboField(1).should(
'text',
newThreatIndicatorRule.indicatorIndexField
);
getIndicatorIndexComboField(2).should('text', newThreatIndicatorRule.indicatorMapping);
getIndicatorIndexComboField(2).should(
'text',
newThreatIndicatorRule.indicatorMappingField
);
getIndicatorMappingComboField(2).should(
'text',
newThreatIndicatorRule.indicatorIndexField
Expand All @@ -357,11 +367,14 @@ describe('indicator match', () => {
getIndicatorOrButton().click();
fillIndicatorMatchRow({
rowNumber: 2,
indexField: newThreatIndicatorRule.indicatorMapping,
indexField: newThreatIndicatorRule.indicatorMappingField,
indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
});
getIndicatorDeleteButton().click();
getIndicatorIndexComboField().should('text', newThreatIndicatorRule.indicatorMapping);
getIndicatorIndexComboField().should(
'text',
newThreatIndicatorRule.indicatorMappingField
);
getIndicatorMappingComboField().should(
'text',
newThreatIndicatorRule.indicatorIndexField
Expand Down Expand Up @@ -441,7 +454,7 @@ describe('indicator match', () => {
);
getDetails(INDICATOR_MAPPING).should(
'have.text',
`${newThreatIndicatorRule.indicatorMapping} MATCHES ${newThreatIndicatorRule.indicatorIndexField}`
`${newThreatIndicatorRule.indicatorMappingField} MATCHES ${newThreatIndicatorRule.indicatorIndexField}`
);
getDetails(INDICATOR_INDEX_QUERY).should('have.text', '*:*');
});
Expand Down Expand Up @@ -471,6 +484,74 @@ describe('indicator match', () => {
});
});

describe('Enrichment', () => {
const fieldSearch = 'threat.indicator.matched';
const fields = [
'threat.indicator.matched.atomic',
'threat.indicator.matched.type',
'threat.indicator.matched.field',
];
const expectedFieldsText = [
newThreatIndicatorRule.atomic,
newThreatIndicatorRule.type,
newThreatIndicatorRule.indicatorMappingField,
];

const expectedEnrichment = [
{ line: 4, text: ' "threat": {' },
Comment on lines +500 to +501
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this is an array so the subsequent .forEach reads a little odd.

Suggested change
const expectedEnrichment = [
{ line: 4, text: ' "threat": {' },
const expectedJsonViewRows = [
{ index: 4, text: ' "threat": {' },

{
line: 3,
text:
' "indicator": "{\\"first_seen\\":\\"2021-03-10T08:02:14.000Z\\",\\"file\\":{\\"size\\":80280,\\"pe\\":{},\\"type\\":\\"elf\\",\\"hash\\":{\\"sha256\\":\\"a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3\\",\\"tlsh\\":\\"6D7312E017B517CC1371A8353BED205E9128223972AE35302E97528DF957703BAB2DBE\\",\\"ssdeep\\":\\"1536:87vbq1lGAXSEYQjbChaAU2yU23M51DjZgSQAvcYkFtZTjzBht5:8D+CAXFYQChaAUk5ljnQssL\\",\\"md5\\":\\"9b6c3518a91d23ed77504b5416bfb5b3\\"}},\\"type\\":\\"file\\",\\"matched\\":{\\"atomic\\":\\"a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3\\",\\"field\\":\\"myhash.mysha256\\",\\"id\\":\\"84cf452c1e0375c3d4412cb550bd1783358468a3b3b777da4829d72c7d6fb74f\\",\\"index\\":\\"filebeat-7.12.0-2021.03.10-000001\\",\\"type\\":\\"file\\"}}"',
},
{ line: 2, text: ' }' },
];

before(() => {
cleanKibana();
esArchiverLoad('threat_indicator');
esArchiverLoad('suspicious_source_event');
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
goToManageAlertsDetectionRules();
createCustomIndicatorRule(newThreatIndicatorRule);
reload();
});

after(() => {
esArchiverUnload('threat_indicator');
esArchiverUnload('suspicious_source_event');
});

beforeEach(() => {
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
goToManageAlertsDetectionRules();
goToRuleDetails();
});

it('Displays matches on the timeline', () => {
addsFieldsToTimeline(fieldSearch, fields);

fields.forEach((field, index) => {
cy.get(TIMELINE_FIELD(field)).should('have.text', expectedFieldsText[index]);
});
});

it('Displays enrichment on the JSON view', () => {
expandFirstAlert();
openJsonView();
scrollJsonViewToBottom();

cy.get(JSON_LINES).then((elements) => {
const length = elements.length;
expectedEnrichment.forEach((enrichment) => {
cy.wrap(elements)
.eq(length - enrichment.line)
.should('have.text', enrichment.text);
});
});
});
});

describe('Duplicates the indicator rule', () => {
beforeEach(() => {
cleanKibana();
Expand Down
14 changes: 9 additions & 5 deletions x-pack/plugins/security_solution/cypress/objects/rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,10 @@ export interface OverrideRule extends CustomRule {

export interface ThreatIndicatorRule extends CustomRule {
indicatorIndexPattern: string[];
indicatorMapping: string;
indicatorMappingField: string;
indicatorIndexField: string;
type?: string;
atomic?: string;
}

export interface MachineLearningRule {
Expand Down Expand Up @@ -299,7 +301,7 @@ export const eqlSequenceRule: CustomRule = {
export const newThreatIndicatorRule: ThreatIndicatorRule = {
name: 'Threat Indicator Rule Test',
description: 'The threat indicator rule description.',
index: ['threat-data-*'],
index: ['suspicious-*'],
severity: 'Critical',
riskScore: '20',
tags: ['test', 'threat'],
Expand All @@ -309,9 +311,11 @@ export const newThreatIndicatorRule: ThreatIndicatorRule = {
note: '# test markdown',
runsEvery,
lookBack,
indicatorIndexPattern: ['threat-indicator-*'],
indicatorMapping: 'agent.id',
indicatorIndexField: 'agent.threat',
indicatorIndexPattern: ['filebeat-*'],
indicatorMappingField: 'myhash.mysha256',
indicatorIndexField: 'threatintel.indicator.file.hash.sha256',
type: 'file',
atomic: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3',
timeline,
maxSignals: 100,
};
Expand Down
12 changes: 12 additions & 0 deletions x-pack/plugins/security_solution/cypress/screens/alerts_details.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export const JSON_CONTENT = '[data-test-subj="jsonView"]';

export const JSON_LINES = '.ace_line';

export const JSON_VIEW_TAB = '[data-test-subj="jsonViewTab"]';
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
* 2.0.
*/

export const CLOSE_BTN = '[data-test-subj="close"]';

export const FIELDS_BROWSER_CATEGORIES_COUNT = '[data-test-subj="categories-count"]';

export const FIELDS_BROWSER_CHECKBOX = (id: string) => {
return `[data-test-subj="field-${id}-checkbox`;
return `[data-test-subj="category-table-container"] [data-test-subj="field-${id}-checkbox"]`;
};

export const FIELDS_BROWSER_CONTAINER = '[data-test-subj="fields-browser-container"]';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ export const MACHINE_LEARNING_JOB_STATUS = '[data-test-subj="machineLearningJobS

export const MITRE_ATTACK_DETAILS = 'MITRE ATT&CK';

export const FIELDS_BROWSER_BTN =
'[data-test-subj="events-viewer-panel"] [data-test-subj="show-field-browser"]';

export const REFRESH_BUTTON = '[data-test-subj="refreshButton"]';

export const RULE_ABOUT_DETAILS_HEADER_TOGGLE = '[data-test-subj="stepAboutDetailsToggle"]';
Expand Down Expand Up @@ -92,6 +95,10 @@ export const TIMELINE_TEMPLATE_DETAILS = 'Timeline template';

export const TIMESTAMP_OVERRIDE_DETAILS = 'Timestamp override';

export const TIMELINE_FIELD = (field: string) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have any standards/guidelines for when to use all caps vs when to use camel case, here? I had assumed that functions would use camelcase as in getDetails below.

return `[data-test-subj="draggable-content-${field}"]`;
};

export const getDetails = (title: string) =>
cy.get(DETAILS_TITLE).contains(title).next(DETAILS_DESCRIPTION);

Expand Down
17 changes: 17 additions & 0 deletions x-pack/plugins/security_solution/cypress/tasks/alerts_details.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* 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 { JSON_CONTENT, JSON_VIEW_TAB } from '../screens/alerts_details';

export const openJsonView = () => {
cy.get(JSON_VIEW_TAB).click();
};

export const scrollJsonViewToBottom = () => {
cy.get(JSON_CONTENT).click({ force: true });
cy.get(JSON_CONTENT).type('{pagedown}{pagedown}{pagedown}');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting! Are there no prebuilt scroll helpers in cypress that would work here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is but does not work with that concrete element (I don't know why)

};
Original file line number Diff line number Diff line change
Expand Up @@ -45,23 +45,23 @@ export const createCustomIndicatorRule = (rule: ThreatIndicatorRule, ruleId = 'r
{
entries: [
{
field: rule.indicatorMapping,
field: rule.indicatorMappingField,
type: 'mapping',
value: rule.indicatorMapping,
value: rule.indicatorIndexField,
},
],
},
],
threat_query: '*:*',
threat_language: 'kuery',
threat_filters: [],
threat_index: ['mock*'],
threat_index: rule.indicatorIndexPattern,
threat_indicator_path: '',
from: 'now-17520h',
index: ['exceptions-*'],
index: rule.index,
query: rule.customQuery || '*:*',
language: 'kuery',
enabled: false,
enabled: true,
},
headers: { 'kbn-xsrf': 'cypress-creds' },
failOnStatusCode: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ export const getCustomQueryInvalidationText = () => cy.contains(CUSTOM_QUERY_REQ
export const fillDefineIndicatorMatchRuleAndContinue = (rule: ThreatIndicatorRule) => {
fillIndexAndIndicatorIndexPattern(rule.index, rule.indicatorIndexPattern);
fillIndicatorMatchRow({
indexField: rule.indicatorMapping,
indexField: rule.indicatorMappingField,
indicatorIndexField: rule.indicatorIndexField,
});
getDefineContinueButton().should('exist').click({ force: true });
Expand Down
Loading