diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index fcba384a9844d..4ce6ea8ec59d9 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -236,6 +236,7 @@ enabled: - 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/detection_engine_api_integration/security_and_spaces/update_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 diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules_install_update_authorization.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules_install_update_authorization.cy.ts new file mode 100644 index 0000000000000..8b211447897c5 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules_install_update_authorization.cy.ts @@ -0,0 +1,124 @@ +/* + * 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 { APP_PATH, RULES_ADD_PATH, RULES_UPDATES } from '../../../common/constants'; +import { createRuleAssetSavedObject } from '../../helpers/rules'; +import { waitForRulesTableToBeLoaded } from '../../tasks/alerts_detection_rules'; +import { createAndInstallMockedPrebuiltRules } from '../../tasks/api_calls/prebuilt_rules'; +import { resetRulesTableState, deleteAlertsAndRules } from '../../tasks/common'; +import { esArchiverResetKibana } from '../../tasks/es_archiver'; +import { login, waitForPageWithoutDateRange } from '../../tasks/login'; +import { SECURITY_DETECTIONS_RULES_URL } from '../../urls/navigation'; +import { ROLES } from '../../../common/test'; +import { + ADD_ELASTIC_RULES_BTN, + getInstallSingleRuleButtonByRuleId, + getUpgradeSingleRuleButtonByRuleId, + INSTALL_ALL_RULES_BUTTON, + RULES_UPDATES_TAB, + RULE_CHECKBOX, + UPGRADE_ALL_RULES_BUTTON, +} from '../../screens/alerts_detection_rules'; + +const RULE_1_ID = 'rule_1'; +const RULE_2_ID = 'rule_2'; +const OUTDATED_RULE_1 = createRuleAssetSavedObject({ + name: 'Outdated rule 1', + rule_id: RULE_1_ID, + version: 1, +}); +const UPDATED_RULE_1 = createRuleAssetSavedObject({ + name: 'Updated rule 1', + rule_id: RULE_1_ID, + version: 2, +}); +const OUTDATED_RULE_2 = createRuleAssetSavedObject({ + name: 'Outdated rule 2', + rule_id: RULE_2_ID, + version: 1, +}); +const UPDATED_RULE_2 = createRuleAssetSavedObject({ + name: 'Updated rule 2', + rule_id: RULE_2_ID, + version: 2, +}); + +const loadPageAsReadOnlyUser = (url: string) => { + login(ROLES.reader); + waitForPageWithoutDateRange(url, ROLES.reader); +}; + +describe('Detection rules, Prebuilt Rules Installation and Update - Authorization/RBAC', () => { + beforeEach(() => { + login(); + resetRulesTableState(); + deleteAlertsAndRules(); + esArchiverResetKibana(); + waitForRulesTableToBeLoaded(); + createAndInstallMockedPrebuiltRules({ rules: [OUTDATED_RULE_1, OUTDATED_RULE_2] }); + }); + + describe('User with read privileges on Security Solution', () => { + 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(() => { + // Now login with read-only user in preparation for test + createAndInstallMockedPrebuiltRules({ rules: [RULE_1, RULE_2], installToKibana: false }); + loadPageAsReadOnlyUser(SECURITY_DETECTIONS_RULES_URL); + waitForRulesTableToBeLoaded(); + }); + + it('should not be able to install prebuilt rules', () => { + // Check that Add Elastic Rules button is disabled + cy.get(ADD_ELASTIC_RULES_BTN).should('be.disabled'); + + // Navigate to Add Elastic Rules page anyways via URL + // and assert that rules cannot be selected and all + // installation buttons are disabled + cy.visit(`${APP_PATH}${RULES_ADD_PATH}`); + cy.get(INSTALL_ALL_RULES_BUTTON).should('be.disabled'); + cy.get(getInstallSingleRuleButtonByRuleId(RULE_1['security-rule'].rule_id)).should( + 'not.exist' + ); + cy.get(RULE_CHECKBOX).should('not.exist'); + }); + }); + + describe('User with read privileges on Security Solution', () => { + beforeEach(() => { + /* Create a second version of the rule, making it available for update */ + createAndInstallMockedPrebuiltRules({ + rules: [UPDATED_RULE_1, UPDATED_RULE_2], + installToKibana: false, + }); + // Now login with read-only user in preparation for test + loadPageAsReadOnlyUser(SECURITY_DETECTIONS_RULES_URL); + waitForRulesTableToBeLoaded(); + }); + + it('should not be able to upgrade prebuilt rules', () => { + // Check that Rule Update tab is not shown + cy.get(RULES_UPDATES_TAB).should('not.exist'); + + // Navigate to Rule Update tab anyways via URL + // and assert that rules cannot be selected and all + // upgrade buttons are disabled + cy.visit(`${APP_PATH}${RULES_UPDATES}`); + cy.get(UPGRADE_ALL_RULES_BUTTON).should('be.disabled'); + cy.get(getUpgradeSingleRuleButtonByRuleId(OUTDATED_RULE_1['security-rule'].rule_id)).should( + 'not.exist' + ); + cy.get(RULE_CHECKBOX).should('not.exist'); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules_install_update_error_handling.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules_install_update_error_handling.cy.ts new file mode 100644 index 0000000000000..b029a934fd1da --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules_install_update_error_handling.cy.ts @@ -0,0 +1,143 @@ +/* + * 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 { waitForRulesTableToBeLoaded } from '../../tasks/alerts_detection_rules'; +import { 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, + assertRuleAvailableForInstallAndInstallOne, + assertRuleAvailableForInstallAndInstallSelected, + assertRuleAvailableForInstallAndInstallAllInPage, + assertRuleAvailableForInstallAndInstallAll, + assertRuleUpgradeAvailableAndUpgradeOne, + assertRuleUpgradeAvailableAndUpgradeSelected, + assertRuleUpgradeAvailableAndUpgradeAllInPage, + assertRuleUpgradeAvailableAndUpgradeAll, + ruleUpdatesTabClick, +} from '../../tasks/prebuilt_rules'; + +describe('Detection rules, Prebuilt Rules Installation and Update - Error handling', () => { + beforeEach(() => { + login(); + resetRulesTableState(); + deleteAlertsAndRules(); + esArchiverResetKibana(); + + visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); + }); + + describe('Installation of prebuilt rules - Should fail gracefully with toast error message when', () => { + 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('installing prebuilt rules one by one', () => { + addElasticRulesButtonClick(); + assertRuleAvailableForInstallAndInstallOne({ rules: [RULE_1], didRequestFail: true }); + }); + + it('installing multiple selected prebuilt rules by selecting them individually', () => { + addElasticRulesButtonClick(); + assertRuleAvailableForInstallAndInstallSelected({ + rules: [RULE_1, RULE_2], + didRequestFail: true, + }); + }); + + it('installing multiple selected prebuilt rules by selecting all in page', () => { + addElasticRulesButtonClick(); + assertRuleAvailableForInstallAndInstallAllInPage({ + rules: [RULE_1, RULE_2], + didRequestFail: true, + }); + }); + + it('installing all available rules at once', () => { + addElasticRulesButtonClick(); + assertRuleAvailableForInstallAndInstallAll({ rules: [RULE_1, RULE_2], didRequestFail: true }); + }); + }); + + describe('Update of prebuilt rules - Should fail gracefully with toast error message when', () => { + const RULE_1_ID = 'rule_1'; + const RULE_2_ID = 'rule_2'; + const OUTDATED_RULE_1 = createRuleAssetSavedObject({ + name: 'Outdated rule 1', + rule_id: RULE_1_ID, + version: 1, + }); + const UPDATED_RULE_1 = createRuleAssetSavedObject({ + name: 'Updated rule 1', + rule_id: RULE_1_ID, + version: 2, + }); + const OUTDATED_RULE_2 = createRuleAssetSavedObject({ + name: 'Outdated rule 2', + rule_id: RULE_2_ID, + version: 1, + }); + const UPDATED_RULE_2 = createRuleAssetSavedObject({ + name: 'Updated rule 2', + rule_id: RULE_2_ID, + version: 2, + }); + beforeEach(() => { + /* Create a new rule and install it */ + createAndInstallMockedPrebuiltRules({ rules: [OUTDATED_RULE_1, OUTDATED_RULE_2] }); + /* Create a second version of the rule, making it available for update */ + createAndInstallMockedPrebuiltRules({ + rules: [UPDATED_RULE_1, UPDATED_RULE_2], + installToKibana: false, + }); + waitForRulesTableToBeLoaded(); + reload(); + }); + + it('upgrading prebuilt rules one by one', () => { + ruleUpdatesTabClick(); + assertRuleUpgradeAvailableAndUpgradeOne({ rules: [OUTDATED_RULE_1], didRequestFail: true }); + }); + + it('upgrading multiple selected prebuilt rules by selecting them individually', () => { + ruleUpdatesTabClick(); + assertRuleUpgradeAvailableAndUpgradeSelected({ + rules: [OUTDATED_RULE_1, OUTDATED_RULE_2], + didRequestFail: true, + }); + }); + + it('upgrading multiple selected prebuilt rules by selecting all in page', () => { + ruleUpdatesTabClick(); + assertRuleUpgradeAvailableAndUpgradeAllInPage({ + rules: [OUTDATED_RULE_1, OUTDATED_RULE_2], + didRequestFail: true, + }); + }); + + it('upgrading all rules with available upgrades at once', () => { + ruleUpdatesTabClick(); + assertRuleUpgradeAvailableAndUpgradeAll({ + rules: [OUTDATED_RULE_1, OUTDATED_RULE_2], + didRequestFail: true, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules_install_update_workflows.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules_install_update_workflows.cy.ts index 355611ba42eea..1a416da168ce9 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules_install_update_workflows.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules_install_update_workflows.cy.ts @@ -12,9 +12,10 @@ import { GO_BACK_TO_RULES_TABLE_BUTTON, INSTALL_ALL_RULES_BUTTON, INSTALL_SELECTED_RULES_BUTTON, - RULES_MANAGEMENT_TABLE, - RULES_ROW, - RULES_UPDATES_TABLE, + NO_RULES_AVAILABLE_FOR_INSTALL_MESSSAGE, + NO_RULES_AVAILABLE_FOR_UPGRADE_MESSSAGE, + RULES_UPDATES_TAB, + RULE_CHECKBOX, SELECT_ALL_RULES_ON_PAGE_CHECKBOX, TOASTER, } from '../../screens/alerts_detection_rules'; @@ -29,6 +30,13 @@ import { login, visitWithoutDateRange } from '../../tasks/login'; import { SECURITY_DETECTIONS_RULES_URL } from '../../urls/navigation'; import { addElasticRulesButtonClick, + assertRuleAvailableForInstallAndInstallOne, + assertRuleAvailableForInstallAndInstallSelected, + assertRuleAvailableForInstallAndInstallAllInPage, + assertRuleAvailableForInstallAndInstallAll, + assertRuleUpgradeAvailableAndUpgradeOne, + assertRuleUpgradeAvailableAndUpgradeSelected, + assertRuleUpgradeAvailableAndUpgradeAllInPage, assertRuleUpgradeAvailableAndUpgradeAll, ruleUpdatesTabClick, } from '../../tasks/prebuilt_rules'; @@ -104,7 +112,7 @@ describe('Detection rules, Prebuilt Rules Installation and Update workflow', () const numberOfRulesToInstall = new Set(ruleIds).size; addElasticRulesButtonClick(); - cy.get(INSTALL_ALL_RULES_BUTTON).click(); + cy.get(INSTALL_ALL_RULES_BUTTON).should('be.enabled').click(); cy.get(TOASTER) .should('be.visible') .should('have.text', `${numberOfRulesToInstall} rules installed successfully.`); @@ -144,17 +152,38 @@ describe('Detection rules, Prebuilt Rules Installation and Update workflow', () beforeEach(() => { createAndInstallMockedPrebuiltRules({ rules: [RULE_1, RULE_2], installToKibana: false }); waitForRulesTableToBeLoaded(); + cy.intercept('POST', '/internal/detection_engine/prebuilt_rules/installation/_perform').as( + 'installPrebuiltRules' + ); }); - it('should install selected rules when user clicks on Install selected rules', () => { + it('should install prebuilt rules one by one', () => { addElasticRulesButtonClick(); - cy.get(SELECT_ALL_RULES_ON_PAGE_CHECKBOX).click(); - cy.get(INSTALL_SELECTED_RULES_BUTTON).click(); + assertRuleAvailableForInstallAndInstallOne({ rules: [RULE_1] }); + }); + + it('should install multiple selected prebuilt rules by selecting them individually', () => { + addElasticRulesButtonClick(); + assertRuleAvailableForInstallAndInstallSelected({ rules: [RULE_1, RULE_2] }); + }); + + it('should install multiple selected prebuilt rules by selecting all in page', () => { + addElasticRulesButtonClick(); + assertRuleAvailableForInstallAndInstallAllInPage({ rules: [RULE_1, RULE_2] }); + }); + + it('should install all available rules at once', () => { + addElasticRulesButtonClick(); + assertRuleAvailableForInstallAndInstallAll({ rules: [RULE_1, RULE_2] }); + }); + + it('should display an empty screen when all available prebuilt rules have been installed', () => { + addElasticRulesButtonClick(); + cy.get(INSTALL_ALL_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); + cy.get(RULE_CHECKBOX).should('not.exist'); + cy.get(NO_RULES_AVAILABLE_FOR_INSTALL_MESSSAGE).should('exist'); + cy.get(GO_BACK_TO_RULES_TABLE_BUTTON).should('exist'); }); it('should fail gracefully with toast error message when request to install rules fails', () => { @@ -170,47 +199,70 @@ describe('Detection rules, Prebuilt Rules Installation and Update workflow', () }); }); - describe('Update of prebuilt rules', () => { - const RULE_ID = 'rule_id'; - const OUTDATED_RULE = createRuleAssetSavedObject({ - name: 'Outdated rule', - rule_id: RULE_ID, + describe('Upgrade of prebuilt rules', () => { + const RULE_1_ID = 'rule_1'; + const RULE_2_ID = 'rule_2'; + const OUTDATED_RULE_1 = createRuleAssetSavedObject({ + name: 'Outdated rule 1', + rule_id: RULE_1_ID, version: 1, }); - const UPDATED_RULE = createRuleAssetSavedObject({ - name: 'Updated rule', - rule_id: RULE_ID, + const UPDATED_RULE_1 = createRuleAssetSavedObject({ + name: 'Updated rule 1', + rule_id: RULE_1_ID, + version: 2, + }); + const OUTDATED_RULE_2 = createRuleAssetSavedObject({ + name: 'Outdated rule 2', + rule_id: RULE_2_ID, + version: 1, + }); + const UPDATED_RULE_2 = createRuleAssetSavedObject({ + name: 'Updated rule 2', + rule_id: RULE_2_ID, version: 2, }); beforeEach(() => { + cy.intercept('POST', '/internal/detection_engine/prebuilt_rules/upgrade/_perform').as( + 'updatePrebuiltRules' + ); /* Create a new rule and install it */ - createAndInstallMockedPrebuiltRules({ rules: [OUTDATED_RULE] }); + createAndInstallMockedPrebuiltRules({ rules: [OUTDATED_RULE_1, OUTDATED_RULE_2] }); /* Create a second version of the rule, making it available for update */ - createAndInstallMockedPrebuiltRules({ rules: [UPDATED_RULE], installToKibana: false }); + createAndInstallMockedPrebuiltRules({ + rules: [UPDATED_RULE_1, UPDATED_RULE_2], + installToKibana: false, + }); waitForRulesTableToBeLoaded(); reload(); }); - it('should update rule succesfully', () => { - cy.intercept('POST', '/internal/detection_engine/prebuilt_rules/upgrade/_perform').as( - 'updatePrebuiltRules' - ); + it('should upgrade prebuilt rules one by one', () => { ruleUpdatesTabClick(); - assertRuleUpgradeAvailableAndUpgradeAll(OUTDATED_RULE); - cy.get(TOASTER).should('be.visible').should('have.text', `1 rule updated successfully.`); + assertRuleUpgradeAvailableAndUpgradeOne({ rules: [OUTDATED_RULE_1] }); }); - 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'); + it('should upgrade multiple selected prebuilt rules by selecting them individually', () => { ruleUpdatesTabClick(); - assertRuleUpgradeAvailableAndUpgradeAll(OUTDATED_RULE); - cy.get(TOASTER).should('be.visible').should('have.text', 'Rule update failed'); + assertRuleUpgradeAvailableAndUpgradeSelected({ rules: [OUTDATED_RULE_1, OUTDATED_RULE_2] }); + }); + + it('should upgrade multiple selected prebuilt rules by selecting all in page', () => { + ruleUpdatesTabClick(); + assertRuleUpgradeAvailableAndUpgradeAllInPage({ rules: [OUTDATED_RULE_1, OUTDATED_RULE_2] }); + }); - /* Assert that the rule has not been updated in the UI */ - cy.get(RULES_UPDATES_TABLE).should('contain', OUTDATED_RULE['security-rule'].name); + it('should upgrade all rules with available upgrades at once', () => { + ruleUpdatesTabClick(); + assertRuleUpgradeAvailableAndUpgradeAll({ rules: [OUTDATED_RULE_1, OUTDATED_RULE_2] }); + cy.get(RULES_UPDATES_TAB).should('not.exist'); + }); + + it('should display an empty screen when all rules with available updates have been upgraded', () => { + ruleUpdatesTabClick(); + assertRuleUpgradeAvailableAndUpgradeAll({ rules: [OUTDATED_RULE_1, OUTDATED_RULE_2] }); + cy.get(RULES_UPDATES_TAB).should('not.exist'); + cy.get(NO_RULES_AVAILABLE_FOR_UPGRADE_MESSSAGE).should('exist'); }); }); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules_notifications.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules_notifications.cy.ts index e5216705ab7d1..9648d565e5f73 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules_notifications.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules_notifications.cy.ts @@ -12,38 +12,41 @@ import { installAllPrebuiltRulesRequest, createAndInstallMockedPrebuiltRules, } from '../../tasks/api_calls/prebuilt_rules'; -import { resetRulesTableState, deleteAlertsAndRules, reload } from '../../tasks/common'; +import { + resetRulesTableState, + deleteAlertsAndRules, + reload, + deletePrebuiltRulesAssets, +} 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', () => { +const RULE_1 = createRuleAssetSavedObject({ + name: 'Test rule 1', + rule_id: 'rule_1', +}); + +describe('Detection rules, Prebuilt Rules Installation and Update Notifications', () => { 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 }); + deletePrebuiltRulesAssets(); }); - describe('Rules installation notification when no rules have been installed', () => { - beforeEach(() => { + describe('No notifications', () => { + it('should NOT display install or update notifications when no prebuilt assets and no rules are installed', () => { visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); + waitForRulesTableToBeLoaded(); + // TODO: test plan asserts "should NOT see a CTA to install prebuilt rules" + // but current behavior is to always show the CTA, even with no prebuilt rule assets installed + // Update that behaviour and then update this test. + cy.get(RULES_UPDATES_TAB).should('not.exist'); }); - 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(); + it('should NOT display install or update notifications when latest rules are installed', () => { + createAndInstallMockedPrebuiltRules({ rules: [RULE_1], installToKibana: true }); visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); waitForRulesTableToBeLoaded(); @@ -55,64 +58,123 @@ describe('Detection rules, Prebuilt Rules Installation and Update workflow', () }); }); - describe('Rule installation notification when at least one rule already installed', () => { + describe('Notifications', () => { 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_1], installToKibana: false }); + }); + + describe('Rules installation notification when no rules have been installed', () => { + beforeEach(() => { + visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); }); - createAndInstallMockedPrebuiltRules({ rules: [RULE_2, RULE_3], installToKibana: false }); - visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); - waitForRulesTableToBeLoaded(); + it('should notify user about prebuilt rules available for installation', () => { + cy.get(ADD_ELASTIC_RULES_BTN).should('be.visible'); + cy.get(ADD_ELASTIC_RULES_BTN).should('have.text', `Add Elastic rules${1}`); + cy.get(RULES_UPDATES_TAB).should('not.exist'); + }); }); - 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}` - ); - }); + describe('Rule installation notification when at least one rule already installed', () => { + beforeEach(() => { + installAllPrebuiltRulesRequest().then(() => { + /* 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 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}`); + it('should notify user about prebuilt rules 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}` + ); + cy.get(RULES_UPDATES_TAB).should('not.exist'); + }); + + 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().then(() => { + 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, + describe('Rule update notification', () => { + beforeEach(() => { + installAllPrebuiltRulesRequest().then(() => { + /* 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', () => { + // No rules available for installation + cy.get(ADD_ELASTIC_RULES_BTN).should('have.text', `Add Elastic rules`); + // But 1 rule available for update + cy.get(RULES_UPDATES_TAB).should('be.visible'); + cy.get(RULES_UPDATES_TAB).should('have.text', `Rule Updates${1}`); }); - 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}`); + describe('Rule installation available and rule update available notifications', () => { + beforeEach(() => { + installAllPrebuiltRulesRequest().then(() => { + /* 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', + }); + /* 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: [RULE_2, UPDATED_RULE], + installToKibana: false, + }); + visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); + waitForRulesTableToBeLoaded(); + }); + }); + + it('should notify user about prebuilt rules available for installation and for upgrade', () => { + // 1 rule available for installation + cy.get(ADD_ELASTIC_RULES_BTN).should('have.text', `Add Elastic rules${1}`); + // 1 rule available for update + cy.get(RULES_UPDATES_TAB).should('be.visible'); + cy.get(RULES_UPDATES_TAB).should('have.text', `Rule Updates${1}`); + }); }); }); }); diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts index 1431fa097b7ac..5ba64892e741a 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts @@ -107,6 +107,8 @@ export const RULES_MONITORING_TABLE = '[data-test-subj="rules-monitoring-table"] export const RULES_UPDATES_TABLE = '[data-test-subj="rules-upgrades-table"]'; +export const ADD_ELASTIC_RULES_TABLE = '[data-test-subj="add-prebuilt-rules-table"]'; + export const RULES_ROW = '.euiTableRow'; export const SEVERITY = '[data-test-subj="severity"]'; @@ -182,3 +184,16 @@ export const RULE_EXECUTION_STATUS_BADGE = '[data-test-subj="ruleExecutionStatus export const EXECUTION_STATUS_FILTER_BUTTON = '[data-test-subj="executionStatusFilterButton"]'; export const EXECUTION_STATUS_FILTER_OPTION = '[data-test-subj="executionStatusFilterOption"]'; + +export const getInstallSingleRuleButtonByRuleId = (ruleId: string) => { + return `[data-test-subj="installSinglePrebuiltRuleButton-${ruleId}"]`; +}; + +export const getUpgradeSingleRuleButtonByRuleId = (ruleId: string) => { + return `[data-test-subj="upgradeSinglePrebuiltRuleButton-${ruleId}"]`; +}; + +export const NO_RULES_AVAILABLE_FOR_INSTALL_MESSSAGE = + '[data-test-subj="noPrebuiltRulesAvailableForInstall"]'; +export const NO_RULES_AVAILABLE_FOR_UPGRADE_MESSSAGE = + '[data-test-subj="noPrebuiltRulesAvailableForUpgrade"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/prebuilt_rules.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/prebuilt_rules.ts index e175fe3345f80..76cb7fb86661b 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/prebuilt_rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/prebuilt_rules.ts @@ -199,6 +199,6 @@ export const createAndInstallMockedPrebuiltRules = ({ createNewRuleAsset({ rule }); }); if (installToKibana) { - installAllPrebuiltRulesRequest(); + return installAllPrebuiltRulesRequest(); } }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/prebuilt_rules.ts b/x-pack/plugins/security_solution/cypress/tasks/prebuilt_rules.ts index 5035b50acaff9..bf9199aa1f81e 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/prebuilt_rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/prebuilt_rules.ts @@ -8,11 +8,21 @@ import { RULES_ADD_PATH, RULES_UPDATES } from '../../common/constants'; import { ADD_ELASTIC_RULES_BTN, - RULES_ROW, + ADD_ELASTIC_RULES_TABLE, + getInstallSingleRuleButtonByRuleId, + getUpgradeSingleRuleButtonByRuleId, + INSTALL_ALL_RULES_BUTTON, + INSTALL_SELECTED_RULES_BUTTON, + RULES_MANAGEMENT_TABLE, RULES_UPDATES_TAB, RULES_UPDATES_TABLE, + RULE_CHECKBOX, + SELECT_ALL_RULES_ON_PAGE_CHECKBOX, + TOASTER, UPGRADE_ALL_RULES_BUTTON, + UPGRADE_SELECTED_RULES_BUTTON, } from '../screens/alerts_detection_rules'; +import { BACK_TO_RULES_TABLE } from '../screens/rule_details'; import type { SAMPLE_PREBUILT_RULE } from './api_calls/prebuilt_rules'; export const addElasticRulesButtonClick = () => { @@ -25,9 +35,190 @@ export const ruleUpdatesTabClick = () => { cy.location('pathname').should('include', RULES_UPDATES); }; -export const assertRuleUpgradeAvailableAndUpgradeAll = (rule: typeof SAMPLE_PREBUILT_RULE) => { - cy.get(RULES_UPDATES_TABLE).find(RULES_ROW).should('have.length', 1); - cy.get(RULES_UPDATES_TABLE).contains(rule['security-rule'].name); +interface RuleInstallUpgradeAssertionPayload { + rules: Array; + didRequestFail?: boolean; +} + +export const assertRuleAvailableForInstallAndInstallOne = ({ + rules, + didRequestFail = false, +}: RuleInstallUpgradeAssertionPayload) => { + interceptInstallationRequestToFail(rules, didRequestFail); + const rule = rules[0]; + cy.get(getInstallSingleRuleButtonByRuleId(rule['security-rule'].rule_id)).click(); + cy.wait('@installPrebuiltRules'); + assertInstallationSuccessOrFailure([rule], didRequestFail); +}; + +export const assertRuleAvailableForInstallAndInstallSelected = ({ + rules, + didRequestFail = false, +}: RuleInstallUpgradeAssertionPayload) => { + interceptInstallationRequestToFail(rules, didRequestFail); + let i = 0; + for (const rule of rules) { + cy.get(RULE_CHECKBOX).eq(i).click(); + cy.get(ADD_ELASTIC_RULES_TABLE).contains(rule['security-rule'].name); + i++; + } + cy.get(INSTALL_SELECTED_RULES_BUTTON).click(); + cy.wait('@installPrebuiltRules'); + assertInstallationSuccessOrFailure(rules, didRequestFail); +}; + +export const assertRuleAvailableForInstallAndInstallAllInPage = ({ + rules, + didRequestFail = false, +}: RuleInstallUpgradeAssertionPayload) => { + interceptInstallationRequestToFail(rules, didRequestFail); + for (const rule of rules) { + cy.get(ADD_ELASTIC_RULES_TABLE).contains(rule['security-rule'].name); + } + cy.get(SELECT_ALL_RULES_ON_PAGE_CHECKBOX).click(); + cy.get(INSTALL_SELECTED_RULES_BUTTON).click(); + cy.wait('@installPrebuiltRules'); + assertInstallationSuccessOrFailure(rules, didRequestFail); +}; + +export const assertRuleAvailableForInstallAndInstallAll = ({ + rules, + didRequestFail = false, +}: RuleInstallUpgradeAssertionPayload) => { + interceptInstallationRequestToFail(rules, didRequestFail); + for (const rule of rules) { + cy.get(ADD_ELASTIC_RULES_TABLE).contains(rule['security-rule'].name); + } + cy.get(INSTALL_ALL_RULES_BUTTON).click(); + cy.wait('@installPrebuiltRules'); + assertInstallationSuccessOrFailure(rules, didRequestFail); +}; + +const assertInstallationSuccessOrFailure = ( + rules: Array, + didRequestFail: boolean +) => { + const rulesString = rules.length > 1 ? 'rules' : 'rule'; + const toastMessage = didRequestFail + ? `${rules.length} ${rulesString} failed to install.` + : `${rules.length} ${rulesString} installed successfully.`; + cy.get(TOASTER).should('be.visible').should('have.text', toastMessage); + if (didRequestFail) { + for (const rule of rules) { + cy.get(ADD_ELASTIC_RULES_TABLE).contains(rule['security-rule'].name); + } + } else { + cy.get(BACK_TO_RULES_TABLE).click(); + for (const rule of rules) { + cy.get(RULES_MANAGEMENT_TABLE).contains(rule['security-rule'].name); + } + } +}; + +const interceptInstallationRequestToFail = ( + rules: Array, + didRequestFail: boolean +) => { + if (didRequestFail) { + cy.intercept('POST', '/internal/detection_engine/prebuilt_rules/installation/_perform', { + body: { + summary: { + succeeded: [], + skipped: [], + failed: rules.length, + }, + }, + }).as('installPrebuiltRules'); + } +}; + +export const assertRuleUpgradeAvailableAndUpgradeOne = ({ + rules, + didRequestFail = false, +}: RuleInstallUpgradeAssertionPayload) => { + interceptUpgradeRequestToFail(rules, didRequestFail); + const rule = rules[0]; + cy.get(getUpgradeSingleRuleButtonByRuleId(rule['security-rule'].rule_id)).click(); + cy.wait('@updatePrebuiltRules'); + assertUpgradeSuccessOrFailure([rule], didRequestFail); +}; + +export const assertRuleUpgradeAvailableAndUpgradeSelected = ({ + rules, + didRequestFail = false, +}: RuleInstallUpgradeAssertionPayload) => { + interceptUpgradeRequestToFail(rules, didRequestFail); + let i = 0; + for (const rule of rules) { + cy.get(RULE_CHECKBOX).eq(i).click(); + cy.get(RULES_UPDATES_TABLE).contains(rule['security-rule'].name); + i++; + } + cy.get(UPGRADE_SELECTED_RULES_BUTTON).click(); + cy.wait('@updatePrebuiltRules'); + assertUpgradeSuccessOrFailure(rules, didRequestFail); +}; + +export const assertRuleUpgradeAvailableAndUpgradeAllInPage = ({ + rules, + didRequestFail = false, +}: RuleInstallUpgradeAssertionPayload) => { + interceptUpgradeRequestToFail(rules, didRequestFail); + for (const rule of rules) { + cy.get(RULES_UPDATES_TABLE).contains(rule['security-rule'].name); + } + cy.get(SELECT_ALL_RULES_ON_PAGE_CHECKBOX).click(); + cy.get(UPGRADE_SELECTED_RULES_BUTTON).click(); + cy.wait('@updatePrebuiltRules'); + assertUpgradeSuccessOrFailure(rules, didRequestFail); +}; + +export const assertRuleUpgradeAvailableAndUpgradeAll = ({ + rules, + didRequestFail = false, +}: RuleInstallUpgradeAssertionPayload) => { + interceptUpgradeRequestToFail(rules, didRequestFail); + for (const rule of rules) { + cy.get(RULES_UPDATES_TABLE).contains(rule['security-rule'].name); + } cy.get(UPGRADE_ALL_RULES_BUTTON).click(); cy.wait('@updatePrebuiltRules'); + assertUpgradeSuccessOrFailure(rules, didRequestFail); +}; + +const assertUpgradeSuccessOrFailure = ( + rules: Array, + didRequestFail: boolean +) => { + const rulesString = rules.length > 1 ? 'rules' : 'rule'; + const toastMessage = didRequestFail + ? `${rules.length} ${rulesString} failed to update.` + : `${rules.length} ${rulesString} updated successfully.`; + cy.get(TOASTER).should('be.visible').should('have.text', toastMessage); + if (didRequestFail) { + for (const rule of rules) { + cy.get(RULES_UPDATES_TABLE).contains(rule['security-rule'].name); + } + } else { + for (const rule of rules) { + cy.get(rule['security-rule'].name).should('not.exist'); + } + } +}; + +const interceptUpgradeRequestToFail = ( + rules: Array, + didRequestFail: boolean +) => { + if (didRequestFail) { + cy.intercept('POST', '/internal/detection_engine/prebuilt_rules/upgrade/_perform', { + body: { + summary: { + succeeded: [], + skipped: [], + failed: rules.length, + }, + }, + }).as('updatePrebuiltRules'); + } }; diff --git a/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/installation_and_upgrade.md b/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/installation_and_upgrade.md index f07c134e37110..09198dd2d9ef8 100644 --- a/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/installation_and_upgrade.md +++ b/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/installation_and_upgrade.md @@ -61,7 +61,7 @@ Then the package gets installed in the background from EPR #### **Scenario: Package is installed via bundled Fleet package in Kibana** -**Automation**: 1 integration test. +**Automation**: 2 integration tests. ```Gherkin Given the package is not installed @@ -173,6 +173,8 @@ TODO: Check why for the legacy API Dmitrii has added 2 integration tests for `ru - `should update outdated prebuilt rules when previous historical versions available` - `should update outdated prebuilt rules when previous historical versions unavailable` +(NOTE: the second scenario tests that, if a new version of a rule is released, it can upgrade the current instance of that rule even if the historical versions of that rule are no longer in the package) + Notes: - Legacy API: @@ -220,7 +222,7 @@ Notes: ### Scenarios for the real package -#### **Scenario: User can install prebuilt rules from scratch, then install new rules and upgrade existing rules from the new pckage** +#### **Scenario: User can install prebuilt rules from scratch, then install new rules and upgrade existing rules from the new package** **Automation**: 1 integration test with real packages. @@ -250,28 +252,27 @@ Then rules returned in this response should exist as alert saved objects ### Rule installation and upgrade notifications on the Rule Management page -#### **Scenario: User is notified when no prebuilt rules are installed and there are rules available to install** +#### **Scenario: User is NOT notified when no prebuilt rules are installed and there are no prebuilt rules assets** **Automation**: 1 e2e test with mock rules + 1 integration test with mock rules for the /status endpoint. ```Gherkin Given no prebuilt rules are installed in Kibana -And there are X prebuilt rules available to install +And no prebuilt rule assets exist When user opens the Rule Management page -Then user should see a CTA to install prebuilt rules -And user should see a number of rules available to install (X) +Then user should NOT see a CTA to install prebuilt rules +And user should NOT see a number of rules available to install And user should NOT see a CTA to upgrade prebuilt rules And user should NOT see a number of rules available to upgrade And user should NOT see the Rule Updates table ``` -#### **Scenario: User is NOT notified when no prebuilt rules are installed and there are no prebuilt rules assets** +#### **Scenario: User is NOT notified when all prebuilt rules are installed and up to date** **Automation**: 1 e2e test with mock rules + 1 integration test with mock rules for the /status endpoint. ```Gherkin -Given no prebuilt rules are installed in Kibana -And no prebuilt rule assets exist +Given all the latest prebuilt rules are installed in Kibana When user opens the Rule Management page Then user should NOT see a CTA to install prebuilt rules And user should NOT see a number of rules available to install @@ -280,15 +281,16 @@ And user should NOT see a number of rules available to upgrade And user should NOT see the Rule Updates table ``` -#### **Scenario: User is NOT notified when all prebuilt rules are installed and up to date** +#### **Scenario: User is notified when no prebuilt rules are installed and there are rules available to install** **Automation**: 1 e2e test with mock rules + 1 integration test with mock rules for the /status endpoint. ```Gherkin -Given all the latest prebuilt rules are installed in Kibana +Given no prebuilt rules are installed in Kibana +And there are X prebuilt rules available to install When user opens the Rule Management page -Then user should NOT see a CTA to install prebuilt rules -And user should NOT see a number of rules available to install +Then user should see a CTA to install prebuilt rules +And user should see a number of rules available to install (X) And user should NOT see a CTA to upgrade prebuilt rules And user should NOT see a number of rules available to upgrade And user should NOT see the Rule Updates table @@ -323,7 +325,6 @@ Then user should NOT see a CTA to install prebuilt rules And user should NOT see a number of rules available to install And user should see a CTA to upgrade prebuilt rules And user should see the number of rules available to upgrade (Z) -And user should see the Rule Updates table ``` #### **Scenario: User is notified when both rules to install and upgrade are available** @@ -339,7 +340,6 @@ Then user should see a CTA to install prebuilt rules And user should see the number of rules available to install (Y) And user should see a CTA to upgrade prebuilt rules And user should see the number of rules available to upgrade (Z) -And user should see the Rule Updates table ``` #### **Scenario: User is notified after a prebuilt rule gets deleted** @@ -533,7 +533,7 @@ Then user should NOT see the Rule Updates tab until the package installation is #### **Scenario: Error is handled when any operation on prebuilt rules fails** -**Automation**: unit tests. +**Automation**: e2e test with mock rules ```Gherkin When user is prebuilt rules diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/translations.ts index 965bb48a8c979..87580af582a16 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/translations.ts @@ -29,7 +29,7 @@ export const INSTALL_RULE_SKIPPED = (skipped: number) => export const INSTALL_RULE_FAILED = (failed: number) => i18n.translate('xpack.securitySolution.detectionEngine.prebuiltRules.toast.installRuleFailed', { - defaultMessage: '{failed, plural, one {# rule has} other {# rules have}} failed to install.', + defaultMessage: '{failed, plural, one {# rule} other {# rules}} failed to install.', values: { failed }, }); @@ -55,6 +55,6 @@ export const UPGRADE_RULE_SKIPPED = (skipped: number) => export const UPGRADE_RULE_FAILED = (failed: number) => i18n.translate('xpack.securitySolution.detectionEngine.prebuiltRules.toast.upgradeRuleFailed', { - defaultMessage: '{failed, plural, one {# rule has} other {# rules have}} failed to update.', + defaultMessage: '{failed, plural, one {# rule} other {# rules}} failed to update.', values: { failed }, }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_no_items_message.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_no_items_message.tsx index 222ab543a7cd9..d40f84d888e83 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_no_items_message.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_no_items_message.tsx @@ -30,6 +30,7 @@ const AddPrebuiltRulesTableNoItemsMessageComponent = () => { title={

{i18n.NO_RULES_AVAILABLE_FOR_INSTALL}

} titleSize="s" body={i18n.NO_RULES_AVAILABLE_FOR_INSTALL_BODY} + data-test-subj="noPrebuiltRulesAvailableForInstall" /> diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/use_add_prebuilt_rules_table_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/use_add_prebuilt_rules_table_columns.tsx index 3026dc654c7a6..cbc4ae369c881 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/use_add_prebuilt_rules_table_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/use_add_prebuilt_rules_table_columns.tsx @@ -98,6 +98,7 @@ const createInstallButtonColumn = ( size="s" disabled={isInstallButtonDisabled} onClick={() => installOneRule(ruleId)} + data-test-subj={`installSinglePrebuiltRuleButton-${ruleId}`} > {isRuleInstalling ? : i18n.INSTALL_RULE_BUTTON} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table.tsx index 2c1eee1bf08b2..c1468b458e339 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table.tsx @@ -29,6 +29,7 @@ const NO_ITEMS_MESSAGE = ( title={

{i18n.NO_RULES_AVAILABLE_FOR_UPGRADE}

} titleSize="s" body={i18n.NO_RULES_AVAILABLE_FOR_UPGRADE_BODY} + data-test-subj="noPrebuiltRulesAvailableForUpgrade" /> ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx index c9203e763299f..3f71325cbddc8 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx @@ -98,6 +98,7 @@ const createUpgradeButtonColumn = ( size="s" disabled={isUpgradeButtonDisabled} onClick={() => upgradeOneRule(ruleId)} + data-test-subj={`upgradeSinglePrebuiltRuleButton-${ruleId}`} > {isRuleUpgrading ? : i18n.UPDATE_RULE_BUTTON} diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/install_latest_bundled_prebuilt_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/install_latest_bundled_prebuilt_rules.ts index c92dc5532255e..97717f00773d9 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/install_latest_bundled_prebuilt_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/install_latest_bundled_prebuilt_rules.ts @@ -11,12 +11,10 @@ import { REPO_ROOT } from '@kbn/repo-info'; import JSON5 from 'json5'; import expect from 'expect'; import { PackageSpecManifest } from '@kbn/fleet-plugin/common'; +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { - deleteAllPrebuiltRuleAssets, - deleteAllRules, - getPrebuiltRulesAndTimelinesStatus, -} from '../../utils'; +import { deleteAllPrebuiltRuleAssets, deleteAllRules } from '../../utils'; +import { getPrebuiltRulesStatus } from '../../utils/prebuilt_rules/get_prebuilt_rules_status'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { @@ -52,10 +50,10 @@ export default ({ getService }: FtrProviderContext): void => { it('should install prebuilt rules from the package that comes bundled with Kibana', async () => { // Verify that status is empty before package installation - const statusBeforePackageInstallation = await getPrebuiltRulesAndTimelinesStatus(supertest); - expect(statusBeforePackageInstallation.rules_installed).toBe(0); - expect(statusBeforePackageInstallation.rules_not_installed).toBe(0); - expect(statusBeforePackageInstallation.rules_not_updated).toBe(0); + const statusBeforePackageInstallation = await getPrebuiltRulesStatus(supertest); + expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_installed).toBe(0); + expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_to_install).toBe(0); + expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_to_upgrade).toBe(0); const EPM_URL = `/api/fleet/epm/packages/security_detection_engine/99.0.0`; @@ -69,11 +67,13 @@ export default ({ getService }: FtrProviderContext): void => { // As opposed to "registry" expect(bundledInstallResponse.body._meta.install_source).toBe('bundled'); + await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); + // Verify that status is updated after package installation - const statusAfterPackageInstallation = await getPrebuiltRulesAndTimelinesStatus(supertest); - expect(statusAfterPackageInstallation.rules_installed).toBe(0); - expect(statusAfterPackageInstallation.rules_not_installed).toBeGreaterThan(0); - expect(statusAfterPackageInstallation.rules_not_updated).toBe(0); + const statusAfterPackageInstallation = await getPrebuiltRulesStatus(supertest); + expect(statusAfterPackageInstallation.stats.num_prebuilt_rules_installed).toBe(0); + expect(statusAfterPackageInstallation.stats.num_prebuilt_rules_to_install).toBeGreaterThan(0); + expect(statusAfterPackageInstallation.stats.num_prebuilt_rules_to_upgrade).toBe(0); }); }); }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/prerelease_packages.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/prerelease_packages.ts index 0079e67c91f7e..b1c32bf0e245e 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/prerelease_packages.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/prerelease_packages.ts @@ -8,12 +8,9 @@ import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; import { DETECTION_ENGINE_RULES_URL_FIND } from '@kbn/security-solution-plugin/common/constants'; import expect from 'expect'; import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { - deleteAllPrebuiltRuleAssets, - deleteAllRules, - getPrebuiltRulesAndTimelinesStatus, - installPrebuiltRulesAndTimelines, -} from '../../utils'; +import { deleteAllPrebuiltRuleAssets, deleteAllRules } from '../../utils'; +import { getPrebuiltRulesStatus } from '../../utils/prebuilt_rules/get_prebuilt_rules_status'; +import { installPrebuiltRules } from '../../utils/prebuilt_rules/install_prebuilt_rules'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { @@ -36,19 +33,19 @@ export default ({ getService }: FtrProviderContext): void => { it('should install latest stable version and ignore prerelease packages', async () => { // Verify that status is empty before package installation - const statusBeforePackageInstallation = await getPrebuiltRulesAndTimelinesStatus(supertest); - expect(statusBeforePackageInstallation.rules_installed).toBe(0); - expect(statusBeforePackageInstallation.rules_not_installed).toBe(0); - expect(statusBeforePackageInstallation.rules_not_updated).toBe(0); + const statusBeforePackageInstallation = await getPrebuiltRulesStatus(supertest); + expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_installed).toBe(0); + expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_to_install).toBe(0); + expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_to_upgrade).toBe(0); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRules(supertest); await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); // Verify that status is updated after package installation - const statusAfterPackageInstallation = await getPrebuiltRulesAndTimelinesStatus(supertest); - expect(statusAfterPackageInstallation.rules_installed).toBe(1); // 1 rule in package 99.0.0 - expect(statusAfterPackageInstallation.rules_not_installed).toBe(0); - expect(statusAfterPackageInstallation.rules_not_updated).toBe(0); + const statusAfterPackageInstallation = await getPrebuiltRulesStatus(supertest); + expect(statusAfterPackageInstallation.stats.num_prebuilt_rules_installed).toBe(1); // 1 rule in package 99.0.0 + expect(statusAfterPackageInstallation.stats.num_prebuilt_rules_to_install).toBe(0); + expect(statusAfterPackageInstallation.stats.num_prebuilt_rules_to_upgrade).toBe(0); // Get installed rules const { body: rulesResponse } = await supertest diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_rules_status.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_rules_status.ts index 6d728e0f8d548..ee5730cee39a8 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_rules_status.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_rules_status.ts @@ -21,6 +21,9 @@ import { createRuleAssetSavedObject, } from '../../utils/prebuilt_rules/create_prebuilt_rule_saved_objects'; import { deleteAllPrebuiltRuleAssets } from '../../utils/prebuilt_rules/delete_all_prebuilt_rule_assets'; +import { getPrebuiltRulesStatus } from '../../utils/prebuilt_rules/get_prebuilt_rules_status'; +import { installPrebuiltRules } from '../../utils/prebuilt_rules/install_prebuilt_rules'; +import { upgradePrebuiltRules } from '../../utils/prebuilt_rules/upgrade_prebuilt_rules'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { @@ -28,208 +31,465 @@ export default ({ getService }: FtrProviderContext): void => { const es = getService('es'); const log = getService('log'); - describe('get_prebuilt_rules_status', () => { - beforeEach(async () => { - await deleteAllPrebuiltRuleAssets(es); - await deleteAllRules(supertest, log); - }); - - it('should return empty structure when no rules package installed', async () => { - const body = await getPrebuiltRulesAndTimelinesStatus(supertest); + describe('Prebuilt Rules status', () => { + describe('get_prebuilt_rules_status', () => { + beforeEach(async () => { + await deleteAllPrebuiltRuleAssets(es); + await deleteAllRules(supertest, log); + }); - expect(body).toMatchObject({ - rules_custom_installed: 0, - rules_installed: 0, - rules_not_installed: 0, - rules_not_updated: 0, + it('should return empty structure when no prebuilt rule assets', async () => { + const { stats } = await getPrebuiltRulesStatus(supertest); + expect(stats).toMatchObject({ + num_prebuilt_rules_installed: 0, + num_prebuilt_rules_to_install: 0, + num_prebuilt_rules_to_upgrade: 0, + num_prebuilt_rules_total_in_package: 0, + }); }); - }); - it('should show that one custom rule is installed when a custom rule is added', async () => { - await createRule(supertest, log, getSimpleRule()); + it('should not update the prebuilt rule status when a custom rule is added', async () => { + await createRule(supertest, log, getSimpleRule()); - const body = await getPrebuiltRulesAndTimelinesStatus(supertest); - expect(body).toMatchObject({ - rules_custom_installed: 1, - rules_installed: 0, - rules_not_installed: 0, - rules_not_updated: 0, + const { stats } = await getPrebuiltRulesStatus(supertest); + expect(stats).toMatchObject({ + num_prebuilt_rules_installed: 0, + num_prebuilt_rules_to_install: 0, + num_prebuilt_rules_to_upgrade: 0, + num_prebuilt_rules_total_in_package: 0, + }); }); - }); - describe(`rule package without historical versions`, () => { - const getRuleAssetSavedObjects = () => [ - createRuleAssetSavedObject({ rule_id: 'rule-1', version: 1 }), - createRuleAssetSavedObject({ rule_id: 'rule-2', version: 2 }), - createRuleAssetSavedObject({ rule_id: 'rule-3', version: 3 }), - createRuleAssetSavedObject({ rule_id: 'rule-4', version: 4 }), - ]; - const RULES_COUNT = 4; - - it('should return the number of rules available to install', async () => { - await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - const body = await getPrebuiltRulesAndTimelinesStatus(supertest); + describe(`rule package without historical versions`, () => { + const getRuleAssetSavedObjects = () => [ + createRuleAssetSavedObject({ rule_id: 'rule-1', version: 1 }), + createRuleAssetSavedObject({ rule_id: 'rule-2', version: 2 }), + createRuleAssetSavedObject({ rule_id: 'rule-3', version: 3 }), + createRuleAssetSavedObject({ rule_id: 'rule-4', version: 4 }), + ]; + const RULES_COUNT = 4; + + it('should return the number of rules available to install', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + const { stats } = await getPrebuiltRulesStatus(supertest); + expect(stats).toMatchObject({ + num_prebuilt_rules_installed: 0, + num_prebuilt_rules_to_install: RULES_COUNT, + num_prebuilt_rules_to_upgrade: 0, + num_prebuilt_rules_total_in_package: RULES_COUNT, + }); + }); - expect(body).toMatchObject({ - rules_custom_installed: 0, - rules_installed: 0, - rules_not_installed: RULES_COUNT, - rules_not_updated: 0, + it('should return the number of installed prebuilt rules after installing them', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(supertest); + + const { stats } = await getPrebuiltRulesStatus(supertest); + expect(stats).toMatchObject({ + num_prebuilt_rules_installed: RULES_COUNT, + num_prebuilt_rules_to_install: 0, + num_prebuilt_rules_to_upgrade: 0, + num_prebuilt_rules_total_in_package: RULES_COUNT, + }); }); - }); - it('should return the number of installed prebuilt rules after installing them', async () => { - await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + it('should notify the user again that a rule is available for install after it is deleted', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(supertest); + await deleteRule(supertest, 'rule-1'); + + const { stats } = await getPrebuiltRulesStatus(supertest); + expect(stats).toMatchObject({ + num_prebuilt_rules_installed: RULES_COUNT - 1, + num_prebuilt_rules_to_install: 1, + num_prebuilt_rules_to_upgrade: 0, + num_prebuilt_rules_total_in_package: RULES_COUNT, + }); + }); - const body = await getPrebuiltRulesAndTimelinesStatus(supertest); - expect(body).toMatchObject({ - rules_custom_installed: 0, - rules_installed: RULES_COUNT, - rules_not_installed: 0, - rules_not_updated: 0, + it('should return available rule updates', async () => { + const ruleAssetSavedObjects = getRuleAssetSavedObjects(); + await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); + await installPrebuiltRules(supertest); + + // Clear previous rule assets + await deleteAllPrebuiltRuleAssets(es); + // Increment the version of one of the installed rules and create the new rule assets + ruleAssetSavedObjects[0]['security-rule'].version += 1; + await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); + + const { stats } = await getPrebuiltRulesStatus(supertest); + expect(stats).toMatchObject({ + num_prebuilt_rules_installed: RULES_COUNT, + num_prebuilt_rules_to_install: 0, + num_prebuilt_rules_to_upgrade: 1, + num_prebuilt_rules_total_in_package: RULES_COUNT, + }); }); - }); - it('should notify the user again that a rule is available for install after it is deleted', async () => { - await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); - await deleteRule(supertest, 'rule-1'); + it('should not return any available update if rule has been successfully upgraded', async () => { + const ruleAssetSavedObjects = getRuleAssetSavedObjects(); + await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); + await installPrebuiltRules(supertest); + + // Clear previous rule assets + await deleteAllPrebuiltRuleAssets(es); + // Increment the version of one of the installed rules and create the new rule assets + ruleAssetSavedObjects[0]['security-rule'].version += 1; + await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); + // Upgrade all rules + await upgradePrebuiltRules(supertest); + + const { stats } = await getPrebuiltRulesStatus(supertest); + expect(stats).toMatchObject({ + num_prebuilt_rules_installed: RULES_COUNT, + num_prebuilt_rules_to_install: 0, + num_prebuilt_rules_to_upgrade: 0, + num_prebuilt_rules_total_in_package: RULES_COUNT, + }); + }); - const body = await getPrebuiltRulesAndTimelinesStatus(supertest); - expect(body).toMatchObject({ - rules_custom_installed: 0, - rules_installed: RULES_COUNT - 1, - rules_not_installed: 1, - rules_not_updated: 0, + it('should not return any updates if none are available', async () => { + const ruleAssetSavedObjects = getRuleAssetSavedObjects(); + await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); + await installPrebuiltRules(supertest); + + // Clear previous rule assets + await deleteAllPrebuiltRuleAssets(es); + // Recreate the rules without bumping any versions + await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); + + const { stats } = await getPrebuiltRulesStatus(supertest); + expect(stats).toMatchObject({ + num_prebuilt_rules_installed: RULES_COUNT, + num_prebuilt_rules_to_install: 0, + num_prebuilt_rules_to_upgrade: 0, + num_prebuilt_rules_total_in_package: RULES_COUNT, + }); }); }); - it('should return available rule updates', async () => { - const ruleAssetSavedObjects = getRuleAssetSavedObjects(); - await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); - await installPrebuiltRulesAndTimelines(supertest); + describe(`rule package with historical versions`, () => { + const getRuleAssetSavedObjects = () => [ + createRuleAssetSavedObject({ rule_id: 'rule-1', version: 1 }), + createRuleAssetSavedObject({ rule_id: 'rule-1', version: 2 }), + createRuleAssetSavedObject({ rule_id: 'rule-2', version: 1 }), + createRuleAssetSavedObject({ rule_id: 'rule-2', version: 2 }), + createRuleAssetSavedObject({ rule_id: 'rule-2', version: 3 }), + ]; + const RULES_COUNT = 2; + + it('should return the number of rules available to install', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + + const { stats } = await getPrebuiltRulesStatus(supertest); + expect(stats).toMatchObject({ + num_prebuilt_rules_installed: 0, + num_prebuilt_rules_to_install: RULES_COUNT, + num_prebuilt_rules_to_upgrade: 0, + num_prebuilt_rules_total_in_package: RULES_COUNT, + }); + }); - // Clear previous rule assets - await deleteAllPrebuiltRuleAssets(es); - // Increment the version of one of the installed rules and create the new rule assets - ruleAssetSavedObjects[0]['security-rule'].version += 1; - await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); + it('should return the number of installed prebuilt rules after installing them', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(supertest); + + const { stats } = await getPrebuiltRulesStatus(supertest); + expect(stats).toMatchObject({ + num_prebuilt_rules_installed: RULES_COUNT, + num_prebuilt_rules_to_install: 0, + num_prebuilt_rules_to_upgrade: 0, + num_prebuilt_rules_total_in_package: RULES_COUNT, + }); + }); - const body = await getPrebuiltRulesAndTimelinesStatus(supertest); - expect(body).toMatchObject({ - rules_custom_installed: 0, - rules_installed: RULES_COUNT, - rules_not_installed: 0, - rules_not_updated: 1, + it('should notify the user again that a rule is available for install after it is deleted', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(supertest); + await deleteRule(supertest, 'rule-1'); + + const { stats } = await getPrebuiltRulesStatus(supertest); + expect(stats).toMatchObject({ + num_prebuilt_rules_installed: RULES_COUNT - 1, + num_prebuilt_rules_to_install: 1, + num_prebuilt_rules_to_upgrade: 0, + num_prebuilt_rules_total_in_package: RULES_COUNT, + }); }); - }); - it('should not return any updates if none are available', async () => { - const ruleAssetSavedObjects = getRuleAssetSavedObjects(); - await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); - await installPrebuiltRulesAndTimelines(supertest); + it('should return available rule updates when previous historical versions available', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(supertest); + + // Add a new version of one of the installed rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ rule_id: 'rule-1', version: 3 }), + ]); + + const { stats } = await getPrebuiltRulesStatus(supertest); + expect(stats).toMatchObject({ + num_prebuilt_rules_installed: RULES_COUNT, + num_prebuilt_rules_to_install: 0, + num_prebuilt_rules_to_upgrade: 1, + num_prebuilt_rules_total_in_package: RULES_COUNT, + }); + }); - // Clear previous rule assets - await deleteAllPrebuiltRuleAssets(es); - // Recreate the rules without bumping any versions - await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); + it('should return available rule updates when previous historical versions unavailable', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(supertest); + + // Delete the previous versions of rule assets + await deleteAllPrebuiltRuleAssets(es); + + // Add a new rule version + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ rule_id: 'rule-1', version: 3 }), + ]); + + const { stats } = await getPrebuiltRulesStatus(supertest); + expect(stats).toMatchObject({ + num_prebuilt_rules_installed: RULES_COUNT, + num_prebuilt_rules_to_install: 0, + num_prebuilt_rules_to_upgrade: 1, + // Two prebuilt rules have been installed, but only 1 rule asset + // is made available after deleting the previous versions + num_prebuilt_rules_total_in_package: 1, + }); + }); - const body = await getPrebuiltRulesAndTimelinesStatus(supertest); - expect(body).toMatchObject({ - rules_custom_installed: 0, - rules_installed: RULES_COUNT, - rules_not_installed: 0, - rules_not_updated: 0, + it('should not return available rule updates after rule has been upgraded', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(supertest); + + // Delete the previous versions of rule assets + await deleteAllPrebuiltRuleAssets(es); + + // Add a new rule version + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ rule_id: 'rule-1', version: 3 }), + ]); + + // Upgrade the rule + await upgradePrebuiltRules(supertest); + + const { stats } = await getPrebuiltRulesStatus(supertest); + expect(stats).toMatchObject({ + num_prebuilt_rules_installed: RULES_COUNT, + num_prebuilt_rules_to_install: 0, + num_prebuilt_rules_to_upgrade: 0, + // Two prebuilt rules have been installed, but only 1 rule asset + // is made available after deleting the previous versions + num_prebuilt_rules_total_in_package: 1, + }); }); }); }); - describe(`rule package with historical versions`, () => { - const getRuleAssetSavedObjects = () => [ - createRuleAssetSavedObject({ rule_id: 'rule-1', version: 1 }), - createRuleAssetSavedObject({ rule_id: 'rule-1', version: 2 }), - createRuleAssetSavedObject({ rule_id: 'rule-2', version: 1 }), - createRuleAssetSavedObject({ rule_id: 'rule-2', version: 2 }), - createRuleAssetSavedObject({ rule_id: 'rule-2', version: 3 }), - ]; - const RULES_COUNT = 2; - - it('should return the number of rules available to install', async () => { - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + describe('get_prebuilt_rules_status - legacy', () => { + beforeEach(async () => { + await deleteAllPrebuiltRuleAssets(es); + await deleteAllRules(supertest, log); + }); + + it('should return empty structure when no rules package installed', async () => { const body = await getPrebuiltRulesAndTimelinesStatus(supertest); expect(body).toMatchObject({ rules_custom_installed: 0, rules_installed: 0, - rules_not_installed: RULES_COUNT, + rules_not_installed: 0, rules_not_updated: 0, }); }); - it('should return the number of installed prebuilt rules after installing them', async () => { - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + it('should show that one custom rule is installed when a custom rule is added', async () => { + await createRule(supertest, log, getSimpleRule()); const body = await getPrebuiltRulesAndTimelinesStatus(supertest); expect(body).toMatchObject({ - rules_custom_installed: 0, - rules_installed: RULES_COUNT, + rules_custom_installed: 1, + rules_installed: 0, rules_not_installed: 0, rules_not_updated: 0, }); }); - it('should notify the user again that a rule is available for install after it is deleted', async () => { - await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); - await deleteRule(supertest, 'rule-1'); + describe(`rule package without historical versions`, () => { + const getRuleAssetSavedObjects = () => [ + createRuleAssetSavedObject({ rule_id: 'rule-1', version: 1 }), + createRuleAssetSavedObject({ rule_id: 'rule-2', version: 2 }), + createRuleAssetSavedObject({ rule_id: 'rule-3', version: 3 }), + createRuleAssetSavedObject({ rule_id: 'rule-4', version: 4 }), + ]; + const RULES_COUNT = 4; + + it('should return the number of rules available to install', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + const body = await getPrebuiltRulesAndTimelinesStatus(supertest); + + expect(body).toMatchObject({ + rules_custom_installed: 0, + rules_installed: 0, + rules_not_installed: RULES_COUNT, + rules_not_updated: 0, + }); + }); - const body = await getPrebuiltRulesAndTimelinesStatus(supertest); - expect(body).toMatchObject({ - rules_custom_installed: 0, - rules_installed: RULES_COUNT - 1, - rules_not_installed: 1, - rules_not_updated: 0, + it('should return the number of installed prebuilt rules after installing them', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRulesAndTimelines(supertest); + + const body = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(body).toMatchObject({ + rules_custom_installed: 0, + rules_installed: RULES_COUNT, + rules_not_installed: 0, + rules_not_updated: 0, + }); }); - }); - it('should return available rule updates when previous historical versions available', async () => { - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + it('should notify the user again that a rule is available for install after it is deleted', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRulesAndTimelines(supertest); + await deleteRule(supertest, 'rule-1'); + + const body = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(body).toMatchObject({ + rules_custom_installed: 0, + rules_installed: RULES_COUNT - 1, + rules_not_installed: 1, + rules_not_updated: 0, + }); + }); - // Add a new version of one of the installed rules - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ - createRuleAssetSavedObject({ rule_id: 'rule-1', version: 3 }), - ]); + it('should return available rule updates', async () => { + const ruleAssetSavedObjects = getRuleAssetSavedObjects(); + await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); + await installPrebuiltRulesAndTimelines(supertest); + + // Clear previous rule assets + await deleteAllPrebuiltRuleAssets(es); + // Increment the version of one of the installed rules and create the new rule assets + ruleAssetSavedObjects[0]['security-rule'].version += 1; + await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); + + const body = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(body).toMatchObject({ + rules_custom_installed: 0, + rules_installed: RULES_COUNT, + rules_not_installed: 0, + rules_not_updated: 1, + }); + }); - const body = await getPrebuiltRulesAndTimelinesStatus(supertest); - expect(body).toMatchObject({ - rules_custom_installed: 0, - rules_installed: RULES_COUNT, - rules_not_installed: 0, - rules_not_updated: 1, + it('should not return any updates if none are available', async () => { + const ruleAssetSavedObjects = getRuleAssetSavedObjects(); + await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); + await installPrebuiltRulesAndTimelines(supertest); + + // Clear previous rule assets + await deleteAllPrebuiltRuleAssets(es); + // Recreate the rules without bumping any versions + await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); + + const body = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(body).toMatchObject({ + rules_custom_installed: 0, + rules_installed: RULES_COUNT, + rules_not_installed: 0, + rules_not_updated: 0, + }); }); }); - it('should return available rule updates when previous historical versions unavailable', async () => { - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + describe(`rule package with historical versions`, () => { + const getRuleAssetSavedObjects = () => [ + createRuleAssetSavedObject({ rule_id: 'rule-1', version: 1 }), + createRuleAssetSavedObject({ rule_id: 'rule-1', version: 2 }), + createRuleAssetSavedObject({ rule_id: 'rule-2', version: 1 }), + createRuleAssetSavedObject({ rule_id: 'rule-2', version: 2 }), + createRuleAssetSavedObject({ rule_id: 'rule-2', version: 3 }), + ]; + const RULES_COUNT = 2; + + it('should return the number of rules available to install', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + const body = await getPrebuiltRulesAndTimelinesStatus(supertest); + + expect(body).toMatchObject({ + rules_custom_installed: 0, + rules_installed: 0, + rules_not_installed: RULES_COUNT, + rules_not_updated: 0, + }); + }); - // Delete the previous versions of rule assets - await deleteAllPrebuiltRuleAssets(es); + it('should return the number of installed prebuilt rules after installing them', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRulesAndTimelines(supertest); + + const body = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(body).toMatchObject({ + rules_custom_installed: 0, + rules_installed: RULES_COUNT, + rules_not_installed: 0, + rules_not_updated: 0, + }); + }); - // Add a new rule version - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ - createRuleAssetSavedObject({ rule_id: 'rule-1', version: 3 }), - ]); + it('should notify the user again that a rule is available for install after it is deleted', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRulesAndTimelines(supertest); + await deleteRule(supertest, 'rule-1'); + + const body = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(body).toMatchObject({ + rules_custom_installed: 0, + rules_installed: RULES_COUNT - 1, + rules_not_installed: 1, + rules_not_updated: 0, + }); + }); - const body = await getPrebuiltRulesAndTimelinesStatus(supertest); - expect(body).toMatchObject({ - rules_custom_installed: 0, - rules_installed: RULES_COUNT, - rules_not_installed: 0, - rules_not_updated: 1, + it('should return available rule updates when previous historical versions available', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRulesAndTimelines(supertest); + + // Add a new version of one of the installed rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ rule_id: 'rule-1', version: 3 }), + ]); + + const body = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(body).toMatchObject({ + rules_custom_installed: 0, + rules_installed: RULES_COUNT, + rules_not_installed: 0, + rules_not_updated: 1, + }); + }); + + it('should return available rule updates when previous historical versions unavailable', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRulesAndTimelines(supertest); + + // Delete the previous versions of rule assets + await deleteAllPrebuiltRuleAssets(es); + + // Add a new rule version + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ rule_id: 'rule-1', version: 3 }), + ]); + + const body = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(body).toMatchObject({ + rules_custom_installed: 0, + rules_installed: RULES_COUNT, + rules_not_installed: 0, + rules_not_updated: 1, + }); }); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/index.ts index 7b376d5986040..67ae12c357351 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/index.ts @@ -12,7 +12,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => { describe('detection engine api security and spaces enabled - Prebuilt Rules', function () { loadTestFile(require.resolve('./get_prebuilt_rules_status')); loadTestFile(require.resolve('./get_prebuilt_timelines_status')); - loadTestFile(require.resolve('./install_prebuilt_rules')); + loadTestFile(require.resolve('./install_and_upgrade_prebuilt_rules')); loadTestFile(require.resolve('./fleet_integration')); }); }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/install_and_upgrade_prebuilt_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/install_and_upgrade_prebuilt_rules.ts new file mode 100644 index 0000000000000..c36b81f93cf7c --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/install_and_upgrade_prebuilt_rules.ts @@ -0,0 +1,462 @@ +/* + * 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 expect from 'expect'; +import { DETECTION_ENGINE_RULES_URL_FIND } from '@kbn/security-solution-plugin/common/constants'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { + deleteAllRules, + deleteAllTimelines, + deleteRule, + getPrebuiltRulesAndTimelinesStatus, +} from '../../utils'; +import { + createHistoricalPrebuiltRuleAssetSavedObjects, + createPrebuiltRuleAssetSavedObjects, + createRuleAssetSavedObject, +} from '../../utils/prebuilt_rules/create_prebuilt_rule_saved_objects'; +import { deleteAllPrebuiltRuleAssets } from '../../utils/prebuilt_rules/delete_all_prebuilt_rule_assets'; +import { installPrebuiltRulesAndTimelines } from '../../utils/prebuilt_rules/install_prebuilt_rules_and_timelines'; +import { installPrebuiltRules } from '../../utils/prebuilt_rules/install_prebuilt_rules'; +import { getPrebuiltRulesStatus } from '../../utils/prebuilt_rules/get_prebuilt_rules_status'; +import { upgradePrebuiltRules } from '../../utils/prebuilt_rules/upgrade_prebuilt_rules'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); + + describe('install and upgrade prebuilt rules with mock rule assets', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllTimelines(es); + await deleteAllPrebuiltRuleAssets(es); + }); + + describe(`rule package without historical versions`, () => { + const getRuleAssetSavedObjects = () => [ + createRuleAssetSavedObject({ rule_id: 'rule-1', version: 1 }), + createRuleAssetSavedObject({ rule_id: 'rule-2', version: 2 }), + createRuleAssetSavedObject({ rule_id: 'rule-3', version: 3 }), + createRuleAssetSavedObject({ rule_id: 'rule-4', version: 4 }), + ]; + const RULES_COUNT = 4; + + describe('using legacy endpoint', () => { + it('should install prebuilt rules', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + const body = await installPrebuiltRulesAndTimelines(supertest); + + expect(body.rules_installed).toBe(RULES_COUNT); + expect(body.rules_updated).toBe(0); + }); + + it('should install correct prebuilt rule versions', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRulesAndTimelines(supertest); + + // Get installed rules + const { body: rulesResponse } = await supertest + .get(DETECTION_ENGINE_RULES_URL_FIND) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + // Check that all prebuilt rules were actually installed and their versions match the latest + expect(rulesResponse.total).toBe(RULES_COUNT); + expect(rulesResponse.data).toEqual( + expect.arrayContaining([ + expect.objectContaining({ rule_id: 'rule-1', version: 1 }), + expect.objectContaining({ rule_id: 'rule-2', version: 2 }), + expect.objectContaining({ rule_id: 'rule-3', version: 3 }), + expect.objectContaining({ rule_id: 'rule-4', version: 4 }), + ]) + ); + }); + + it('should install missing prebuilt rules', async () => { + // Install all prebuilt detection rules + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRulesAndTimelines(supertest); + + // Delete one of the installed rules + await deleteRule(supertest, 'rule-1'); + + // Check that one prebuilt rule is missing + const statusResponse = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(statusResponse.rules_not_installed).toBe(1); + + // Call the install prebuilt rules again and check that the missing rule was installed + const response = await installPrebuiltRulesAndTimelines(supertest); + expect(response.rules_installed).toBe(1); + expect(response.rules_updated).toBe(0); + }); + + it('should update outdated prebuilt rules', async () => { + // Install all prebuilt detection rules + const ruleAssetSavedObjects = getRuleAssetSavedObjects(); + await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); + await installPrebuiltRulesAndTimelines(supertest); + + // Clear previous rule assets + await deleteAllPrebuiltRuleAssets(es); + // Increment the version of one of the installed rules and create the new rule assets + ruleAssetSavedObjects[0]['security-rule'].version += 1; + await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); + + // Check that one prebuilt rule status shows that one rule is outdated + const statusResponse = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(statusResponse.rules_not_updated).toBe(1); + + // Call the install prebuilt rules again and check that the outdated rule was updated + const response = await installPrebuiltRulesAndTimelines(supertest); + expect(response.rules_installed).toBe(0); + expect(response.rules_updated).toBe(1); + }); + + it('should not install prebuilt rules if they are up to date', async () => { + // Install all prebuilt detection rules + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRulesAndTimelines(supertest); + + // Check that all prebuilt rules were installed + const statusResponse = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(statusResponse.rules_not_installed).toBe(0); + expect(statusResponse.rules_not_updated).toBe(0); + + // Call the install prebuilt rules again and check that no rules were installed + const response = await installPrebuiltRulesAndTimelines(supertest); + expect(response.rules_installed).toBe(0); + expect(response.rules_updated).toBe(0); + }); + }); + + describe('using current endpoint', () => { + it('should install prebuilt rules', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + const body = await installPrebuiltRules(supertest); + + expect(body.summary.succeeded).toBe(RULES_COUNT); + expect(body.summary.failed).toBe(0); + expect(body.summary.skipped).toBe(0); + }); + + it('should install correct prebuilt rule versions', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + const body = await installPrebuiltRules(supertest); + + // Check that all prebuilt rules were actually installed and their versions match the latest + expect(body.results.created).toEqual( + expect.arrayContaining([ + expect.objectContaining({ rule_id: 'rule-1', version: 1 }), + expect.objectContaining({ rule_id: 'rule-2', version: 2 }), + expect.objectContaining({ rule_id: 'rule-3', version: 3 }), + expect.objectContaining({ rule_id: 'rule-4', version: 4 }), + ]) + ); + }); + + it('should install missing prebuilt rules', async () => { + // Install all prebuilt detection rules + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(supertest); + + // Delete one of the installed rules + await deleteRule(supertest, 'rule-1'); + + // Check that one prebuilt rule is missing + const statusResponse = await getPrebuiltRulesStatus(supertest); + expect(statusResponse.stats.num_prebuilt_rules_to_install).toBe(1); + + // Call the install prebuilt rules again and check that the missing rule was installed + const response = await installPrebuiltRules(supertest); + expect(response.summary.succeeded).toBe(1); + }); + + it('should update outdated prebuilt rules', async () => { + // Install all prebuilt detection rules + const ruleAssetSavedObjects = getRuleAssetSavedObjects(); + await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); + await installPrebuiltRules(supertest); + + // Clear previous rule assets + await deleteAllPrebuiltRuleAssets(es); + // Increment the version of one of the installed rules and create the new rule assets + ruleAssetSavedObjects[0]['security-rule'].version += 1; + await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); + + // Check that one prebuilt rule status shows that one rule is outdated + const statusResponse = await getPrebuiltRulesStatus(supertest); + expect(statusResponse.stats.num_prebuilt_rules_to_install).toBe(0); + expect(statusResponse.stats.num_prebuilt_rules_to_upgrade).toBe(1); + + // Call the install prebuilt rules again and check that the outdated rule was updated + const response = await upgradePrebuiltRules(supertest); + expect(response.summary.succeeded).toBe(1); + expect(response.summary.skipped).toBe(0); + }); + + it('should not install prebuilt rules if they are up to date', async () => { + // Install all prebuilt detection rules + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(supertest); + + // Check that all prebuilt rules were installed + const statusResponse = await getPrebuiltRulesStatus(supertest); + expect(statusResponse.stats.num_prebuilt_rules_to_install).toBe(0); + expect(statusResponse.stats.num_prebuilt_rules_to_upgrade).toBe(0); + + // Call the install prebuilt rules again and check that no rules were installed + const installResponse = await installPrebuiltRules(supertest); + expect(installResponse.summary.succeeded).toBe(0); + expect(installResponse.summary.skipped).toBe(0); + + // Call the upgrade prebuilt rules endpoint and check that no rules were updated + const upgradeResponse = await upgradePrebuiltRules(supertest); + expect(upgradeResponse.summary.succeeded).toBe(0); + expect(upgradeResponse.summary.skipped).toBe(0); + }); + }); + }); + + describe(`rule package with historical versions`, () => { + const getRuleAssetSavedObjects = () => [ + createRuleAssetSavedObject({ rule_id: 'rule-1', version: 1 }), + createRuleAssetSavedObject({ rule_id: 'rule-1', version: 2 }), + createRuleAssetSavedObject({ rule_id: 'rule-2', version: 1 }), + createRuleAssetSavedObject({ rule_id: 'rule-2', version: 2 }), + createRuleAssetSavedObject({ rule_id: 'rule-2', version: 3 }), + ]; + const RULES_COUNT = 2; + + describe('using legacy endpoint', () => { + it('should install prebuilt rules', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + const body = await installPrebuiltRulesAndTimelines(supertest); + + expect(body.rules_installed).toBe(RULES_COUNT); + expect(body.rules_updated).toBe(0); + }); + + it('should install correct prebuilt rule versions', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRulesAndTimelines(supertest); + + // Get installed rules + const { body: rulesResponse } = await supertest + .get(DETECTION_ENGINE_RULES_URL_FIND) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + // Check that all prebuilt rules were actually installed and their versions match the latest + expect(rulesResponse.total).toBe(RULES_COUNT); + expect(rulesResponse.data).toEqual( + expect.arrayContaining([ + expect.objectContaining({ rule_id: 'rule-1', version: 2 }), + expect.objectContaining({ rule_id: 'rule-2', version: 3 }), + ]) + ); + }); + + it('should not install prebuilt rules if they are up to date', async () => { + // Install all prebuilt detection rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRulesAndTimelines(supertest); + + // Check that all prebuilt rules were installed + const statusResponse = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(statusResponse.rules_not_installed).toBe(0); + + // Call the install prebuilt rules again and check that no rules were installed + const response = await installPrebuiltRulesAndTimelines(supertest); + expect(response.rules_installed).toBe(0); + expect(response.rules_updated).toBe(0); + }); + + it('should install missing prebuilt rules', async () => { + // Install all prebuilt detection rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRulesAndTimelines(supertest); + + // Delete one of the installed rules + await deleteRule(supertest, 'rule-1'); + + // Check that one prebuilt rule is missing + const statusResponse = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(statusResponse.rules_not_installed).toBe(1); + + // Call the install prebuilt rules endpoint again and check that the missing rule was installed + const response = await installPrebuiltRulesAndTimelines(supertest); + expect(response.rules_installed).toBe(1); + expect(response.rules_updated).toBe(0); + }); + + it('should update outdated prebuilt rules when previous historical versions available', async () => { + // Install all prebuilt detection rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRulesAndTimelines(supertest); + + // Add a new version of one of the installed rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ rule_id: 'rule-1', version: 3 }), + ]); + + // Check that one prebuilt rule status shows that one rule is outdated + const statusResponse = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(statusResponse.rules_not_updated).toBe(1); + + // Call the install prebuilt rules again and check that the outdated rule was updated + const response = await installPrebuiltRulesAndTimelines(supertest); + expect(response.rules_installed).toBe(0); + expect(response.rules_updated).toBe(1); + + const _statusResponse = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(_statusResponse.rules_not_installed).toBe(0); + expect(_statusResponse.rules_not_updated).toBe(0); + }); + + it('should update outdated prebuilt rules when previous historical versions unavailable', async () => { + // Install all prebuilt detection rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRulesAndTimelines(supertest); + + // Clear previous rule assets + await deleteAllPrebuiltRuleAssets(es); + + // Add a new rule version + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ rule_id: 'rule-1', version: 3 }), + ]); + + // Check that one prebuilt rule status shows that one rule is outdated + const statusResponse = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(statusResponse.rules_not_updated).toBe(1); + expect(statusResponse.rules_not_installed).toBe(0); + + // Call the install prebuilt rules again and check that the outdated rule was updated + const response = await installPrebuiltRulesAndTimelines(supertest); + expect(response.rules_installed).toBe(0); + expect(response.rules_updated).toBe(1); + + const _statusResponse = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(_statusResponse.rules_not_updated).toBe(0); + expect(_statusResponse.rules_not_installed).toBe(0); + }); + }); + + describe('using current endpoint', () => { + it('should install prebuilt rules', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + const body = await installPrebuiltRules(supertest); + + expect(body.summary.succeeded).toBe(RULES_COUNT); + }); + + it('should install correct prebuilt rule versions', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + const response = await installPrebuiltRules(supertest); + + // Check that all prebuilt rules were actually installed and their versions match the latest + expect(response.summary.succeeded).toBe(RULES_COUNT); + expect(response.results.created).toEqual( + expect.arrayContaining([ + expect.objectContaining({ rule_id: 'rule-1', version: 2 }), + expect.objectContaining({ rule_id: 'rule-2', version: 3 }), + ]) + ); + }); + + it('should not install prebuilt rules if they are up to date', async () => { + // Install all prebuilt detection rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(supertest); + + // Check that all prebuilt rules were installed + const statusResponse = await getPrebuiltRulesStatus(supertest); + expect(statusResponse.stats.num_prebuilt_rules_to_install).toBe(0); + + // Call the install prebuilt rules again and check that no rules were installed + const response = await installPrebuiltRules(supertest); + expect(response.summary.succeeded).toBe(0); + expect(response.summary.total).toBe(0); + }); + + it('should install missing prebuilt rules', async () => { + // Install all prebuilt detection rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(supertest); + + // Delete one of the installed rules + await deleteRule(supertest, 'rule-1'); + + // Check that one prebuilt rule is missing + const statusResponse = await getPrebuiltRulesStatus(supertest); + expect(statusResponse.stats.num_prebuilt_rules_to_install).toBe(1); + + // Call the install prebuilt rules endpoint again and check that the missing rule was installed + const response = await installPrebuiltRules(supertest); + expect(response.summary.succeeded).toBe(1); + expect(response.summary.total).toBe(1); + }); + + it('should update outdated prebuilt rules when previous historical versions available', async () => { + // Install all prebuilt detection rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(supertest); + + // Add a new version of one of the installed rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ rule_id: 'rule-1', version: 3 }), + ]); + + // Check that the prebuilt rule status shows that one rule is outdated + const statusResponse = await getPrebuiltRulesStatus(supertest); + expect(statusResponse.stats.num_prebuilt_rules_to_upgrade).toBe(1); + + // Call the upgrade prebuilt rules endpoint and check that the outdated rule was updated + const response = await upgradePrebuiltRules(supertest); + expect(response.summary.succeeded).toBe(1); + expect(response.summary.total).toBe(1); + + const status = await getPrebuiltRulesStatus(supertest); + expect(status.stats.num_prebuilt_rules_to_install).toBe(0); + expect(status.stats.num_prebuilt_rules_to_upgrade).toBe(0); + }); + + it('should update outdated prebuilt rules when previous historical versions unavailable', async () => { + // Install all prebuilt detection rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(supertest); + + // Clear previous rule assets + await deleteAllPrebuiltRuleAssets(es); + + // Add a new rule version + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ rule_id: 'rule-1', version: 3 }), + ]); + + // Check that the prebuilt rule status shows that one rule is outdated + const statusResponse = await getPrebuiltRulesStatus(supertest); + expect(statusResponse.stats.num_prebuilt_rules_to_upgrade).toBe(1); + expect(statusResponse.stats.num_prebuilt_rules_to_install).toBe(0); + + // Call the upgrade prebuilt rules endpoint and check that the outdated rule was updated + const response = await upgradePrebuiltRules(supertest); + expect(response.summary.succeeded).toBe(1); + expect(response.summary.total).toBe(1); + + const status = await getPrebuiltRulesStatus(supertest); + expect(status.stats.num_prebuilt_rules_to_install).toBe(0); + expect(status.stats.num_prebuilt_rules_to_upgrade).toBe(0); + }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/install_prebuilt_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/install_prebuilt_rules.ts deleted file mode 100644 index dc96909ac6bc9..0000000000000 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/install_prebuilt_rules.ts +++ /dev/null @@ -1,250 +0,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 expect from 'expect'; -import { DETECTION_ENGINE_RULES_URL_FIND } from '@kbn/security-solution-plugin/common/constants'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { - deleteAllRules, - deleteAllTimelines, - deleteRule, - getPrebuiltRulesAndTimelinesStatus, -} from '../../utils'; -import { - createHistoricalPrebuiltRuleAssetSavedObjects, - createPrebuiltRuleAssetSavedObjects, - createRuleAssetSavedObject, -} from '../../utils/prebuilt_rules/create_prebuilt_rule_saved_objects'; -import { deleteAllPrebuiltRuleAssets } from '../../utils/prebuilt_rules/delete_all_prebuilt_rule_assets'; -import { installPrebuiltRulesAndTimelines } from '../../utils/prebuilt_rules/install_prebuilt_rules_and_timelines'; - -// eslint-disable-next-line import/no-default-export -export default ({ getService }: FtrProviderContext): void => { - const es = getService('es'); - const supertest = getService('supertest'); - const log = getService('log'); - - describe('install_prebuilt_rules_from_mock_assets', () => { - beforeEach(async () => { - await deleteAllRules(supertest, log); - await deleteAllTimelines(es); - await deleteAllPrebuiltRuleAssets(es); - }); - - describe(`rule package without historical versions`, () => { - const getRuleAssetSavedObjects = () => [ - createRuleAssetSavedObject({ rule_id: 'rule-1', version: 1 }), - createRuleAssetSavedObject({ rule_id: 'rule-2', version: 2 }), - createRuleAssetSavedObject({ rule_id: 'rule-3', version: 3 }), - createRuleAssetSavedObject({ rule_id: 'rule-4', version: 4 }), - ]; - const RULES_COUNT = 4; - - it('should install prebuilt rules', async () => { - await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - const body = await installPrebuiltRulesAndTimelines(supertest); - - expect(body.rules_installed).toBe(RULES_COUNT); - expect(body.rules_updated).toBe(0); - }); - - it('should install correct prebuilt rule versions', async () => { - await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); - - // Get installed rules - const { body: rulesResponse } = await supertest - .get(DETECTION_ENGINE_RULES_URL_FIND) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - // Check that all prebuilt rules were actually installed and their versions match the latest - expect(rulesResponse.total).toBe(RULES_COUNT); - expect(rulesResponse.data).toEqual( - expect.arrayContaining([ - expect.objectContaining({ rule_id: 'rule-1', version: 1 }), - expect.objectContaining({ rule_id: 'rule-2', version: 2 }), - expect.objectContaining({ rule_id: 'rule-3', version: 3 }), - expect.objectContaining({ rule_id: 'rule-4', version: 4 }), - ]) - ); - }); - - it('should not install prebuilt rules if they are up to date', async () => { - // Install all prebuilt detection rules - await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); - - // Check that all prebuilt rules were installed - const statusResponse = await getPrebuiltRulesAndTimelinesStatus(supertest); - expect(statusResponse.rules_not_installed).toBe(0); - - // Call the install prebuilt rules again and check that no rules were installed - const response = await installPrebuiltRulesAndTimelines(supertest); - expect(response.rules_installed).toBe(0); - expect(response.rules_updated).toBe(0); - }); - - it('should install missing prebuilt rules', async () => { - // Install all prebuilt detection rules - await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); - - // Delete one of the installed rules - await deleteRule(supertest, 'rule-1'); - - // Check that one prebuilt rule is missing - const statusResponse = await getPrebuiltRulesAndTimelinesStatus(supertest); - expect(statusResponse.rules_not_installed).toBe(1); - - // Call the install prebuilt rules again and check that the missing rule was installed - const response = await installPrebuiltRulesAndTimelines(supertest); - expect(response.rules_installed).toBe(1); - expect(response.rules_updated).toBe(0); - }); - - it('should update outdated prebuilt rules', async () => { - // Install all prebuilt detection rules - const ruleAssetSavedObjects = getRuleAssetSavedObjects(); - await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); - await installPrebuiltRulesAndTimelines(supertest); - - // Clear previous rule assets - await deleteAllPrebuiltRuleAssets(es); - // Increment the version of one of the installed rules and create the new rule assets - ruleAssetSavedObjects[0]['security-rule'].version += 1; - await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); - - // Check that one prebuilt rule status shows that one rule is outdated - const statusResponse = await getPrebuiltRulesAndTimelinesStatus(supertest); - expect(statusResponse.rules_not_updated).toBe(1); - - // Call the install prebuilt rules again and check that the outdated rule was updated - const response = await installPrebuiltRulesAndTimelines(supertest); - expect(response.rules_installed).toBe(0); - expect(response.rules_updated).toBe(1); - }); - }); - - describe(`rule package with historical versions`, () => { - const getRuleAssetSavedObjects = () => [ - createRuleAssetSavedObject({ rule_id: 'rule-1', version: 1 }), - createRuleAssetSavedObject({ rule_id: 'rule-1', version: 2 }), - createRuleAssetSavedObject({ rule_id: 'rule-2', version: 1 }), - createRuleAssetSavedObject({ rule_id: 'rule-2', version: 2 }), - createRuleAssetSavedObject({ rule_id: 'rule-2', version: 3 }), - ]; - const RULES_COUNT = 2; - - it('should install prebuilt rules', async () => { - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - const body = await installPrebuiltRulesAndTimelines(supertest); - - expect(body.rules_installed).toBe(RULES_COUNT); - expect(body.rules_updated).toBe(0); - }); - - it('should install correct prebuilt rule versions', async () => { - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); - - // Get installed rules - const { body: rulesResponse } = await supertest - .get(DETECTION_ENGINE_RULES_URL_FIND) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - // Check that all prebuilt rules were actually installed and their versions match the latest - expect(rulesResponse.total).toBe(RULES_COUNT); - expect(rulesResponse.data).toEqual( - expect.arrayContaining([ - expect.objectContaining({ rule_id: 'rule-1', version: 2 }), - expect.objectContaining({ rule_id: 'rule-2', version: 3 }), - ]) - ); - }); - - it('should not install prebuilt rules if they are up to date', async () => { - // Install all prebuilt detection rules - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); - - // Check that all prebuilt rules were installed - const statusResponse = await getPrebuiltRulesAndTimelinesStatus(supertest); - expect(statusResponse.rules_not_installed).toBe(0); - - // Call the install prebuilt rules again and check that no rules were installed - const response = await installPrebuiltRulesAndTimelines(supertest); - expect(response.rules_installed).toBe(0); - expect(response.rules_updated).toBe(0); - }); - - it('should install missing prebuilt rules', async () => { - // Install all prebuilt detection rules - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); - - // Delete one of the installed rules - await deleteRule(supertest, 'rule-1'); - - // Check that one prebuilt rule is missing - const statusResponse = await getPrebuiltRulesAndTimelinesStatus(supertest); - expect(statusResponse.rules_not_installed).toBe(1); - - // Call the install prebuilt rules again and check that the missing rule was installed - const response = await installPrebuiltRulesAndTimelines(supertest); - expect(response.rules_installed).toBe(1); - expect(response.rules_updated).toBe(0); - }); - - it('should update outdated prebuilt rules when previous historical versions available', async () => { - // Install all prebuilt detection rules - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); - - // Add a new version of one of the installed rules - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ - createRuleAssetSavedObject({ rule_id: 'rule-1', version: 3 }), - ]); - - // Check that one prebuilt rule status shows that one rule is outdated - const statusResponse = await getPrebuiltRulesAndTimelinesStatus(supertest); - expect(statusResponse.rules_not_updated).toBe(1); - - // Call the install prebuilt rules again and check that the outdated rule was updated - const response = await installPrebuiltRulesAndTimelines(supertest); - expect(response.rules_installed).toBe(0); - expect(response.rules_updated).toBe(1); - }); - - it('should update outdated prebuilt rules when previous historical versions unavailable', async () => { - // Install all prebuilt detection rules - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); - - // Clear previous rule assets - await deleteAllPrebuiltRuleAssets(es); - - // Add a new rule version - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ - createRuleAssetSavedObject({ rule_id: 'rule-1', version: 3 }), - ]); - - // Check that one prebuilt rule status shows that one rule is outdated - const statusResponse = await getPrebuiltRulesAndTimelinesStatus(supertest); - expect(statusResponse.rules_not_updated).toBe(1); - - // Call the install prebuilt rules again and check that the outdated rule was updated - const response = await installPrebuiltRulesAndTimelines(supertest); - expect(response.rules_installed).toBe(0); - expect(response.rules_updated).toBe(1); - }); - }); - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/update_prebuilt_rules_package/config.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/update_prebuilt_rules_package/config.ts new file mode 100644 index 0000000000000..63e43d962a52e --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/update_prebuilt_rules_package/config.ts @@ -0,0 +1,18 @@ +/* + * 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 { FtrConfigProviderContext } from '@kbn/test'; + +// eslint-disable-next-line import/no-default-export +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile(require.resolve('../config.base.ts')); + + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('./update_prebuilt_rules_package.ts')], + }; +} diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/update_prebuilt_rules_package/update_prebuilt_rules_package.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/update_prebuilt_rules_package/update_prebuilt_rules_package.ts new file mode 100644 index 0000000000000..0e25999a37e9b --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/update_prebuilt_rules_package/update_prebuilt_rules_package.ts @@ -0,0 +1,274 @@ +/* + * 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 fs from 'fs/promises'; +import path from 'path'; +import getMajorVersion from 'semver/functions/major'; +import getMinorVersion from 'semver/functions/minor'; +// @ts-expect-error we have to check types with "allowJs: false" for now, causing this import to fail +import { REPO_ROOT } from '@kbn/repo-info'; +import JSON5 from 'json5'; +import expect from 'expect'; +import { PackageSpecManifest } from '@kbn/fleet-plugin/common'; +import { DETECTION_ENGINE_RULES_URL_FIND } from '@kbn/security-solution-plugin/common/constants'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { + deleteAllPrebuiltRuleAssets, + deleteAllRules, + getPrebuiltRulesStatus, + installPrebuiltRules, + upgradePrebuiltRules, +} from '../../utils'; +import { reviewPrebuiltRulesToInstall } from '../../utils/prebuilt_rules/review_install_prebuilt_rules'; +import { reviewPrebuiltRulesToUpgrade } from '../../utils/prebuilt_rules/review_upgrade_prebuilt_rules'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); + + let currentVersion: string; + let previousVersion: string; + + /* + * Recursively query the Fleet EPM API to find the latest GA patch + * version of the previous minor version of the current package + */ + const getPreviousMinorGAVersion = async ( + majorVersion: number, + minorVersion: number, + patchVersion: number + ): Promise => { + // Failsafe check to prevent infinite recursion in case no GA + // patch version is published for the previous minor version + if (patchVersion > 15) { + throw new Error('Unable to find previous minor GA version'); + } + const EPM_URL = `/api/fleet/epm/packages/security_detection_engine/${majorVersion}.${minorVersion}.${patchVersion}`; + + const getPackageResponse = await supertest + .get(EPM_URL) + .set('kbn-xsrf', 'xxxx') + .type('application/json') + .send({ force: true }); + + if (getPackageResponse.status !== 200 || getPackageResponse.body?.item.release !== 'ga') { + return getPreviousMinorGAVersion(majorVersion, minorVersion, patchVersion + 1); + } + return getPackageResponse.body.item.version ?? ''; + }; + + describe('update_prebuilt_rules_package', () => { + before(async () => { + const configFilePath = path.resolve(REPO_ROOT, 'fleet_packages.json'); + const fleetPackages = await fs.readFile(configFilePath, 'utf8'); + + const parsedFleetPackages: PackageSpecManifest[] = JSON5.parse(fleetPackages); + + const securityDetectionEnginePackage = parsedFleetPackages.find( + (fleetPackage) => fleetPackage.name === 'security_detection_engine' + ); + + currentVersion = securityDetectionEnginePackage?.version ?? ''; + + const majorVersion = getMajorVersion(currentVersion); + const minorVersion = getMinorVersion(currentVersion); + + expect(securityDetectionEnginePackage).not.toBeUndefined(); + + try { + previousVersion = await getPreviousMinorGAVersion(majorVersion, minorVersion - 1, 0); + } catch (error) { + // If no previous minor GA version is found, carry out test with existing current version + previousVersion = currentVersion; + } + }); + + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllPrebuiltRuleAssets(es); + }); + + it('should allow user to install prebuilt rules from scratch, then install new rules and upgrade existing rules from the new package', async () => { + // PART 1: Install prebuilt rules from the previous minor version as the current version + + // Verify that status is empty before package installation + const statusBeforePackageInstallation = await getPrebuiltRulesStatus(supertest); + expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_installed).toBe(0); + expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_to_install).toBe(0); + expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_to_upgrade).toBe(0); + expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_total_in_package).toBe(0); + + const EPM_URL_FOR_PREVIOUS_VERSION = `/api/fleet/epm/packages/security_detection_engine/${previousVersion}`; + + const installPreviousPackageResponse = await supertest + .post(EPM_URL_FOR_PREVIOUS_VERSION) + .set('kbn-xsrf', 'xxxx') + .type('application/json') + .send({ force: true }) + .expect(200); + + expect(installPreviousPackageResponse.body._meta.install_source).toBe('registry'); + expect(installPreviousPackageResponse.body.items.length).toBeGreaterThan(0); + + // Verify that status is updated after the installation of package "N-1" + const statusAfterPackageInstallation = await getPrebuiltRulesStatus(supertest); + expect(statusAfterPackageInstallation.stats.num_prebuilt_rules_installed).toBe(0); + expect(statusAfterPackageInstallation.stats.num_prebuilt_rules_to_install).toBeGreaterThan(0); + expect(statusAfterPackageInstallation.stats.num_prebuilt_rules_to_upgrade).toBe(0); + expect( + statusAfterPackageInstallation.stats.num_prebuilt_rules_total_in_package + ).toBeGreaterThan(0); + + // Verify that _review endpoint returns the same number of rules to install as the status endpoint + const prebuiltRulesToInstallReview = await reviewPrebuiltRulesToInstall(supertest); + expect(prebuiltRulesToInstallReview.stats.num_rules_to_install).toBe( + statusAfterPackageInstallation.stats.num_prebuilt_rules_to_install + ); + + // Verify that the _perform endpoint returns the same number of installed rules as the status endpoint + // and the _review endpoint + const installPrebuiltRulesResponse = await installPrebuiltRules(supertest); + expect(installPrebuiltRulesResponse.summary.succeeded).toBe( + statusAfterPackageInstallation.stats.num_prebuilt_rules_to_install + ); + expect(installPrebuiltRulesResponse.summary.succeeded).toBe( + prebuiltRulesToInstallReview.stats.num_rules_to_install + ); + + // Get installed rules + const { body: rulesResponse } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL_FIND}?per_page=10000`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + // Check that all prebuilt rules were actually installed + expect(rulesResponse.total).toBe(installPrebuiltRulesResponse.summary.succeeded); + expect( + installPrebuiltRulesResponse.results.created.map((rule: { rule_id: string }) => ({ + rule_id: rule.rule_id, + })) + ).toEqual( + expect.arrayContaining( + rulesResponse.data.map((rule: { rule_id: string }) => ({ rule_id: rule.rule_id })) + ) + ); + + // PART 2: Now install the lastest (current) package, defined in fleet_packages.json + const EPM_URL_FOR_CURRENT_VERSION = `/api/fleet/epm/packages/security_detection_engine/${currentVersion}`; + + const installLatestPackageResponse = await supertest + .post(EPM_URL_FOR_CURRENT_VERSION) + .set('kbn-xsrf', 'xxxx') + .type('application/json') + .send({ force: true }) + .expect(200); + + expect(installLatestPackageResponse.body.items.length).toBeGreaterThanOrEqual(0); + + // Verify status after intallation of the latest package + const statusAfterLatestPackageInstallation = await getPrebuiltRulesStatus(supertest); + expect( + statusAfterLatestPackageInstallation.stats.num_prebuilt_rules_installed + ).toBeGreaterThan(0); + expect( + statusAfterLatestPackageInstallation.stats.num_prebuilt_rules_to_install + ).toBeGreaterThanOrEqual(0); + expect( + statusAfterLatestPackageInstallation.stats.num_prebuilt_rules_to_upgrade + ).toBeGreaterThanOrEqual(0); + expect( + statusAfterLatestPackageInstallation.stats.num_prebuilt_rules_total_in_package + ).toBeGreaterThan(0); + + // Verify that _review endpoint returns the same number of rules to install as the status endpoint + const prebuiltRulesToInstallReviewAfterLatestPackageInstallation = + await reviewPrebuiltRulesToInstall(supertest); + expect( + prebuiltRulesToInstallReviewAfterLatestPackageInstallation.stats.num_rules_to_install + ).toBe(statusAfterLatestPackageInstallation.stats.num_prebuilt_rules_to_install); + + // Install available rules and verify that the _perform endpoint returns the same number of + // installed rules as the status endpoint and the _review endpoint + const installPrebuiltRulesResponseAfterLatestPackageInstallation = await installPrebuiltRules( + supertest + ); + expect(installPrebuiltRulesResponseAfterLatestPackageInstallation.summary.succeeded).toBe( + statusAfterLatestPackageInstallation.stats.num_prebuilt_rules_to_install + ); + expect( + installPrebuiltRulesResponseAfterLatestPackageInstallation.summary.succeeded + ).toBeGreaterThanOrEqual( + prebuiltRulesToInstallReviewAfterLatestPackageInstallation.stats.num_rules_to_install + ); + + // Get installed rules + const { body: rulesResponseAfterPackageUpdate } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL_FIND}?per_page=10000`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + // Check that the expected new prebuilt rules from the latest package were actually installed + expect( + rulesResponseAfterPackageUpdate.data.map((rule: { rule_id: string }) => ({ + rule_id: rule.rule_id, + })) + ).toEqual( + expect.arrayContaining( + installPrebuiltRulesResponseAfterLatestPackageInstallation.results.created.map( + (rule: { rule_id: string }) => ({ + rule_id: rule.rule_id, + }) + ) + ) + ); + + // Verify that the upgrade _review endpoint returns the same number of rules to upgrade as the status endpoint + const prebuiltRulesToUpgradeReviewAfterLatestPackageInstallation = + await reviewPrebuiltRulesToUpgrade(supertest); + expect( + prebuiltRulesToUpgradeReviewAfterLatestPackageInstallation.stats.num_rules_to_upgrade_total + ).toBe(statusAfterLatestPackageInstallation.stats.num_prebuilt_rules_to_upgrade); + + // Call the upgrade _perform endpoint and verify that the number of upgraded rules is the same as the one + // returned by the _review endpoint and the status endpoint + const upgradePrebuiltRulesResponseAfterLatestPackageInstallation = await upgradePrebuiltRules( + supertest + ); + expect(upgradePrebuiltRulesResponseAfterLatestPackageInstallation.summary.succeeded).toEqual( + statusAfterLatestPackageInstallation.stats.num_prebuilt_rules_to_upgrade + ); + expect(upgradePrebuiltRulesResponseAfterLatestPackageInstallation.summary.succeeded).toEqual( + prebuiltRulesToUpgradeReviewAfterLatestPackageInstallation.stats.num_rules_to_upgrade_total + ); + + // Get installed rules + const { body: rulesResponseAfterPackageUpdateAndRuleUpgrades } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL_FIND}?per_page=10000`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + // Check that the expected new prebuilt rules from the latest package were actually installed + expect( + rulesResponseAfterPackageUpdateAndRuleUpgrades.data.map((rule: { rule_id: string }) => ({ + rule_id: rule.rule_id, + })) + ).toEqual( + expect.arrayContaining( + upgradePrebuiltRulesResponseAfterLatestPackageInstallation.results.updated.map( + (rule: { rule_id: string }) => ({ + rule_id: rule.rule_id, + }) + ) + ) + ); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/index.ts b/x-pack/test/detection_engine_api_integration/utils/index.ts index b79999d00eb06..e02edc7908047 100644 --- a/x-pack/test/detection_engine_api_integration/utils/index.ts +++ b/x-pack/test/detection_engine_api_integration/utils/index.ts @@ -101,7 +101,10 @@ export * from './wait_for_signals_to_be_present'; export * from './prebuilt_rules/create_prebuilt_rule_saved_objects'; export * from './prebuilt_rules/delete_all_prebuilt_rule_assets'; export * from './prebuilt_rules/delete_prebuilt_rules_fleet_package'; +export * from './prebuilt_rules/get_prebuilt_rules_status'; export * from './prebuilt_rules/get_prebuilt_rules_and_timelines_status'; export * from './prebuilt_rules/install_prebuilt_rules_fleet_package'; +export * from './prebuilt_rules/install_prebuilt_rules'; +export * from './prebuilt_rules/upgrade_prebuilt_rules'; export * from './prebuilt_rules/install_mock_prebuilt_rules'; export * from './prebuilt_rules/install_prebuilt_rules_and_timelines'; diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/get_prebuilt_rules_and_timelines_status.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/get_prebuilt_rules_and_timelines_status.ts index 9a757d73824c8..109c6e148537c 100644 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/get_prebuilt_rules_and_timelines_status.ts +++ b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/get_prebuilt_rules_and_timelines_status.ts @@ -12,6 +12,7 @@ import { import type SuperTest from 'supertest'; /** + * (LEGACY) * Helper to retrieve the prebuilt rules status * * @param supertest The supertest deps diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/get_prebuilt_rules_status.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/get_prebuilt_rules_status.ts new file mode 100644 index 0000000000000..cb283d8cf940d --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/get_prebuilt_rules_status.ts @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + GET_PREBUILT_RULES_STATUS_URL, + GetPrebuiltRulesStatusResponseBody, +} from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules'; +import type SuperTest from 'supertest'; + +/** + * Helper to retrieve the prebuilt rules status + * + * @param supertest The supertest deps + */ +export const getPrebuiltRulesStatus = async ( + supertest: SuperTest.SuperTest +): Promise => { + const response = await supertest + .get(GET_PREBUILT_RULES_STATUS_URL) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + return response.body; +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules.ts new file mode 100644 index 0000000000000..c11ccb7b37abd --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules.ts @@ -0,0 +1,46 @@ +/* + * 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 { + PERFORM_RULE_INSTALLATION_URL, + RuleVersionSpecifier, + PerformRuleInstallationResponseBody, +} from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules'; +import type SuperTest from 'supertest'; + +/** + * Installs available prebuilt rules in Kibana. Rules are + * installed from the security-rule saved objects. + * + * - No rules will be installed if there are no security-rule assets (e.g., the + * package is not installed or mocks are not created). + * + * - Pass in an array of rule version specifiers to install specific rules. Otherwise + * all available rules will be installed. + * + * @param supertest SuperTest instance + * @param rules Array of rule version specifiers to install (optional) + * @returns Install prebuilt rules response + */ +export const installPrebuiltRules = async ( + supertest: SuperTest.SuperTest, + rules?: RuleVersionSpecifier[] +): Promise => { + let payload = {}; + if (rules) { + payload = { mode: 'SPECIFIC_RULES', rules }; + } else { + payload = { mode: 'ALL_RULES' }; + } + const response = await supertest + .post(PERFORM_RULE_INSTALLATION_URL) + .set('kbn-xsrf', 'true') + .send(payload) + .expect(200); + + return response.body; +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_and_timelines.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_and_timelines.ts index 3609e1adbc112..7954e2b47bbac 100644 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_and_timelines.ts +++ b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_and_timelines.ts @@ -12,8 +12,11 @@ import { import type SuperTest from 'supertest'; /** + * (LEGACY) * Installs all prebuilt rules and timelines available in Kibana. Rules are * installed from the security-rule saved objects. + * This is a legacy endpoint and has been replaced by: + * POST /internal/detection_engine/prebuilt_rules/installation/_perform * * - No rules will be installed if there are no security-rule assets (e.g., the * package is not installed or mocks are not created). diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/review_install_prebuilt_rules.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/review_install_prebuilt_rules.ts new file mode 100644 index 0000000000000..1c14aec343e9d --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/review_install_prebuilt_rules.ts @@ -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 { REVIEW_RULE_INSTALLATION_URL } from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules/urls'; +import { ReviewRuleInstallationResponseBody } from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules/review_rule_installation/review_rule_installation_route'; +import type SuperTest from 'supertest'; + +/** + * Returns prebuilt rules that are avaialble to install + * + * @param supertest SuperTest instance + * @returns Review Install prebuilt rules response + */ +export const reviewPrebuiltRulesToInstall = async ( + supertest: SuperTest.SuperTest +): Promise => { + const response = await supertest + .post(REVIEW_RULE_INSTALLATION_URL) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + return response.body; +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/review_upgrade_prebuilt_rules.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/review_upgrade_prebuilt_rules.ts new file mode 100644 index 0000000000000..9b8e817a571d3 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/review_upgrade_prebuilt_rules.ts @@ -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 { REVIEW_RULE_UPGRADE_URL } from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules/urls'; +import { ReviewRuleUpgradeResponseBody } from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules/review_rule_upgrade/review_rule_upgrade_route'; +import type SuperTest from 'supertest'; + +/** + * Returns prebuilt rules that are available to Upgrade + * + * @param supertest SuperTest instance + * @returns Review Upgrade prebuilt rules response + */ +export const reviewPrebuiltRulesToUpgrade = async ( + supertest: SuperTest.SuperTest +): Promise => { + const response = await supertest + .post(REVIEW_RULE_UPGRADE_URL) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + return response.body; +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/upgrade_prebuilt_rules.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/upgrade_prebuilt_rules.ts new file mode 100644 index 0000000000000..bb3299cb5dd9e --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/upgrade_prebuilt_rules.ts @@ -0,0 +1,42 @@ +/* + * 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 { + PERFORM_RULE_UPGRADE_URL, + RuleVersionSpecifier, + PerformRuleUpgradeResponseBody, +} from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules'; +import type SuperTest from 'supertest'; + +/** + * Upgrades available prebuilt rules in Kibana. + * + * - Pass in an array of rule version specifiers to upgrade specific rules. Otherwise + * all available rules will be upgraded. + * + * @param supertest SuperTest instance + * @param rules Array of rule version specifiers to upgrade (optional) + * @returns Upgrade prebuilt rules response + */ +export const upgradePrebuiltRules = async ( + supertest: SuperTest.SuperTest, + rules?: RuleVersionSpecifier[] +): Promise => { + let payload = {}; + if (rules) { + payload = { mode: 'SPECIFIC_RULES', rules, pick_version: 'TARGET' }; + } else { + payload = { mode: 'ALL_RULES', pick_version: 'TARGET' }; + } + const response = await supertest + .post(PERFORM_RULE_UPGRADE_URL) + .set('kbn-xsrf', 'true') + .send(payload) + .expect(200); + + return response.body; +};