Skip to content

Commit

Permalink
[Security Solution] Expand prebuilt rules install/update workflow tes…
Browse files Browse the repository at this point in the history
…t coverage (elastic#155241)

## Summary

Extends test coverage for the current Prebuilt Rules installation and
update workflows, in the Rules Management area.

Follows the test plan:
https://docs.google.com/document/d/1d_1DYnHlnCaPznWTjeCxhoaRUwxc2O_V0LToAPG0xLE/edit#heading=h.y4vywfmfu3ef

Other changes besides the new tests:
- Integration tests related to prebuilt rules were moved to a new
`prebuilt_rules` dir from their old `group1` dir.
- Existing Cypress tests related to prebuilt rules were renamed to
`prebuilt_rules_management.cy.ts` to differentiate those tests to the
new tests related to notifications, installation and updates.
- Prevented the installation of the +700 prebuilt rules in test suites
where it is not necessary. Replaced that with installing a low number of
mock prebuilt rules, which enables to test the same functionality.
- Unskipping tests in
[rules_selection.cy.ts](https://github.com/elastic/kibana/blob/3d146298a43e1ba24d83e0ede2758b87e826d0b6/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_selection.cy.ts#L34).
See
[explanation](elastic#154694 (comment)).

### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <[email protected]>
(cherry picked from commit d6d4c64)
  • Loading branch information
jpdjere committed Jul 3, 2023
1 parent 8739470 commit 0705471
Show file tree
Hide file tree
Showing 35 changed files with 955 additions and 35 deletions.
3 changes: 3 additions & 0 deletions .buildkite/ftr_configs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,9 @@ enabled:
- x-pack/test/detection_engine_api_integration/security_and_spaces/group9/config.ts
- x-pack/test/detection_engine_api_integration/security_and_spaces/group10/config.ts
- x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/config.ts
- x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/config.ts
- x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/config.ts
- x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/config.ts
- x-pack/test/encrypted_saved_objects_api_integration/config.ts
- x-pack/test/examples/config.ts
- x-pack/test/fleet_api_integration/config.agent.ts
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 @@ -162,6 +162,7 @@ export const ALERTS_PATH = '/alerts' as const;
export const ALERT_DETAILS_REDIRECT_PATH = `${ALERTS_PATH}/redirect` as const;
export const RULES_PATH = '/rules' as const;
export const RULES_ADD_PATH = `${RULES_PATH}/add_rules` as const;
export const RULES_UPDATES = `${RULES_PATH}/updates` as const;
export const RULES_CREATE_PATH = `${RULES_PATH}/create` as const;
export const EXCEPTIONS_PATH = '/exceptions' as const;
export const EXCEPTION_LIST_DETAIL_PATH = `${EXCEPTIONS_PATH}/details/:detailName` as const;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/*
* 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 { BulkInstallPackageInfo } from '@kbn/fleet-plugin/common';
import type { Rule } from '../../../public/detection_engine/rule_management/logic/types';
import { createRuleAssetSavedObject } from '../../helpers/rules';
import {
GO_BACK_TO_RULES_TABLE_BUTTON,
INSTALL_ALL_RULES_BUTTON,
INSTALL_SELECTED_RULES_BUTTON,
RULES_MANAGEMENT_TABLE,
RULES_ROW,
RULES_UPDATES_TABLE,
SELECT_ALL_RULES_ON_PAGE_CHECKBOX,
TOASTER,
} from '../../screens/alerts_detection_rules';
import { waitForRulesTableToBeLoaded } from '../../tasks/alerts_detection_rules';
import {
getRuleAssets,
createAndInstallMockedPrebuiltRules,
} from '../../tasks/api_calls/prebuilt_rules';
import { resetRulesTableState, deleteAlertsAndRules, reload } from '../../tasks/common';
import { esArchiverResetKibana } from '../../tasks/es_archiver';
import { login, visitWithoutDateRange } from '../../tasks/login';
import { SECURITY_DETECTIONS_RULES_URL } from '../../urls/navigation';
import {
addElasticRulesButtonClick,
assertRuleUpgradeAvailableAndUpgradeAll,
ruleUpdatesTabClick,
} from '../../tasks/prebuilt_rules';

describe('Detection rules, Prebuilt Rules Installation and Update workflow', () => {
beforeEach(() => {
login();
resetRulesTableState();
deleteAlertsAndRules();
esArchiverResetKibana();

visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL);
});

describe('Installation of prebuilt rules package via Fleet', () => {
beforeEach(() => {
cy.intercept('POST', '/api/fleet/epm/packages/_bulk*').as('installPackage');
waitForRulesTableToBeLoaded();
});

it('should install package from Fleet in the background', () => {
/* Assert that the package in installed from Fleet by checking that
/* the installSource is "registry", as opposed to "bundle" */
cy.wait('@installPackage', {
timeout: 60000,
}).then(({ response }) => {
cy.wrap(response?.statusCode).should('eql', 200);

const packages = response?.body.items.map(({ name, result }: BulkInstallPackageInfo) => ({
name,
installSource: result.installSource,
}));

expect(packages.length).to.have.greaterThan(0);
expect(packages).to.deep.include.members([
{ name: 'security_detection_engine', installSource: 'registry' },
]);
});
});

it('should install rules from the Fleet package when user clicks on CTA', () => {
/* Retrieve how many rules were installed from the Fleet package */
cy.wait('@installPackage', {
timeout: 60000,
}).then(() => {
getRuleAssets().then((response) => {
const ruleIds = response.body.hits.hits.map(
(hit: { _source: { ['security-rule']: Rule } }) => hit._source['security-rule'].rule_id
);

const numberOfRulesToInstall = new Set(ruleIds).size;
addElasticRulesButtonClick();

cy.get(INSTALL_ALL_RULES_BUTTON).click();
cy.get(TOASTER)
.should('be.visible')
.should('have.text', `${numberOfRulesToInstall} rules installed successfully.`);
});
});
});
});

describe('Installation of prebuilt rules', () => {
const RULE_1 = createRuleAssetSavedObject({
name: 'Test rule 1',
rule_id: 'rule_1',
});
const RULE_2 = createRuleAssetSavedObject({
name: 'Test rule 2',
rule_id: 'rule_2',
});
beforeEach(() => {
createAndInstallMockedPrebuiltRules({ rules: [RULE_1, RULE_2], installToKibana: false });
waitForRulesTableToBeLoaded();
});

it('should install selected rules when user clicks on Install selected rules', () => {
addElasticRulesButtonClick();
cy.get(SELECT_ALL_RULES_ON_PAGE_CHECKBOX).click();
cy.get(INSTALL_SELECTED_RULES_BUTTON).click();
cy.get(TOASTER).should('be.visible').should('have.text', `2 rules installed successfully.`);
cy.get(GO_BACK_TO_RULES_TABLE_BUTTON).click();
cy.get(RULES_MANAGEMENT_TABLE).find(RULES_ROW).should('have.length', 2);
cy.get(RULES_MANAGEMENT_TABLE).contains(RULE_1['security-rule'].name);
cy.get(RULES_MANAGEMENT_TABLE).contains(RULE_2['security-rule'].name);
});

it('should fail gracefully with toast error message when request to install rules fails', () => {
/* Stub request to force rules installation to fail */
cy.intercept('POST', '/internal/detection_engine/prebuilt_rules/installation/_perform', {
statusCode: 500,
}).as('installPrebuiltRules');
addElasticRulesButtonClick();
cy.get(SELECT_ALL_RULES_ON_PAGE_CHECKBOX).click();
cy.get(INSTALL_SELECTED_RULES_BUTTON).click();
cy.wait('@installPrebuiltRules');
cy.get(TOASTER).should('be.visible').should('have.text', 'Rule installation failed');
});
});

describe('Update of prebuilt rules', () => {
const RULE_ID = 'rule_id';
const OUTDATED_RULE = createRuleAssetSavedObject({
name: 'Outdated rule',
rule_id: RULE_ID,
version: 1,
});
const UPDATED_RULE = createRuleAssetSavedObject({
name: 'Updated rule',
rule_id: RULE_ID,
version: 2,
});
beforeEach(() => {
/* Create a new rule and install it */
createAndInstallMockedPrebuiltRules({ rules: [OUTDATED_RULE] });
/* Create a second version of the rule, making it available for update */
createAndInstallMockedPrebuiltRules({ rules: [UPDATED_RULE], installToKibana: false });
waitForRulesTableToBeLoaded();
reload();
});

it('should update rule succesfully', () => {
cy.intercept('POST', '/internal/detection_engine/prebuilt_rules/upgrade/_perform').as(
'updatePrebuiltRules'
);
ruleUpdatesTabClick();
assertRuleUpgradeAvailableAndUpgradeAll(OUTDATED_RULE);
cy.get(TOASTER).should('be.visible').should('have.text', `1 rule updated successfully.`);
});

it('should fail gracefully with toast error message when request to update rules fails', () => {
/* Stub request to force rules update to fail */
cy.intercept('POST', '/internal/detection_engine/prebuilt_rules/upgrade/_perform', {
statusCode: 500,
}).as('updatePrebuiltRules');
ruleUpdatesTabClick();
assertRuleUpgradeAvailableAndUpgradeAll(OUTDATED_RULE);
cy.get(TOASTER).should('be.visible').should('have.text', 'Rule update failed');

/* Assert that the rule has not been updated in the UI */
cy.get(RULES_UPDATES_TABLE).should('contain', OUTDATED_RULE['security-rule'].name);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
* 2.0.
*/

import { createRuleAssetSavedObject } from '../../helpers/rules';
import {
COLLAPSED_ACTION_BTN,
ELASTIC_RULES_BTN,
LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN,
ADD_ELASTIC_RULES_BTN,
RULES_EMPTY_PROMPT,
RULES_MONITORING_TAB,
RULES_ROW,
Expand All @@ -29,13 +30,20 @@ import {
waitForRuleToUpdate,
} from '../../tasks/alerts_detection_rules';
import {
excessivelyInstallAllPrebuiltRules,
createAndInstallMockedPrebuiltRules,
getAvailablePrebuiltRulesCount,
} from '../../tasks/api_calls/prebuilt_rules';
import { cleanKibana, deleteAlertsAndRules } from '../../tasks/common';
import { cleanKibana, deleteAlertsAndRules, deletePrebuiltRulesAssets } from '../../tasks/common';
import { login, visitWithoutDateRange } from '../../tasks/login';
import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation';

const rules = Array.from(Array(5)).map((_, i) => {
return createRuleAssetSavedObject({
name: `Test rule ${i + 1}`,
rule_id: `rule_${i + 1}`,
});
});

describe('Prebuilt rules', () => {
before(() => {
cleanKibana();
Expand All @@ -44,8 +52,9 @@ describe('Prebuilt rules', () => {
beforeEach(() => {
login();
deleteAlertsAndRules();
deletePrebuiltRulesAssets();
visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
excessivelyInstallAllPrebuiltRules();
createAndInstallMockedPrebuiltRules({ rules });
cy.reload();
waitForPrebuiltDetectionRulesToBeLoaded();
});
Expand Down Expand Up @@ -114,10 +123,10 @@ describe('Prebuilt rules', () => {
'have.text',
`Elastic rules (${expectedNumberOfRulesAfterDeletion})`
);
cy.get(LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN).should('have.text', `Add Elastic rules1`);
cy.get(ADD_ELASTIC_RULES_BTN).should('have.text', `Add Elastic rules1`);

// Navigate to the prebuilt rule installation page
cy.get(LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN).click();
cy.get(ADD_ELASTIC_RULES_BTN).click();

// Click the "Install all rules" button
cy.get(INSTALL_ALL_RULES_BUTTON).click();
Expand All @@ -144,7 +153,7 @@ describe('Prebuilt rules', () => {
selectNumberOfRules(numberOfRulesToBeSelected);
deleteSelectedRules();

cy.get(LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN).should(
cy.get(ADD_ELASTIC_RULES_BTN).should(
'have.text',
`Add Elastic rules${numberOfRulesToBeSelected}`
);
Expand All @@ -154,7 +163,7 @@ describe('Prebuilt rules', () => {
);

// Navigate to the prebuilt rule installation page
cy.get(LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN).click();
cy.get(ADD_ELASTIC_RULES_BTN).click();

// Click the "Install all rules" button
cy.get(INSTALL_ALL_RULES_BUTTON).click();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* 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 { createRuleAssetSavedObject } from '../../helpers/rules';
import { ADD_ELASTIC_RULES_BTN, RULES_UPDATES_TAB } from '../../screens/alerts_detection_rules';
import { deleteFirstRule, waitForRulesTableToBeLoaded } from '../../tasks/alerts_detection_rules';
import {
installAllPrebuiltRulesRequest,
createAndInstallMockedPrebuiltRules,
} from '../../tasks/api_calls/prebuilt_rules';
import { resetRulesTableState, deleteAlertsAndRules, reload } from '../../tasks/common';
import { login, visitWithoutDateRange } from '../../tasks/login';
import { SECURITY_DETECTIONS_RULES_URL } from '../../urls/navigation';

describe('Detection rules, Prebuilt Rules Installation and Update workflow', () => {
beforeEach(() => {
login();
/* Make sure persisted rules table state is cleared */
resetRulesTableState();
deleteAlertsAndRules();

const RULE_1 = createRuleAssetSavedObject({
name: 'Test rule 1',
rule_id: 'rule_1',
});
createAndInstallMockedPrebuiltRules({ rules: [RULE_1], installToKibana: false });
});

describe('Rules installation notification when no rules have been installed', () => {
beforeEach(() => {
visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL);
});

it('should notify user about prebuilt rules available for installation', () => {
cy.get(ADD_ELASTIC_RULES_BTN).should('be.visible');
});
});

describe('No notifications', () => {
it('should display no install or update notifications when latest rules are installed', () => {
/* Install current available rules */
installAllPrebuiltRulesRequest();
visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL);
waitForRulesTableToBeLoaded();

/* Assert that there are no installation or update notifications */
/* Add Elastic Rules button and Rule Upgrade tabs should not contain a number badge */
cy.get(ADD_ELASTIC_RULES_BTN).should('have.text', 'Add Elastic rules');
cy.get(RULES_UPDATES_TAB).should('have.text', 'Rule Updates');
});
});

describe('Rule installation notification when at least one rule already installed', () => {
beforeEach(() => {
installAllPrebuiltRulesRequest();
/* Create new rule assets with a different rule_id as the one that was */
/* installed before in order to trigger the installation notification */
const RULE_2 = createRuleAssetSavedObject({
name: 'Test rule 2',
rule_id: 'rule_2',
});
const RULE_3 = createRuleAssetSavedObject({
name: 'Test rule 3',
rule_id: 'rule_3',
});

createAndInstallMockedPrebuiltRules({ rules: [RULE_2, RULE_3], installToKibana: false });
visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL);
waitForRulesTableToBeLoaded();
});

it('should notify user about prebuilt rules package available for installation', () => {
const numberOfAvailableRules = 2;
cy.get(ADD_ELASTIC_RULES_BTN).should('be.visible');
cy.get(ADD_ELASTIC_RULES_BTN).should(
'have.text',
`Add Elastic rules${numberOfAvailableRules}`
);
});

it('should notify user a rule is again available for installation if it is deleted', () => {
/* Install available rules, assert that the notification is gone */
/* then delete one rule and assert that the notification is back */
installAllPrebuiltRulesRequest();
reload();
deleteFirstRule();
cy.get(ADD_ELASTIC_RULES_BTN).should('be.visible');
cy.get(ADD_ELASTIC_RULES_BTN).should('have.text', `Add Elastic rules${1}`);
});
});

describe('Rule update notification', () => {
beforeEach(() => {
installAllPrebuiltRulesRequest();
/* Create new rule asset with the same rule_id as the one that was installed */
/* but with a higher version, in order to trigger the update notification */
const UPDATED_RULE = createRuleAssetSavedObject({
name: 'Test rule 1.1 (updated)',
rule_id: 'rule_1',
version: 2,
});
createAndInstallMockedPrebuiltRules({ rules: [UPDATED_RULE], installToKibana: false });
visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL);
waitForRulesTableToBeLoaded();
reload();
});

it('should notify user about prebuilt rules package available for update', () => {
cy.get(RULES_UPDATES_TAB).should('be.visible');
cy.get(RULES_UPDATES_TAB).should('have.text', `Rule Updates${1}`);
});
});
});
Loading

0 comments on commit 0705471

Please sign in to comment.