Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution] Converge detection engine on single schema representation #96186

Merged
merged 31 commits into from
Apr 14, 2021

Conversation

marshallmain
Copy link
Contributor

@marshallmain marshallmain commented Apr 3, 2021

Summary

In this PR we:

  • Migrate siem.signals alerts to convert null values to undefined and also normalize threshold.field in the SO so we don't have to normalize it at runtime
  • Delete signalParamsSchema in favor of io-ts based schemas that we already use for the API, making it easier to maintain parity between API types and internal SO/detection engine types
  • Remove most usage of RuleTypeParams interface, which was a pseudo-union of all 6 rule types but typed any rule-type specific fields as optional (and also had to be manually maintained, rather than being generated from the schema)
    • As part of this removal, functions that used RuleTypeParams and fields from the rule SO were converted to take the rule SO as an entire object instead (see e.g. buildBulkBody)
  • Added more mock data closer to the schema that defines the data structure in rule_schemas.mock.ts and started reducing mock data duplication

Future work:
[ ] If/when #95843 is addressed, we can convert from using the rule SO structure to using the Alert structure
[ ] We need to convert the import and addPrepackaged routes to use rule-type specific validation

Checklist

Delete any items that are not applicable to this PR.

For maintainers

@marshallmain marshallmain changed the title [Draft][Security Solution] Converge detection engine on single schema representation [Security Solution] Converge detection engine on single schema representation Apr 7, 2021
@marshallmain marshallmain marked this pull request as ready for review April 7, 2021 07:12
@marshallmain marshallmain requested review from a team as code owners April 7, 2021 07:12
@marshallmain marshallmain added release_note:skip Skip the PR/issue when compiling release notes Team: SecuritySolution Security Solutions Team working on SIEM, Endpoint, Timeline, Resolver, etc. Team:Detections and Resp Security Detection Response Team v7.13.0 v8.0.0 labels Apr 7, 2021
@@ -159,12 +159,11 @@ const commonParams = {
tags,
interval,
enabled,
throttle: throttleOrNull,
throttle,
Copy link
Contributor Author

@marshallmain marshallmain Apr 8, 2021

Choose a reason for hiding this comment

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

Minor API change here, no longer allowing null to be sent in as a value for throttle. We already use throttle: 'no_actions' in the UI and API tests to represent the same concept that null represents here, and null is not listed as an allowed value in the API docs.

The driving reason for making this change is that the existing manually created response schema defines throttle as an optional string (not null), and this newer schema builds both the request and response schema using the same structure to ensure parity between them. If we leave this field as throttleOrNull then the new response schema will allow null and therefore will be incompatible with the existing response schema. So the best way to get consistency between the existing response and the new response is to simply disallow null as an input in this schema, a slight change from existing behavior but still compatible with our public docs.

created_at,
created_by: createdByOrNull,
created_by,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Similar to above with throttleOrNull being changed to throttle, in this case updated_by and created_by are no longer allowed to be null in the response to get consistency with the existing response schema.

typeof params.threshold === 'object' &&
!Array.isArray(params.threshold)
? {
field: Array.isArray(params.threshold.field)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is fun 😂

@madirey
Copy link
Contributor

madirey commented Apr 12, 2021

Threshold work looks good 👍

@spong spong requested a review from a team April 12, 2021 15:51
@marshallmain
Copy link
Contributor Author

@elasticmachine merge upstream

? params.exceptions_list
: params.lists != null
? params.lists
: [],
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@FrankHassanabad Is there any other conversion that needs to be done on these fields or is the rename enough?

Copy link
Contributor

@rylnd rylnd left a comment

Choose a reason for hiding this comment

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

Marshall walked me through this code earlier. We uncovered one missing normalization and a few more non-essential improvements that he's working on right now. I called a few of them out in review here, but I trust CI and Marshall's diligence for the rest.

Overall, WOW. This is an incredible payment toward the detection engine tech debt, as evidenced by the diff stats here 😉 . The fact that this will enable further cleanup is even more exciting!

Thank you for making the time to clean this up, it is very much appreciated.

@@ -332,5 +332,5 @@ export const editedRule = {
export const expectedExportedRule = (ruleResponse: Cypress.Response) => {
const jsonrule = ruleResponse.body;

return `{"author":[],"actions":[],"created_at":"${jsonrule.created_at}","updated_at":"${jsonrule.updated_at}","created_by":"elastic","description":"${jsonrule.description}","enabled":false,"false_positives":[],"from":"now-17520h","id":"${jsonrule.id}","immutable":false,"index":["exceptions-*"],"interval":"10s","rule_id":"rule_testing","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":${jsonrule.risk_score},"risk_score_mapping":[],"name":"${jsonrule.name}","query":"${jsonrule.query}","references":[],"severity":"${jsonrule.severity}","severity_mapping":[],"updated_by":"elastic","tags":[],"to":"now","type":"query","threat":[],"throttle":"no_actions","version":1,"exceptions_list":[]}\n{"exported_count":1,"missing_rules":[],"missing_rules_count":0}\n`;
return `{"id":"${jsonrule.id}","updated_at":"${jsonrule.updated_at}","updated_by":"elastic","created_at":"${jsonrule.created_at}","created_by":"elastic","name":"${jsonrule.name}","tags":[],"interval":"10s","enabled":false,"description":"${jsonrule.description}","risk_score":${jsonrule.risk_score},"severity":"${jsonrule.severity}","output_index":".siem-signals-default","author":[],"false_positives":[],"from":"now-17520h","rule_id":"rule_testing","max_signals":100,"risk_score_mapping":[],"severity_mapping":[],"threat":[],"to":"now","references":[],"version":1,"exceptions_list":[],"immutable":false,"type":"query","language":"kuery","index":["exceptions-*"],"query":"${jsonrule.query}","throttle":"no_actions","actions":[]}\n{"exported_count":1,"missing_rules":[],"missing_rules_count":0}\n`;
Copy link
Contributor

Choose a reason for hiding this comment

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

As discussed offline: the ordering here changed due to the object now being build piecemeal from:

  • base fields
  • type-specific fields
  • action sidecar fields

@@ -14,7 +14,7 @@ import {
} from '../../../../common/constants';

import { NotificationAlertTypeDefinition } from './types';
import { RuleAlertAttributes } from '../signals/types';
import { AlertAttributes } from '../signals/types';
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: we talked about potentially removing the AlertAttributes type in favor of the analogous Alert type from alerting (which we're already using below to type response mocks).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried removing this briefly and found that Alert in the alerting framework does some processing on the raw SO before it becomes an Alert: it at least converts the created_at and updated_at fields from string to Date, I didn't look further than that. We don't want to duplicate or couple to that conversion process, so it's probably best to leave AlertAttributes as a completely raw representation of the SO until we get access to the full Alert in the executor through a formal alerting framework parameter. At that point we should be able to remove the entire codepath where we sidestep the alerting framework and directly access the SO, and remove AlertAttributes with it.

@@ -101,12 +99,9 @@ export const createRulesBulkRoute = (
});
}

/**
* TODO: Remove this use of `as` by utilizing the proper type
Copy link
Contributor

Choose a reason for hiding this comment

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

😎

}: SingleBulkCreateParams): Promise<SingleBulkCreateResponse> => {
const ruleParams = ruleSO.attributes.params;
Copy link
Contributor

Choose a reason for hiding this comment

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

A++

@@ -83,7 +77,7 @@ export const thresholdExecutor = async ({
services,
logger,
ruleId: ruleParams.ruleId,
bucketByFields: normalizeThresholdField(ruleParams.threshold.field),
Copy link
Contributor

Choose a reason for hiding this comment

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

Sanity check: this is no longer necessary due to the migration, correct?

Copy link
Contributor Author

@marshallmain marshallmain Apr 14, 2021

Choose a reason for hiding this comment

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

Right, the migration will convert all threshold.field values to string[]. By this point in the executor we've also runtime-validated with thresholdSpecificRuleParams so the compiler knows ruleParams.threshold must be of type ThresholdNormalized

});

export const sampleRuleSO = (): SavedObject<RuleAlertAttributes> => {
export const sampleRuleSO = <T extends RuleParams>(params: T): SavedObject<AlertAttributes<T>> => {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: this looks to share a lot of functionality with getResult; could we perhaps rewrite this in terms of getResult?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great question, I spent some time while refactoring trying to combine these which lead to creating #95843. Summary: once we have access to the alerting framework Alert object in the executor, we can further unify these types and mocks.

Expanded: similar to above with removing AlertAttributes, this mock is used for functions that expect a raw Alert SO as opposed to the processed Alert that the alerting framework client provides. These functions are called from the detection engine executor function where we don't have access to the alerts client so we (currently) have to sidestep the alerts client and get the SO directly using the SO client. getResult is used as a mock for functions that take an Alert as a parameter. Those functions are called from the API routes since in those routes we have access to an alerts client.

import { sampleDocNoSortId, sampleDocSearchResultsNoSortId } from '../__mocks__/es_results';
import { sampleThresholdSignalHistory } from '../__mocks__/threshold_signal_history.mock';
import { calculateThresholdSignalUuid } from '../utils';
import { transformThresholdResultsToEcs } from './bulk_create_threshold_signals';

describe('transformThresholdNormalizedResultsToEcs', () => {
it('should return transformed threshold results for pre-7.12 rules', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Are these tests no longer relevant due to the migration?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, we shouldn't need to worry about pre-7.12 rules anymore. Since transformThresholdResultsToEcs takes a normalized threshold parameter even before these changes, these tests seem to be more about testing the conversion from Threshold to ThresholdNormalized rather than testing transformThresholdResultsToEcs with different inputs. The conversion has test coverage in utils.test.ts so I don't think we're losing significant coverage here.

@marshallmain
Copy link
Contributor Author

@elasticmachine merge upstream

@kibanamachine
Copy link
Contributor

💚 Build Succeeded

Metrics [docs]

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
securitySolution 215.6KB 215.4KB -168.0B

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

Copy link
Member

@pmuellr pmuellr left a comment

Choose a reason for hiding this comment

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

LGTM

@marshallmain marshallmain merged commit ff7c533 into elastic:master Apr 14, 2021
kibanamachine added a commit to kibanamachine/kibana that referenced this pull request Apr 14, 2021
…entation (elastic#96186)

* Replace validation function in signal executor

* Remove more RuleTypeParams usage

* Add security solution rules migration to alerting plugin

* Handle and test null value in threshold.field

* Remove runtime normalization of threshold field

* Remove signalParamsSchema

Co-authored-by: Davis Plumlee <davis.plumlee@elastic.co>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
@kibanamachine
Copy link
Contributor

💚 Backport successful

Status Branch Result
7.x

This backport PR will be merged automatically after passing CI.

kibanamachine added a commit that referenced this pull request Apr 14, 2021
…entation (#96186) (#97152)

* Replace validation function in signal executor

* Remove more RuleTypeParams usage

* Add security solution rules migration to alerting plugin

* Handle and test null value in threshold.field

* Remove runtime normalization of threshold field

* Remove signalParamsSchema

Co-authored-by: Davis Plumlee <davis.plumlee@elastic.co>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

Co-authored-by: Marshall Main <55718608+marshallmain@users.noreply.github.com>
Co-authored-by: Davis Plumlee <davis.plumlee@elastic.co>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
auto-backport Deprecated - use backport:version if exact versions are needed release_note:skip Skip the PR/issue when compiling release notes Team:Detections and Resp Security Detection Response Team Team: SecuritySolution Security Solutions Team working on SIEM, Endpoint, Timeline, Resolver, etc. v7.13.0 v8.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants