Skip to content

Conversation

cnasikas
Copy link
Member

Summary

This PR prevents internally managed rule types from being enabled/disabled through the Rule Disable, Enable and Bulk Enable and Disable APIs. The prevention logic is on he route level because we want to support updating internally managed rules from the alerts client.

Partial fixes: #222101

Checklist

Check the PR satisfies following conditions.

Reviewers should verify this PR satisfies this list as well.

@cnasikas cnasikas self-assigned this Sep 27, 2025
@cnasikas cnasikas added release_note:skip Skip the PR/issue when compiling release notes backport:skip This PR does not require backporting Team:ResponseOps Platform ResponseOps team (formerly the Cases and Alerting teams) t// v9.2.0 labels Sep 27, 2025
@cnasikas cnasikas marked this pull request as ready for review September 27, 2025 13:15
@cnasikas cnasikas requested a review from a team as a code owner September 27, 2025 13:15
@elasticmachine
Copy link
Contributor

Pinging @elastic/response-ops (Team:ResponseOps)

@cnasikas
Copy link
Member Author

@elasticmachine merge upstream

@elasticmachine
Copy link
Contributor

elasticmachine commented Sep 28, 2025

💔 Build Failed

Failed CI Steps

Test Failures

  • [job] [logs] FTR Configs #42 / alerting api integration security and spaces enabled Alerts - Group 5 bulk untrack should create new alerts if run rules again after alerts are untracked
  • [job] [logs] FTR Configs #80 / Fleet Endpoints Integrations inputs_with_standalone_docker_agent generate a valid config for standalone agents
  • [job] [logs] FTR Configs #80 / Fleet Endpoints Integrations inputs_with_standalone_docker_agent generate a valid config for standalone agents

Metrics [docs]

✅ unchanged

History

cc @cnasikas

import { rulesClientMock } from '../../mocks';
import { validateInternalRuleTypesByQuery } from './validate_internal_rule_types_by_query';

describe('validateInternalRuleTypesByQuery', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: missing happy path test.

});

describe('internally managed rule types', () => {
it('throws 400 if the rule type is internally managed', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Reading these for the different APIs, I noticed you don't test the "Rule type not found" error path. I think it is fine, but I just wanna confirm. For each of these routes, the rulesClient does that check too, right?

import type { RegistryRuleType } from '../../rule_type_registry';
import type { RulesClient } from '../../rules_client';

export const validateInternalRuleTypesByQuery = async ({
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel like we had this conversation, but I don't remember the outcome 😅

I agree that the check should be done at the route level, but why is this function not defined in the rules client itself?

The routes would then do

rulesClient.validateInternalRuleTypesByQuery({
  req: body,
  ruleTypes,
  operationText: 'disable',
})
const bulkDisableResults = await rulesClient.bulkDisableRules({ filter, ids, untrack });

Or something like that.

const disableParams = { id, untrack };

try {
const rule = await rulesClient.get({ id });
Copy link
Contributor

Choose a reason for hiding this comment

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

Why don't these(disable+enable) use validateInternalRuleTypesByQuery? Is it because of getRuleTypesByQuery? The performance should be fine if filter is optional and only an id is passed.

Copy link
Member Author

Choose a reason for hiding this comment

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

The validateInternalRuleTypesByQuery does an ES aggregation to see if the rule is internal. I find it simpler to reason, explicit, and more performant if we do a single check. Also, if in the future the validateInternalRuleTypesByQuery changes to a way that supports only bulk edits, we will not have to go to all single routes and change the logic.


it('should throw 400 error when trying to update an internally managed rule type', async () => {
const { body: createdRule1 } = await supertest
const { body: createdRule } = await supertest
Copy link
Contributor

Choose a reason for hiding this comment

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

This test is for update but the change was done to bulk_edit_rules_route. Should a test also be added to x-pack/platform/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_edit.ts?

Copy link
Member Author

Choose a reason for hiding this comment

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

The tests for the bulk_edit and update were introduced in this #234937. Here, I fixed a bug in the test, I realized.

const ruleTypesByQuery = await rulesClient.getRuleTypesByQuery({ filter, ids });
const ruleTypeIds = new Set(ruleTypesByQuery.ruleTypes);

for (const ruleTypeId of ruleTypeIds) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If this validation is for bulk operations, maybe we should add the errors to an array and only throw at the end with all the failing rules? wdyt?

I say "maybe" because if the user sets the filter option, it isn't very helpful.

However, in a scenario where multiple IDs are passed, if we throw the first one, the user removes it from the list, and then the API throws again, and again. It is not great UX.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, it is not possible with the filter option to do it, but I would give it a try for the ids.

const ruleType = ruleTypes.get(ruleTypeId);

if (!ruleType) {
throw Boom.badRequest('Rule type not found');
Copy link
Contributor

Choose a reason for hiding this comment

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

you could include the rule type id here, should help to debug if needed

}

if (ruleType.internallyManaged) {
throw Boom.badRequest(
Copy link
Contributor

@jcger jcger Sep 29, 2025

Choose a reason for hiding this comment

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

I have no time to test it, so I might be wrong here. But if we call a bulk action, let's say for all rules. What exactly happens here? For me it looks like, if there is an internal rule type, it will throw, not being able to run that bulk action on the other rules (those belonging to non internally managed types). This would make the whole bulk action useless as soon as there is an internally managed rule type

Copy link
Member Author

Choose a reason for hiding this comment

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

This is correct, and it is the intended behavior. It is much better to fail early for all than to introduce side effects where some of them are updated and some are not. Aside from the complexity of handling the various cases, the route should be idempotent on every call and failing early, regardless of achieving that.

Copy link
Contributor

Choose a reason for hiding this comment

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

Couldn't we ignore internally managed rules by filtering them out? That should keep it idempotent. We don't have to throw an error once we get to internally managed rule type. It's also worsening UX: users with internally managed rules won't be able to bulk edit

Copy link
Member Author

@cnasikas cnasikas Sep 29, 2025

Choose a reason for hiding this comment

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

An API should be clear on its behavior. Filtering out internal rule types would confuse users because it is not clear why they are not updated (the API returned 200). I get the UX issue, which I agree with, but I think this should be addressed on the UI level. Btw, all prior validations work like this. If one of the rules fails to pass a condition, the whole API throws.

Copy link
Member Author

Choose a reason for hiding this comment

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

I will open an issue for the UX/UI. We need to make it clear that the rule is internally managed.

Copy link
Contributor

@adcoelho adcoelho left a comment

Choose a reason for hiding this comment

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

Tested and works as expected 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport:skip This PR does not require backporting release_note:skip Skip the PR/issue when compiling release notes Team:ResponseOps Platform ResponseOps team (formerly the Cases and Alerting teams) t// v9.2.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Prevent almost all significant event rule mutations
4 participants