Skip to content

Commit f912fdc

Browse files
rylndkibanamachine
andauthored
[Detection Engine] Remove Endpoint List bootstrap calls from Rule-related routes (elastic#258266)
## Summary This PR removes two calls to `createEndpointList()` from rule-related code paths. [PR elastic#233289](elastic#233289) removed the use of endpoint exceptions in rules, so ensuring the endpoint list exists in these rule routes is no longer required. Dropping these calls fixes a 9.3 issue where users with **only** the Rules Kibana feature could not preview rules, because the routes were invoking `createEndpointList()`, which depended on list/exception permissions those users do not have. The problem was first seen on the Alerts RBAC branch but affects current 9.3 behavior. ### Expected behavior for rules that reference an endpoint exception list - **Until the feature flag is enabled/removed:** Rules keep using any associated endpoint exception list when the `endpointExceptionsMovedUnderManagement` feature flag is off. No change to that behavior. - **When the user cannot read the endpoint exception list:** Endpoint exceptions are ignored (no error). Rule creation and preview succeed; only endpoint exception list items are skipped for that execution. A new integration test under `create_rules` covers this: a user with only the Rules feature can create and preview a rule that references the endpoint exception list, and the preview completes successfully with no errors while endpoint exceptions are ignored. --- ## How to Review 1. **Feature flag and execution:** Ensure rule execution and `getExceptions` still respect `endpointExceptionsMovedUnderManagement` (endpoint lists filtered out when the flag is on) and that missing read permission for the endpoint list results in endpoint exceptions being ignored, not in failures. 1. **New integration test:** Run the new test that uses a role with only the Rules feature (e.g. `rulesAllPreviewIndexRole`), creates a rule that references the endpoint exception list, and runs a rule preview. It should pass and assert that the preview returns no errors (validating that endpoint exceptions are ignored when the user cannot read the endpoint list). 1. **Regression:** Run the existing detections/rule creation and prebuilt-rule installation tests to confirm no regressions from removing `createEndpointList()`. ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [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 - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [x] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
1 parent 1b8b97f commit f912fdc

9 files changed

Lines changed: 142 additions & 15 deletions

File tree

x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ export const createRuleRoute = (router: SecuritySolutionPluginRouter): void => {
6161

6262
const rulesClient = await ctx.alerting.getRulesClient();
6363
const detectionRulesClient = ctx.securitySolution.getDetectionRulesClient();
64-
const exceptionsClient = ctx.lists?.getExceptionListClient();
65-
const { canWriteEndpointList } = await ctx.securitySolution.getEndpointAuthz();
6664

6765
if (request.body.rule_id != null) {
6866
const rule = await readRules({
@@ -78,10 +76,6 @@ export const createRuleRoute = (router: SecuritySolutionPluginRouter): void => {
7876
}
7977
}
8078

81-
// This will create the endpoint list if it does not exist yet
82-
if (canWriteEndpointList) {
83-
await exceptionsClient?.createEndpointList();
84-
}
8579
checkDefaultRuleExceptionListReferences({
8680
exceptionLists: request.body.exceptions_list,
8781
});

x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,6 @@ export const previewRulesRoute = (
152152
});
153153
throwAuthzError(await mlAuthz.validateRuleType(internalRule.params.type));
154154

155-
const listsContext = await context.lists;
156-
await listsContext?.getExceptionListClient().createEndpointList();
157-
158155
const spaceId = siemClient.getSpaceId();
159156
const previewId = uuidv4();
160157
const username = security?.authc.getCurrentUser(request)?.username;

x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/basic_license_essentials_tier/find_rule_exception_references.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ export default ({ getService }: FtrProviderContext) => {
221221
.expect(200);
222222

223223
const refs = references.references.flatMap((ref: RuleReferencesSchema) => Object.keys(ref));
224-
expect(refs.sort()).to.eql(['i_exist', 'i_exist_2', 'endpoint_list'].sort());
224+
expect(refs.sort()).to.eql(['i_exist', 'i_exist_2'].sort());
225225
});
226226
});
227227
};

x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_preview/preview_rules.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,25 @@ export default ({ getService }: FtrProviderContext) => {
105105
});
106106

107107
it('should NOT be able to preview a rule', async () => {
108-
await supertestWithoutAuth
108+
// t1_analyst has Rules read but not preview index privileges; they get 200 with an error
109+
// in the body (no longer 403, since we no longer call createEndpointList on this route).
110+
const { body } = await supertestWithoutAuth
109111
.post(DETECTION_ENGINE_RULES_PREVIEW)
110112
.auth(role, 'changeme')
111113
.set('kbn-xsrf', 'true')
112114
.send(getSimplePreviewRule())
113-
.expect(403);
115+
.expect(200);
116+
117+
const { logs } = getSimpleRulePreviewOutput(undefined, [
118+
{
119+
errors: [
120+
'Missing "read" privileges for the ".preview.alerts-security.alerts" or ".internal.preview.alerts-security.alerts" indices. Without these privileges you cannot use the Rule Preview feature.',
121+
],
122+
warnings: [],
123+
duration: 0,
124+
},
125+
]);
126+
expect(body).to.eql({ logs });
114127
});
115128
});
116129

x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_rules.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
} from '@kbn/security-solution-plugin/common/constants';
1717
import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
1818
import { ROLES } from '@kbn/security-solution-plugin/common/test';
19+
import { ENDPOINT_ARTIFACT_LISTS, ENDPOINT_LIST_URL } from '@kbn/securitysolution-list-constants';
1920

2021
import {
2122
deleteAllRules,
@@ -40,9 +41,12 @@ import {
4041
fetchRule,
4142
waitForAlertToComplete,
4243
refreshIndex,
44+
rulesAllPreviewIndexRole,
45+
createExceptionListItem,
4346
} from '../../../utils';
4447
import { createUserAndRole, deleteUserAndRole } from '../../../../../config/services/common';
4548
import { ROLE } from '../../../../../config/services/security_solution_edr_workflows_roles_users';
49+
import { deleteAllExceptions } from '../../../../lists_and_exception_lists/utils';
4650

4751
export default ({ getService }: FtrProviderContext) => {
4852
const supertest = getService('supertest');
@@ -748,5 +752,83 @@ export default ({ getService }: FtrProviderContext) => {
748752
);
749753
});
750754
});
755+
756+
describe('@skipInServerless as a user with only the Rules feature', () => {
757+
beforeEach(async () => {
758+
await deleteAllRules(supertest, log);
759+
await utils.createSuperTestWithCustomRole(rulesAllPreviewIndexRole);
760+
});
761+
762+
afterEach(async () => {
763+
await utils.cleanUpCustomRoles();
764+
await deleteAllRules(supertest, log);
765+
await deleteAllExceptions(supertest, log);
766+
});
767+
768+
it('creates and previews a rule that references the endpoint exception list', async () => {
769+
// ensure the endpoint list exists
770+
await supertest.post(ENDPOINT_LIST_URL).set('kbn-xsrf', 'true').send().expect(200);
771+
// add 1 item to the existing endpoint exception list, which will be queried by the rule
772+
await createExceptionListItem(supertest, log, {
773+
description: 'item description',
774+
entries: [
775+
{
776+
field: 'keyword',
777+
operator: 'included',
778+
type: 'match',
779+
value: 'something',
780+
},
781+
],
782+
list_id: ENDPOINT_ARTIFACT_LISTS.endpointExceptions.id,
783+
name: ENDPOINT_ARTIFACT_LISTS.endpointExceptions.id,
784+
os_types: [],
785+
type: 'simple',
786+
namespace_type: 'agnostic',
787+
});
788+
789+
const ruleParams = getCustomQueryRuleParams({
790+
rule_id: 'rule-with-endpoint-exception-list',
791+
exceptions_list: [
792+
{
793+
id: ENDPOINT_ARTIFACT_LISTS.endpointExceptions.id,
794+
list_id: ENDPOINT_ARTIFACT_LISTS.endpointExceptions.id,
795+
namespace_type: 'agnostic',
796+
type: 'endpoint',
797+
},
798+
],
799+
});
800+
801+
const restrictedApis = detectionsApi.withUser({
802+
username: rulesAllPreviewIndexRole.name,
803+
password: 'changeme',
804+
});
805+
806+
const { body: createdRule } = await restrictedApis
807+
.createRule({ body: ruleParams })
808+
.expect(200);
809+
810+
expect(createdRule.exceptions_list).toHaveLength(1);
811+
expect(createdRule.exceptions_list?.[0]).toMatchObject({
812+
list_id: ENDPOINT_ARTIFACT_LISTS.endpointExceptions.id,
813+
type: 'endpoint',
814+
});
815+
816+
const { body: previewBody } = await restrictedApis
817+
.rulePreview({
818+
query: {},
819+
body: {
820+
...ruleParams,
821+
invocationCount: 1,
822+
timeframeEnd: new Date().toISOString(),
823+
},
824+
})
825+
.expect(200);
826+
827+
expect(previewBody.previewId).toBeDefined();
828+
expect(previewBody.logs.length).toBeGreaterThan(0);
829+
expect(previewBody.logs[0].duration).toBeGreaterThan(0);
830+
expect(previewBody.logs[0].errors).toHaveLength(0);
831+
});
832+
});
751833
});
752834
};

x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/utils/auth/roles.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,38 @@
55
* 2.0.
66
*/
77

8-
import { RULES_FEATURE_ID_V3 } from '@kbn/security-solution-features/constants';
8+
import {
9+
RULES_FEATURE_ID_V2,
10+
RULES_FEATURE_ID_V3,
11+
} from '@kbn/security-solution-features/constants';
912
import type { CustomRole } from '../../../../config/services/types';
1013

14+
/** Role with Rules V2 ALL and preview indices access. Should be able to create rules and preview them. **/
15+
export const rulesAllPreviewIndexRole: CustomRole = {
16+
name: 'rules_all_preview_index',
17+
privileges: {
18+
elasticsearch: {
19+
indices: [
20+
{
21+
names: [
22+
'.preview.alerts-security.alerts-*',
23+
'.internal.preview.alerts-security.alerts-*',
24+
],
25+
privileges: ['read'],
26+
},
27+
],
28+
},
29+
kibana: [
30+
{
31+
feature: {
32+
[RULES_FEATURE_ID_V2]: ['all'],
33+
},
34+
spaces: ['*'],
35+
},
36+
],
37+
},
38+
};
39+
1140
/** Role with only Rules V3 ALL (no Alerts or other SIEM features). */
1241
export const rulesAllV3OnlyRole: CustomRole = {
1342
name: 'rules_all_v3_only',

x-pack/solutions/security/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/exceptions/alerts_table_flow/endpoint_exceptions.cy.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
* 2.0.
66
*/
77

8-
import { deleteEndpointExceptionList } from '../../../../../tasks/api_calls/exceptions';
8+
import {
9+
createEndpointExceptionList,
10+
deleteEndpointExceptionList,
11+
} from '../../../../../tasks/api_calls/exceptions';
912
import { deleteAlertsAndRules } from '../../../../../tasks/api_calls/common';
1013
import {
1114
expandFirstAlert,
@@ -54,6 +57,7 @@ describe(
5457
login();
5558
deleteAlertsAndRules();
5659
deleteEndpointExceptionList();
60+
createEndpointExceptionList();
5761

5862
cy.task('esArchiverLoad', { archiveName: 'endpoint' });
5963
createRule(getEndpointRule()).then((rule) =>

x-pack/solutions/security/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/exceptions/shared_exception_lists_management/shared_exception_list_page/filter_table.cy.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ import {
1111
EXCEPTIONS_TABLE_SHOWING_LISTS,
1212
EXCEPTIONS_TABLE_LIST_NAME,
1313
} from '../../../../../../screens/exceptions';
14-
import { createExceptionList } from '../../../../../../tasks/api_calls/exceptions';
14+
import {
15+
createEndpointExceptionList,
16+
createExceptionList,
17+
deleteExceptionLists,
18+
} from '../../../../../../tasks/api_calls/exceptions';
1519
import { createRule } from '../../../../../../tasks/api_calls/rules';
1620
import {
1721
waitForExceptionsTableToBeLoaded,
@@ -41,6 +45,8 @@ const getExceptionList2 = () => ({
4145
describe('Filter Lists', { tags: ['@ess', '@serverless', '@skipInServerlessMKI'] }, () => {
4246
beforeEach(() => {
4347
login();
48+
deleteExceptionLists();
49+
createEndpointExceptionList();
4450

4551
// Create exception list associated with a rule
4652
createExceptionList(getExceptionList2(), getExceptionList2().list_id).then((response) =>

x-pack/solutions/security/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/exceptions/shared_exception_lists_management/shared_exception_list_page/manage_lists.cy.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
EXCEPTIONS_TABLE_SHOWING_LISTS,
3333
} from '../../../../../../screens/exceptions';
3434
import {
35+
createEndpointExceptionList,
3536
createExceptionList,
3637
deleteExceptionLists,
3738
} from '../../../../../../tasks/api_calls/exceptions';
@@ -64,6 +65,7 @@ describe(
6465
beforeEach(() => {
6566
deleteAlertsAndRules();
6667
deleteExceptionLists();
68+
createEndpointExceptionList();
6769
createRule(getNewRule({ name: 'Another rule' }));
6870

6971
// Create exception list associated with a rule

0 commit comments

Comments
 (0)