[Security Solution] Expand prebuilt rules install/update workflow test coverage (#155241)

## Summary

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

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

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



### Checklist

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

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Juan Pablo Djeredjian 2023-07-03 19:22:40 +02:00 committed by GitHub
parent 6a9e8d422c
commit d6d4c6495f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 955 additions and 35 deletions

View file

@ -232,6 +232,9 @@ enabled:
- x-pack/test/detection_engine_api_integration/security_and_spaces/group9/config.ts
- x-pack/test/detection_engine_api_integration/security_and_spaces/group10/config.ts
- x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/config.ts
- x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/config.ts
- x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/config.ts
- x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/config.ts
- x-pack/test/encrypted_saved_objects_api_integration/config.ts
- x-pack/test/examples/config.ts
- x-pack/test/fleet_api_integration/config.agent.ts

View file

@ -165,6 +165,7 @@ export const ALERT_DETAILS_REDIRECT_PATH = `${ALERTS_PATH}/redirect` as const;
export const RULES_PATH = '/rules' as const;
export const RULES_LANDING_PATH = `${RULES_PATH}/landing` as const;
export const RULES_ADD_PATH = `${RULES_PATH}/add_rules` as const;
export const RULES_UPDATES = `${RULES_PATH}/updates` as const;
export const RULES_CREATE_PATH = `${RULES_PATH}/create` as const;
export const EXCEPTIONS_PATH = '/exceptions' as const;
export const EXCEPTION_LIST_DETAIL_PATH = `${EXCEPTIONS_PATH}/details/:detailName` as const;

View file

@ -0,0 +1,175 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { BulkInstallPackageInfo } from '@kbn/fleet-plugin/common';
import type { Rule } from '../../../public/detection_engine/rule_management/logic/types';
import { createRuleAssetSavedObject } from '../../helpers/rules';
import {
GO_BACK_TO_RULES_TABLE_BUTTON,
INSTALL_ALL_RULES_BUTTON,
INSTALL_SELECTED_RULES_BUTTON,
RULES_MANAGEMENT_TABLE,
RULES_ROW,
RULES_UPDATES_TABLE,
SELECT_ALL_RULES_ON_PAGE_CHECKBOX,
TOASTER,
} from '../../screens/alerts_detection_rules';
import { waitForRulesTableToBeLoaded } from '../../tasks/alerts_detection_rules';
import {
getRuleAssets,
createAndInstallMockedPrebuiltRules,
} from '../../tasks/api_calls/prebuilt_rules';
import { resetRulesTableState, deleteAlertsAndRules, reload } from '../../tasks/common';
import { esArchiverResetKibana } from '../../tasks/es_archiver';
import { login, visitWithoutDateRange } from '../../tasks/login';
import { SECURITY_DETECTIONS_RULES_URL } from '../../urls/navigation';
import {
addElasticRulesButtonClick,
assertRuleUpgradeAvailableAndUpgradeAll,
ruleUpdatesTabClick,
} from '../../tasks/prebuilt_rules';
describe('Detection rules, Prebuilt Rules Installation and Update workflow', () => {
beforeEach(() => {
login();
resetRulesTableState();
deleteAlertsAndRules();
esArchiverResetKibana();
visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL);
});
describe('Installation of prebuilt rules package via Fleet', () => {
beforeEach(() => {
cy.intercept('POST', '/api/fleet/epm/packages/_bulk*').as('installPackage');
waitForRulesTableToBeLoaded();
});
it('should install package from Fleet in the background', () => {
/* Assert that the package in installed from Fleet by checking that
/* the installSource is "registry", as opposed to "bundle" */
cy.wait('@installPackage', {
timeout: 60000,
}).then(({ response }) => {
cy.wrap(response?.statusCode).should('eql', 200);
const packages = response?.body.items.map(({ name, result }: BulkInstallPackageInfo) => ({
name,
installSource: result.installSource,
}));
expect(packages.length).to.have.greaterThan(0);
expect(packages).to.deep.include.members([
{ name: 'security_detection_engine', installSource: 'registry' },
]);
});
});
it('should install rules from the Fleet package when user clicks on CTA', () => {
/* Retrieve how many rules were installed from the Fleet package */
cy.wait('@installPackage', {
timeout: 60000,
}).then(() => {
getRuleAssets().then((response) => {
const ruleIds = response.body.hits.hits.map(
(hit: { _source: { ['security-rule']: Rule } }) => hit._source['security-rule'].rule_id
);
const numberOfRulesToInstall = new Set(ruleIds).size;
addElasticRulesButtonClick();
cy.get(INSTALL_ALL_RULES_BUTTON).click();
cy.get(TOASTER)
.should('be.visible')
.should('have.text', `${numberOfRulesToInstall} rules installed successfully.`);
});
});
});
});
describe('Installation of prebuilt rules', () => {
const RULE_1 = createRuleAssetSavedObject({
name: 'Test rule 1',
rule_id: 'rule_1',
});
const RULE_2 = createRuleAssetSavedObject({
name: 'Test rule 2',
rule_id: 'rule_2',
});
beforeEach(() => {
createAndInstallMockedPrebuiltRules({ rules: [RULE_1, RULE_2], installToKibana: false });
waitForRulesTableToBeLoaded();
});
it('should install selected rules when user clicks on Install selected rules', () => {
addElasticRulesButtonClick();
cy.get(SELECT_ALL_RULES_ON_PAGE_CHECKBOX).click();
cy.get(INSTALL_SELECTED_RULES_BUTTON).click();
cy.get(TOASTER).should('be.visible').should('have.text', `2 rules installed successfully.`);
cy.get(GO_BACK_TO_RULES_TABLE_BUTTON).click();
cy.get(RULES_MANAGEMENT_TABLE).find(RULES_ROW).should('have.length', 2);
cy.get(RULES_MANAGEMENT_TABLE).contains(RULE_1['security-rule'].name);
cy.get(RULES_MANAGEMENT_TABLE).contains(RULE_2['security-rule'].name);
});
it('should fail gracefully with toast error message when request to install rules fails', () => {
/* Stub request to force rules installation to fail */
cy.intercept('POST', '/internal/detection_engine/prebuilt_rules/installation/_perform', {
statusCode: 500,
}).as('installPrebuiltRules');
addElasticRulesButtonClick();
cy.get(SELECT_ALL_RULES_ON_PAGE_CHECKBOX).click();
cy.get(INSTALL_SELECTED_RULES_BUTTON).click();
cy.wait('@installPrebuiltRules');
cy.get(TOASTER).should('be.visible').should('have.text', 'Rule installation failed');
});
});
describe('Update of prebuilt rules', () => {
const RULE_ID = 'rule_id';
const OUTDATED_RULE = createRuleAssetSavedObject({
name: 'Outdated rule',
rule_id: RULE_ID,
version: 1,
});
const UPDATED_RULE = createRuleAssetSavedObject({
name: 'Updated rule',
rule_id: RULE_ID,
version: 2,
});
beforeEach(() => {
/* Create a new rule and install it */
createAndInstallMockedPrebuiltRules({ rules: [OUTDATED_RULE] });
/* Create a second version of the rule, making it available for update */
createAndInstallMockedPrebuiltRules({ rules: [UPDATED_RULE], installToKibana: false });
waitForRulesTableToBeLoaded();
reload();
});
it('should update rule succesfully', () => {
cy.intercept('POST', '/internal/detection_engine/prebuilt_rules/upgrade/_perform').as(
'updatePrebuiltRules'
);
ruleUpdatesTabClick();
assertRuleUpgradeAvailableAndUpgradeAll(OUTDATED_RULE);
cy.get(TOASTER).should('be.visible').should('have.text', `1 rule updated successfully.`);
});
it('should fail gracefully with toast error message when request to update rules fails', () => {
/* Stub request to force rules update to fail */
cy.intercept('POST', '/internal/detection_engine/prebuilt_rules/upgrade/_perform', {
statusCode: 500,
}).as('updatePrebuiltRules');
ruleUpdatesTabClick();
assertRuleUpgradeAvailableAndUpgradeAll(OUTDATED_RULE);
cy.get(TOASTER).should('be.visible').should('have.text', 'Rule update failed');
/* Assert that the rule has not been updated in the UI */
cy.get(RULES_UPDATES_TABLE).should('contain', OUTDATED_RULE['security-rule'].name);
});
});
});

View file

@ -5,10 +5,11 @@
* 2.0.
*/
import { createRuleAssetSavedObject } from '../../helpers/rules';
import {
COLLAPSED_ACTION_BTN,
ELASTIC_RULES_BTN,
LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN,
ADD_ELASTIC_RULES_BTN,
RULES_EMPTY_PROMPT,
RULES_MONITORING_TAB,
RULES_ROW,
@ -29,13 +30,20 @@ import {
waitForRuleToUpdate,
} from '../../tasks/alerts_detection_rules';
import {
excessivelyInstallAllPrebuiltRules,
createAndInstallMockedPrebuiltRules,
getAvailablePrebuiltRulesCount,
} from '../../tasks/api_calls/prebuilt_rules';
import { cleanKibana, deleteAlertsAndRules } from '../../tasks/common';
import { cleanKibana, deleteAlertsAndRules, deletePrebuiltRulesAssets } from '../../tasks/common';
import { login, visitWithoutDateRange } from '../../tasks/login';
import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation';
const rules = Array.from(Array(5)).map((_, i) => {
return createRuleAssetSavedObject({
name: `Test rule ${i + 1}`,
rule_id: `rule_${i + 1}`,
});
});
describe('Prebuilt rules', () => {
before(() => {
cleanKibana();
@ -44,8 +52,9 @@ describe('Prebuilt rules', () => {
beforeEach(() => {
login();
deleteAlertsAndRules();
deletePrebuiltRulesAssets();
visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
excessivelyInstallAllPrebuiltRules();
createAndInstallMockedPrebuiltRules({ rules });
cy.reload();
waitForPrebuiltDetectionRulesToBeLoaded();
});
@ -114,10 +123,10 @@ describe('Prebuilt rules', () => {
'have.text',
`Elastic rules (${expectedNumberOfRulesAfterDeletion})`
);
cy.get(LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN).should('have.text', `Add Elastic rules1`);
cy.get(ADD_ELASTIC_RULES_BTN).should('have.text', `Add Elastic rules1`);
// Navigate to the prebuilt rule installation page
cy.get(LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN).click();
cy.get(ADD_ELASTIC_RULES_BTN).click();
// Click the "Install all rules" button
cy.get(INSTALL_ALL_RULES_BUTTON).click();
@ -144,7 +153,7 @@ describe('Prebuilt rules', () => {
selectNumberOfRules(numberOfRulesToBeSelected);
deleteSelectedRules();
cy.get(LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN).should(
cy.get(ADD_ELASTIC_RULES_BTN).should(
'have.text',
`Add Elastic rules${numberOfRulesToBeSelected}`
);
@ -154,7 +163,7 @@ describe('Prebuilt rules', () => {
);
// Navigate to the prebuilt rule installation page
cy.get(LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN).click();
cy.get(ADD_ELASTIC_RULES_BTN).click();
// Click the "Install all rules" button
cy.get(INSTALL_ALL_RULES_BUTTON).click();

View file

@ -0,0 +1,117 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createRuleAssetSavedObject } from '../../helpers/rules';
import { ADD_ELASTIC_RULES_BTN, RULES_UPDATES_TAB } from '../../screens/alerts_detection_rules';
import { deleteFirstRule, waitForRulesTableToBeLoaded } from '../../tasks/alerts_detection_rules';
import {
installAllPrebuiltRulesRequest,
createAndInstallMockedPrebuiltRules,
} from '../../tasks/api_calls/prebuilt_rules';
import { resetRulesTableState, deleteAlertsAndRules, reload } from '../../tasks/common';
import { login, visitWithoutDateRange } from '../../tasks/login';
import { SECURITY_DETECTIONS_RULES_URL } from '../../urls/navigation';
describe('Detection rules, Prebuilt Rules Installation and Update workflow', () => {
beforeEach(() => {
login();
/* Make sure persisted rules table state is cleared */
resetRulesTableState();
deleteAlertsAndRules();
const RULE_1 = createRuleAssetSavedObject({
name: 'Test rule 1',
rule_id: 'rule_1',
});
createAndInstallMockedPrebuiltRules({ rules: [RULE_1], installToKibana: false });
});
describe('Rules installation notification when no rules have been installed', () => {
beforeEach(() => {
visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL);
});
it('should notify user about prebuilt rules available for installation', () => {
cy.get(ADD_ELASTIC_RULES_BTN).should('be.visible');
});
});
describe('No notifications', () => {
it('should display no install or update notifications when latest rules are installed', () => {
/* Install current available rules */
installAllPrebuiltRulesRequest();
visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL);
waitForRulesTableToBeLoaded();
/* Assert that there are no installation or update notifications */
/* Add Elastic Rules button and Rule Upgrade tabs should not contain a number badge */
cy.get(ADD_ELASTIC_RULES_BTN).should('have.text', 'Add Elastic rules');
cy.get(RULES_UPDATES_TAB).should('have.text', 'Rule Updates');
});
});
describe('Rule installation notification when at least one rule already installed', () => {
beforeEach(() => {
installAllPrebuiltRulesRequest();
/* Create new rule assets with a different rule_id as the one that was */
/* installed before in order to trigger the installation notification */
const RULE_2 = createRuleAssetSavedObject({
name: 'Test rule 2',
rule_id: 'rule_2',
});
const RULE_3 = createRuleAssetSavedObject({
name: 'Test rule 3',
rule_id: 'rule_3',
});
createAndInstallMockedPrebuiltRules({ rules: [RULE_2, RULE_3], installToKibana: false });
visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL);
waitForRulesTableToBeLoaded();
});
it('should notify user about prebuilt rules package available for installation', () => {
const numberOfAvailableRules = 2;
cy.get(ADD_ELASTIC_RULES_BTN).should('be.visible');
cy.get(ADD_ELASTIC_RULES_BTN).should(
'have.text',
`Add Elastic rules${numberOfAvailableRules}`
);
});
it('should notify user a rule is again available for installation if it is deleted', () => {
/* Install available rules, assert that the notification is gone */
/* then delete one rule and assert that the notification is back */
installAllPrebuiltRulesRequest();
reload();
deleteFirstRule();
cy.get(ADD_ELASTIC_RULES_BTN).should('be.visible');
cy.get(ADD_ELASTIC_RULES_BTN).should('have.text', `Add Elastic rules${1}`);
});
});
describe('Rule update notification', () => {
beforeEach(() => {
installAllPrebuiltRulesRequest();
/* Create new rule asset with the same rule_id as the one that was installed */
/* but with a higher version, in order to trigger the update notification */
const UPDATED_RULE = createRuleAssetSavedObject({
name: 'Test rule 1.1 (updated)',
rule_id: 'rule_1',
version: 2,
});
createAndInstallMockedPrebuiltRules({ rules: [UPDATED_RULE], installToKibana: false });
visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL);
waitForRulesTableToBeLoaded();
reload();
});
it('should notify user about prebuilt rules package available for update', () => {
cy.get(RULES_UPDATES_TAB).should('be.visible');
cy.get(RULES_UPDATES_TAB).should('have.text', `Rule Updates${1}`);
});
});
});

View file

@ -4,6 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createRuleAssetSavedObject } from '../../helpers/rules';
import {
SELECTED_RULES_NUMBER_LABEL,
SELECT_ALL_RULES_BTN,
@ -17,17 +18,32 @@ import {
import {
excessivelyInstallAllPrebuiltRules,
getAvailablePrebuiltRulesCount,
createAndInstallMockedPrebuiltRules,
} from '../../tasks/api_calls/prebuilt_rules';
import { cleanKibana } from '../../tasks/common';
import { login, visitWithoutDateRange } from '../../tasks/login';
import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation';
// TODO: See https://github.com/elastic/kibana/issues/154694
describe.skip('Rules selection', () => {
beforeEach(() => {
const RULE_1 = createRuleAssetSavedObject({
name: 'Test rule 1',
rule_id: 'rule_1',
});
const RULE_2 = createRuleAssetSavedObject({
name: 'Test rule 2',
rule_id: 'rule_2',
});
describe('Rules selection', () => {
before(() => {
cleanKibana();
});
beforeEach(() => {
login();
/* Create and install two mock rules */
createAndInstallMockedPrebuiltRules({ rules: [RULE_1, RULE_2] });
visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
waitForPrebuiltDetectionRulesToBeLoaded();
});
it('should correctly update the selection label when rules are individually selected and unselected', () => {

View file

@ -4,9 +4,10 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import dateMath from '@kbn/datemath';
import moment from 'moment';
import type { PrebuiltRuleAsset } from '../../server/lib/detection_engine/prebuilt_rules';
import { getPrebuiltRuleMock } from '../../server/lib/detection_engine/prebuilt_rules/mocks';
import type { ThreatArray } from '../../common/detection_engine/rule_schema';
@ -77,3 +78,21 @@ export const convertHistoryStartToSize = (relativeTime: string) => {
return relativeTime;
}
};
/**
* A helper function to create a rule asset saved object (type: security-rule)
*
* @param overrideParams Params to override the default mock
* @returns Created rule asset saved object
*/
export const createRuleAssetSavedObject = (overrideParams: Partial<PrebuiltRuleAsset>) => ({
'security-rule': {
...getPrebuiltRuleMock(),
...overrideParams,
},
type: 'security-rule',
references: [],
coreMigrationVersion: '8.6.0',
updated_at: '2022-11-01T12:56:39.717Z',
created_at: '2022-11-01T12:56:39.717Z',
});

View file

@ -59,12 +59,21 @@ export const INTEGRATIONS_POPOVER = '[data-test-subj="IntegrationsDisplayPopover
export const INTEGRATIONS_POPOVER_TITLE = '[data-test-subj="IntegrationsPopoverTitle"]';
export const LOAD_PREBUILT_RULES_BTN = '[data-test-subj="load-prebuilt-rules"]';
export const ADD_ELASTIC_RULES_BTN = '[data-test-subj="addElasticRulesButton"]';
export const LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN = '[data-test-subj="loadPrebuiltRulesBtn"]';
export const ADD_ELASTIC_RULES_EMPTY_PROMPT_BTN =
'[data-test-subj="add-elastc-rules-empty-empty-prompt-button"]';
export const INSTALL_ALL_RULES_BUTTON = '[data-test-subj="installAllRulesButton"]';
export const INSTALL_SELECTED_RULES_BUTTON = '[data-test-subj="installSelectedRulesButton"]';
export const UPGRADE_ALL_RULES_BUTTON = '[data-test-subj="upgradeAllRulesButton"]';
export const UPGRADE_SELECTED_RULES_BUTTON = '[data-test-subj="upgradeSelectedRulesButton"]';
export const GO_BACK_TO_RULES_TABLE_BUTTON = '[data-test-subj="addRulesGoBackToRulesTableBtn"]';
export const RULES_TABLE_INITIAL_LOADING_INDICATOR =
'[data-test-subj="initialLoadingPanelAllRulesTable"]';
@ -90,10 +99,14 @@ export const RULES_MANAGEMENT_TAB = '[data-test-subj="navigation-management"]';
export const RULES_MONITORING_TAB = '[data-test-subj="navigation-monitoring"]';
export const RULES_UPDATES_TAB = '[data-test-subj="navigation-updates"]';
export const RULES_MANAGEMENT_TABLE = '[data-test-subj="rules-management-table"]';
export const RULES_MONITORING_TABLE = '[data-test-subj="rules-monitoring-table"]';
export const RULES_UPDATES_TABLE = '[data-test-subj="rules-upgrades-table"]';
export const RULES_ROW = '.euiTableRow';
export const SEVERITY = '[data-test-subj="severity"]';

View file

@ -13,7 +13,6 @@ import {
DELETE_RULE_ACTION_BTN,
DELETE_RULE_BULK_BTN,
RULES_SELECTED_TAG,
LOAD_PREBUILT_RULES_BTN,
RULES_TABLE_INITIAL_LOADING_INDICATOR,
RULES_TABLE_AUTOREFRESH_INDICATOR,
RULE_CHECKBOX,
@ -65,6 +64,7 @@ import {
DUPLICATE_WITH_EXCEPTIONS_OPTION,
DUPLICATE_WITH_EXCEPTIONS_WITHOUT_EXPIRED_OPTION,
TOASTER_CLOSE_ICON,
ADD_ELASTIC_RULES_EMPTY_PROMPT_BTN,
} from '../screens/alerts_detection_rules';
import type { RULES_MONITORING_TABLE } from '../screens/alerts_detection_rules';
import { EUI_CHECKBOX } from '../screens/common/controls';
@ -336,7 +336,7 @@ export const waitForRulesTableToBeRefreshed = () => {
export const waitForPrebuiltDetectionRulesToBeLoaded = () => {
cy.log('Wait for prebuilt rules to be loaded');
cy.get(LOAD_PREBUILT_RULES_BTN, { timeout: 300000 }).should('not.exist');
cy.get(ADD_ELASTIC_RULES_EMPTY_PROMPT_BTN, { timeout: 300000 }).should('not.exist');
cy.get(RULES_MANAGEMENT_TABLE).should('exist');
cy.get(RULES_TABLE_REFRESH_INDICATOR).should('not.exist');
};

View file

@ -5,7 +5,10 @@
* 2.0.
*/
import { ELASTIC_SECURITY_RULE_ID } from '../../../common/detection_engine/constants';
import type { PrePackagedRulesStatusResponse } from '../../../public/detection_engine/rule_management/logic/types';
import { getPrebuiltRuleWithExceptionsMock } from '../../../server/lib/detection_engine/prebuilt_rules/mocks';
import { createRuleAssetSavedObject } from '../../helpers/rules';
export const getPrebuiltRulesStatus = () => {
return cy.request<PrePackagedRulesStatusResponse>({
@ -15,6 +18,18 @@ export const getPrebuiltRulesStatus = () => {
});
};
export const SAMPLE_PREBUILT_RULE = createRuleAssetSavedObject({
...getPrebuiltRuleWithExceptionsMock(),
rule_id: ELASTIC_SECURITY_RULE_ID,
tags: ['test-tag-1'],
enabled: true,
});
/* Install all prebuilt rules available as security-rule saved objects
* Use in combination with `preventPrebuiltRulesPackageInstallation` and
* `createNewRuleAsset` to create mocked prebuilt rules and install only those
* instead of all rules available in the `security_detection_engine` package
*/
export const installAllPrebuiltRulesRequest = () => {
return cy.request({
method: 'POST',
@ -58,3 +73,131 @@ export const excessivelyInstallAllPrebuiltRules = () => {
waitTillPrebuiltRulesReadyToInstall();
installAllPrebuiltRulesRequest();
};
export const createNewRuleAsset = ({
index = '.kibana_security_solution',
rule = SAMPLE_PREBUILT_RULE,
}: {
index?: string;
rule?: typeof SAMPLE_PREBUILT_RULE;
}) => {
const url = `${Cypress.env('ELASTICSEARCH_URL')}/${index}/_doc/security-rule:${
rule['security-rule'].rule_id
}`;
cy.waitUntil(
() => {
return cy
.request({
method: 'PUT',
url,
headers: { 'kbn-xsrf': 'cypress-creds', 'Content-Type': 'application/json' },
failOnStatusCode: false,
body: rule,
})
.then((response) => response.status === 200);
},
{ interval: 500, timeout: 12000 }
);
};
export const bulkCreateRuleAssets = ({
index = '.kibana_security_solution',
rules = [SAMPLE_PREBUILT_RULE],
}: {
index?: string;
rules?: Array<typeof SAMPLE_PREBUILT_RULE>;
}) => {
const url = `${Cypress.env('ELASTICSEARCH_URL')}/${index}/_bulk`;
const bulkIndexRequestBody = rules.reduce((body, rule) => {
const indexOperation = {
index: {
_index: index,
_id: rule['security-rule'].rule_id,
},
};
const documentData = JSON.stringify(rule);
return body.concat(JSON.stringify(indexOperation), '\n', documentData, '\n');
}, '');
cy.request({
method: 'PUT',
url: `${Cypress.env('ELASTICSEARCH_URL')}/${index}/_mapping`,
body: {
dynamic: true,
},
headers: {
'Content-Type': 'application/json',
},
});
cy.waitUntil(
() => {
return cy
.request({
method: 'POST',
url,
headers: { 'kbn-xsrf': 'cypress-creds', 'Content-Type': 'application/json' },
failOnStatusCode: false,
body: bulkIndexRequestBody,
})
.then((response) => response.status === 200);
},
{ interval: 500, timeout: 12000 }
);
};
export const getRuleAssets = (index: string | undefined = '.kibana_security_solution') => {
const url = `${Cypress.env('ELASTICSEARCH_URL')}/${index}/_search?size=10000`;
return cy.request({
method: 'GET',
url,
headers: { 'kbn-xsrf': 'cypress-creds', 'Content-Type': 'application/json' },
failOnStatusCode: false,
body: {
query: {
term: { type: { value: 'security-rule' } },
},
},
});
};
/* Prevent the installation of the `security_detection_engine` package from Fleet
/* by intercepting the request and returning a mock empty object as response
/* Used primarily to prevent the unwanted installation of "real" prebuilt rules
/* during e2e tests, and allow for manual installation of mock rules instead. */
export const preventPrebuiltRulesPackageInstallation = () => {
cy.intercept('POST', '/api/fleet/epm/packages/_bulk*', {});
};
/**
* Prevent the installation of the `security_detection_engine` package from Fleet.
* The create a `security-rule` asset for each rule provided in the `rules` array.
* Optionally install the rules to Kibana, with a flag defaulting to true
* Explicitly set the `installToKibana` flag to false in cases when needing to
* make mock rules available for installation or update, but do those operations manually
*
* * @param {Array} rules - Rule assets to be created and optionally installed
*
* * @param {string} installToKibana - Flag to decide whether to install the rules as 'alerts' SO. Defaults to true.
*/
export const createAndInstallMockedPrebuiltRules = ({
rules,
installToKibana = true,
}: {
rules?: Array<typeof SAMPLE_PREBUILT_RULE>;
installToKibana?: boolean;
}) => {
cy.log('Install prebuilt rules');
preventPrebuiltRulesPackageInstallation();
// TODO: use this bulk method once the issue with Cypress is fixed
// bulkCreateRuleAssets({ rules });
rules?.forEach((rule) => {
createNewRuleAsset({ rule });
});
if (installToKibana) {
installAllPrebuiltRulesRequest();
}
};

View file

@ -218,6 +218,27 @@ export const deleteConnectors = () => {
});
};
export const deletePrebuiltRulesAssets = () => {
const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`;
rootRequest({
method: 'POST',
url: `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed`,
body: {
query: {
bool: {
filter: [
{
match: {
type: 'security-rule',
},
},
],
},
},
},
});
};
export const postDataView = (dataSource: string) => {
rootRequest({
method: 'POST',

View file

@ -0,0 +1,33 @@
/*
* 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 { RULES_ADD_PATH, RULES_UPDATES } from '../../common/constants';
import {
ADD_ELASTIC_RULES_BTN,
RULES_ROW,
RULES_UPDATES_TAB,
RULES_UPDATES_TABLE,
UPGRADE_ALL_RULES_BUTTON,
} from '../screens/alerts_detection_rules';
import type { SAMPLE_PREBUILT_RULE } from './api_calls/prebuilt_rules';
export const addElasticRulesButtonClick = () => {
cy.get(ADD_ELASTIC_RULES_BTN).click();
cy.location('pathname').should('include', RULES_ADD_PATH);
};
export const ruleUpdatesTabClick = () => {
cy.get(RULES_UPDATES_TAB).click();
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);
cy.get(UPGRADE_ALL_RULES_BUTTON).click();
cy.wait('@updatePrebuiltRules');
};

View file

@ -36,6 +36,7 @@
"@kbn/cases-plugin",
"@kbn/data-plugin",
"@kbn/core-http-common",
"@kbn/data-views-plugin"
"@kbn/data-views-plugin",
"@kbn/fleet-plugin",
]
}

View file

@ -36,7 +36,7 @@ export const INSTALL_RULE_FAILED = (failed: number) =>
export const RULE_UPGRADE_FAILED = i18n.translate(
'xpack.securitySolution.detectionEngine.prebuiltRules.toast.ruleUpgradeFailed',
{
defaultMessage: 'Rule upgrade failed',
defaultMessage: 'Rule update failed',
}
);

View file

@ -27,7 +27,11 @@ export const AddPrebuiltRulesHeaderButtons = () => {
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false} wrap={true}>
{shouldDisplayInstallSelectedRulesButton ? (
<EuiFlexItem grow={false}>
<EuiButton onClick={installSelectedRules} disabled={isRequestInProgress}>
<EuiButton
onClick={installSelectedRules}
disabled={isRequestInProgress}
data-test-subj="installSelectedRulesButton"
>
{i18n.INSTALL_SELECTED_RULES(numberOfSelectedRules)}
{isRuleInstalling ? <EuiLoadingSpinner size="s" /> : undefined}
</EuiButton>

View file

@ -27,7 +27,11 @@ export const UpgradePrebuiltRulesTableButtons = () => {
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false} wrap={true}>
{shouldDisplayUpgradeSelectedRulesButton ? (
<EuiFlexItem grow={false}>
<EuiButton onClick={upgradeSelectedRules} disabled={isRequestInProgress}>
<EuiButton
onClick={upgradeSelectedRules}
disabled={isRequestInProgress}
data-test-subj="upgradeSelectedRulesButton"
>
<>
{i18n.UPDATE_SELECTED_RULES(numberOfSelectedRules)}
{isRuleUpgrading ? <EuiLoadingSpinner size="s" /> : undefined}
@ -41,6 +45,7 @@ export const UpgradePrebuiltRulesTableButtons = () => {
iconType="plusInCircle"
onClick={upgradeAllRules}
disabled={!isRulesAvailableForUpgrade || isRequestInProgress}
data-test-subj="upgradeAllRulesButton"
>
{i18n.UPDATE_ALL}
{isRuleUpgrading ? <EuiLoadingSpinner size="s" /> : undefined}

View file

@ -20,9 +20,9 @@ import { SpyRoute } from '../../../../common/utils/route/spy_routes';
import { MissingPrivilegesCallOut } from '../../../../detections/components/callouts/missing_privileges_callout';
import { MlJobCompatibilityCallout } from '../../../../detections/components/callouts/ml_job_compatibility_callout';
import { NeedAdminForUpdateRulesCallOut } from '../../../../detections/components/callouts/need_admin_for_update_callout';
import { LoadPrePackagedRulesButton } from '../../../../detections/components/rules/pre_packaged_rules/load_prepackaged_rules_button';
import { useUserData } from '../../../../detections/components/user_info';
import { AddElasticRulesButton } from '../../../../detections/components/rules/pre_packaged_rules/add_elastic_rules_button';
import { ValueListsFlyout } from '../../../../detections/components/value_lists_management_flyout';
import { useUserData } from '../../../../detections/components/user_info';
import { useListsConfig } from '../../../../detections/containers/detection_engine/lists/use_lists_config';
import { redirectToDetections } from '../../../../detections/pages/detection_engine/rules/helpers';
import * as i18n from '../../../../detections/pages/detection_engine/rules/translations';
@ -106,7 +106,7 @@ const RulesPageComponent: React.FC = () => {
<SuperHeader>
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false} wrap={true}>
<EuiFlexItem grow={false}>
<LoadPrePackagedRulesButton />
<AddElasticRulesButton />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiToolTip position="top" content={i18n.UPLOAD_VALUE_LISTS_TOOLTIP}>

View file

@ -14,17 +14,17 @@ import { useGetSecuritySolutionLinkProps } from '../../../../common/components/l
import { SecurityPageName } from '../../../../../common';
import { usePrebuiltRulesStatus } from '../../../../detection_engine/rule_management/logic/prebuilt_rules/use_prebuilt_rules_status';
interface LoadPrePackagedRulesButtonProps {
interface AddElasticRulesButtonProps {
'data-test-subj'?: string;
fill?: boolean;
showBadge?: boolean;
}
export const LoadPrePackagedRulesButton = ({
'data-test-subj': dataTestSubj = 'loadPrebuiltRulesBtn',
export const AddElasticRulesButton = ({
'data-test-subj': dataTestSubj = 'addElasticRulesButton',
fill,
showBadge = true,
}: LoadPrePackagedRulesButtonProps) => {
}: AddElasticRulesButtonProps) => {
const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps();
const { onClick: onClickLink } = getSecuritySolutionLinkProps({
deepLinkId: SecurityPageName.rulesAdd,

View file

@ -8,7 +8,7 @@
import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React, { memo } from 'react';
import styled from 'styled-components';
import { LoadPrePackagedRulesButton } from './load_prepackaged_rules_button';
import { AddElasticRulesButton } from './add_elastic_rules_button';
import * as i18n from './translations';
const EmptyPrompt = styled(EuiEmptyPrompt)`
@ -26,9 +26,9 @@ const PrePackagedRulesPromptComponent = () => {
actions={
<EuiFlexGroup justifyContent="center">
<EuiFlexItem grow={false}>
<LoadPrePackagedRulesButton
<AddElasticRulesButton
fill={true}
data-test-subj="load-prebuilt-rules"
data-test-subj="add-elastc-rules-empty-empty-prompt-button"
showBadge={false}
/>
</EuiFlexItem>

View file

@ -0,0 +1,38 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrConfigProviderContext } from '@kbn/test';
import path from 'path';
export const BUNDLED_PACKAGE_DIR = path.join(
path.dirname(__filename),
'./fleet_bundled_packages/fixtures'
);
// 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('./prerelease_packages.ts'),
require.resolve('./install_latest_bundled_prebuilt_rules.ts'),
],
kbnTestServer: {
...functionalConfig.get('kbnTestServer'),
serverArgs: [
...functionalConfig.get('kbnTestServer.serverArgs'),
/* Tests in this directory simulate an air-gapped environment in which the instance doesn't have access to EPR.
* To do that, we point the Fleet url to an invalid URL, and instruct Fleet to fetch bundled packages at the
* location defined in BUNDLED_PACKAGE_DIR.
*/
`--xpack.fleet.registryUrl=http://invalidURL:8080`,
`--xpack.fleet.developer.bundledPackageLocation=${BUNDLED_PACKAGE_DIR}`,
],
},
};
}

View file

@ -0,0 +1,79 @@
/*
* 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';
// @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 { FtrProviderContext } from '../../common/ftr_provider_context';
import {
deleteAllPrebuiltRuleAssets,
deleteAllRules,
getPrebuiltRulesAndTimelinesStatus,
} from '../../utils';
// 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');
/* This test simulates an air-gapped environment in which the user doesn't have access to EPR.
/* We first download the package from the registry as done during build time, and then
/* attempt to install it from the local file system. The API response from EPM provides
/* us with the information of whether the package was installed from the registry or
/* from a package that was bundled with Kibana */
describe('install_bundled_prebuilt_rules', () => {
beforeEach(async () => {
await deleteAllRules(supertest, log);
await deleteAllPrebuiltRuleAssets(es);
});
it('should list `security_detection_engine` as a bundled fleet package in the `fleet_package.json` file', 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'
);
expect(securityDetectionEnginePackage).not.toBeUndefined();
expect(securityDetectionEnginePackage?.name).toBe('security_detection_engine');
});
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 EPM_URL = `/api/fleet/epm/packages/security_detection_engine/99.0.0`;
const bundledInstallResponse = await supertest
.post(EPM_URL)
.set('kbn-xsrf', 'xxxx')
.type('application/json')
.send({ force: true })
.expect(200);
// As opposed to "registry"
expect(bundledInstallResponse.body._meta.install_source).toBe('bundled');
// 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);
});
});
};

View file

@ -0,0 +1,66 @@
/*
* 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 { 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';
// 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');
/* This test makes use of the mock packages created in the '/fleet_bundled_packages' folder,
/* in order to assert that, in production environments, the latest stable version of the package
/* is installed, and that prerelease packages are ignored.
/* The mock packages to test are 99.0.0 and 99.0.1-beta.1, where the latter is a prerelease package.
/* (We use high mock version numbers to prevent clashes with real packages downloaded in other tests.)
/* To do assertions on which packages have been installed, 99.0.0 has a single rule to install,
/* while 99.0.1-beta.1 has 2 rules to install. Also, both packages have the version as part of the rule names. */
describe('prerelease_packages', () => {
beforeEach(async () => {
await deleteAllRules(supertest, log);
await deleteAllPrebuiltRuleAssets(es);
});
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);
await installPrebuiltRulesAndTimelines(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);
// Get installed rules
const { body: rulesResponse } = await supertest
.get(DETECTION_ENGINE_RULES_URL_FIND)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
// Assert that installed rules are from package 99.0.0 and not from prerelease (beta) package
expect(rulesResponse.data.length).toBe(1);
expect(rulesResponse.data[0].name).not.toContain('beta');
expect(rulesResponse.data[0].name).toContain('99.0.0');
});
});
};

View file

@ -29,10 +29,6 @@ export default ({ loadTestFile }: FtrProviderContext): void => {
loadTestFile(require.resolve('./export_rules'));
loadTestFile(require.resolve('./find_rules'));
loadTestFile(require.resolve('./find_rule_exception_references'));
loadTestFile(require.resolve('./get_prebuilt_rules_status'));
loadTestFile(require.resolve('./get_prebuilt_timelines_status'));
loadTestFile(require.resolve('./install_prebuilt_rules'));
loadTestFile(require.resolve('./get_rule_management_filters'));
loadTestFile(require.resolve('./fleet_integration'));
});
};

View file

@ -0,0 +1,43 @@
/*
* 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';
import path from 'path';
export const BUNDLED_PACKAGE_DIR = path.join(
path.dirname(__filename),
'./fleet_bundled_packages/fixtures'
);
// 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('./install_large_prebuilt_rules_package.ts')],
kbnTestServer: {
...functionalConfig.get('kbnTestServer'),
serverArgs: [
...functionalConfig.get('kbnTestServer.serverArgs'),
/* Tests in this directory simulate an air-gapped environment in which the instance doesn't have access to EPR.
* To do that, we point the Fleet url to an invalid URL, and instruct Fleet to fetch bundled packages at the
* location defined in BUNDLED_PACKAGE_DIR.
* Since we want to test the installation of a large package, we created a specific package `security_detection_engine-100.0.0`
* which contains 15000 rules assets and 750 unique rules, and attempt to install it.
*/
`--xpack.fleet.registryUrl=http://invalidURL:8080`,
`--xpack.fleet.developer.bundledPackageLocation=${BUNDLED_PACKAGE_DIR}`,
],
env: {
/* Limit the heap memory to the lowest amount with which Kibana doesn't crash with an out of memory error
* when installing the large package.
*/
NODE_OPTIONS: '--max-old-space-size=700',
},
},
};
}

View file

@ -0,0 +1,49 @@
/*
* 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 { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import { deleteAllRules, getPrebuiltRulesAndTimelinesStatus } from '../../utils';
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_large_prebuilt_rules_package', () => {
beforeEach(async () => {
await deleteAllRules(supertest, log);
await deleteAllPrebuiltRuleAssets(es);
});
afterEach(async () => {
await deleteAllRules(supertest, log);
await deleteAllPrebuiltRuleAssets(es);
});
it('should install a package containing 15000 prebuilt rules without crashing', 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);
// Install the package with 15000 prebuilt historical version of rules rules and 750 unique rules
await installPrebuiltRulesAndTimelines(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(750);
expect(statusAfterPackageInstallation.rules_not_installed).toBe(0);
expect(statusAfterPackageInstallation.rules_not_updated).toBe(0);
});
});
};

View file

@ -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('.')],
};
}

View file

@ -10,6 +10,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
import {
createRule,
deleteAllRules,
deleteRule,
getPrebuiltRulesAndTimelinesStatus,
getSimpleRule,
installPrebuiltRulesAndTimelines,
@ -90,6 +91,20 @@ export default ({ getService }: FtrProviderContext): void => {
});
});
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,
});
});
it('should return available rule updates', async () => {
const ruleAssetSavedObjects = getRuleAssetSavedObjects();
await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects);
@ -109,6 +124,25 @@ export default ({ getService }: FtrProviderContext): void => {
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,
});
});
});
describe(`rule package with historical versions`, () => {
@ -146,7 +180,21 @@ export default ({ getService }: FtrProviderContext): void => {
});
});
it('should return available rule updates when previous historical versions available)', async () => {
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,
});
});
it('should return available rule updates when previous historical versions available', async () => {
await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects());
await installPrebuiltRulesAndTimelines(supertest);

View file

@ -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 { FtrProviderContext } from '../../common/ftr_provider_context';
// eslint-disable-next-line import/no-default-export
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('./fleet_integration'));
});
};

View file

@ -55,6 +55,11 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
`--home.disableWelcomeScreen=true`,
// Specify which version of the detection-rules package to install
// `--xpack.securitySolution.prebuiltRulesPackageVersion=8.3.1`,
// Set an inexistent directory as the Fleet bundled packages location
// in order to force Fleet to reach out to the registry to download the
// packages listed in fleet_packages.json
// See: https://elastic.slack.com/archives/CNMNXV4RG/p1683033379063079
`--xpack.fleet.developer.bundledPackageLocation=./inexistentDir`,
],
},
};