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][Detections] Refactor signal ancestry to allow multiple parents #76531

Merged
merged 12 commits into from
Sep 9, 2020

Conversation

marshallmain
Copy link
Contributor

@marshallmain marshallmain commented Sep 2, 2020

Summary

Remaining issues:

  1. On existing .signals mappings there will be no filtering/querying on signal.parents or signal.depth. We would have to ask the user to create a new space for full sequence functionality

TODO: Force mapping update so existing signal indices will add the new fields (signal.depth, signal.parents, signal.ancestors.index) to the mapping

This PR changes the way that signal.parent.rule and signal.ancestors.rule are populated, as well as signal.parent.depth and signal.ancestors.depth. They will contain the rule and depth of the parent event rather than the current event as they do now. It also deprecates signal.parent in favor of signal.parents which is expected to be an array of parent events/signals. It also refactors buildSignal to make it easy to build a signal from one or multiple events.

signal.parent currently contains a mix of information from the current signal and the parent signal/event - signal.parent.rule contains the current rule id (not the parent rule id), but the other fields such as signal.parent.id are from the parent signal/event.

Existing signal structure chart
New signal structure chart WITHOUT sequence based rule
New signal structure chart WITH sequence based rule (and using the signal above as an input)

Post 7.10 signals that are built on pre-7.10 signals will have some duplication in signal.ancestors.rule and signal.ancestors.depth. If a post-7.10 signal is built on a pre-7.10 chain of signals, then signal.ancestors will be

[
  {rule: 1, type: event, id: 0, depth: 1},
  {rule: 2, type: signal, id: 1, depth: 2},
  {rule: 3, type: signal, id: 2, depth: 3},
  {rule: 3, type: signal, id: 3, depth: 3}, -> first ancestors entry added by a post-7.10 signal
  {rule: 4, type: signal, id: 4, depth: 4} -> future signals increment depth correctly
]

The rule de-duplication logic that prevents a rule from triggering on a signal with that same rule in its ancestry still works in this case since no data is missing.

Checklist

Delete any items that are not applicable to this PR.

For maintainers

"ancestors": {
"properties": {
"rule": {
"type": "keyword"
},
"index": {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

index was in parent but not here

@@ -58,7 +58,10 @@ export const buildBulkBody = ({
tags,
throttle,
});
const signal = buildSignal(doc, rule);
const signal: Signal = {
...buildSignal([doc], rule),
Copy link
Contributor Author

@marshallmain marshallmain Sep 2, 2020

Choose a reason for hiding this comment

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

buildSignal now expects an array of documents and handles building the rule, parents, and ancestors. additionalSignalFields takes a single document and creates the fields for the special case where we only have 1 event triggering the signal (so we can copy the fields from the event into the signal). When we add signals based on EQL sequences we won't be able to copy every event from the sequence into the signal, instead relying on signal.parents to contain references to the sub-events.

@@ -208,4 +210,102 @@ describe('buildRule', () => {
};
expect(rule).toEqual(expected);
});

test('it builds a rule and removes internal tags', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

removeInternalTagsFromRule now happens inside buildRule so 1 additional test here.

};
expect(rule).toEqual(expected);
});

Copy link
Contributor Author

@marshallmain marshallmain Sep 2, 2020

Choose a reason for hiding this comment

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

Next 4 tests are simply moved from build_signal.test.ts since removeInternalTagsFromRule was moved from build_signal.ts to build_rule.ts

return removeInternalTagsFromRule(rule);
};

export const removeInternalTagsFromRule = (rule: Partial<RulesSchema>): Partial<RulesSchema> => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Cut/paste from build_signal.ts since this is rule-related code.

expect(signal).toEqual(expected);
});

test('it builds a signal as expected with original_event if is present and without internal tags in them', () => {
Copy link
Contributor Author

@marshallmain marshallmain Sep 2, 2020

Choose a reason for hiding this comment

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

This test was deleted - buildSignal is no longer responsible for removing internal tags. Test is replaced with the similar test above in build_rule.test.ts

expect(signal).toEqual(expected);
});

test('it builds a ancestor correctly if the parent does exist without internal tags in them', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Deleted this test - buildAncestor no longer needs the rule, so it doesn't need to test a rule with internal tags in it.

},
];
expect(signal).toEqual(expected);
});

test('it removes internal tags from a typical rule', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Next 4 tests moved to build_rule.test.ts

return {
rule: rule.id != null ? rule.id : '',
rule: doc._source.signal.rule.id,
Copy link
Contributor Author

@marshallmain marshallmain Sep 2, 2020

Choose a reason for hiding this comment

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

Semantics change: signal.parents.rule now contains the parent rule rather than current signal rule.

};
} else {
return {
rule: rule.id != null ? rule.id : '',
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the parent doc is not a signal, then there is no signal.parents.rule

return !doc._source.signal.ancestors.some((ancestor) => ancestor.rule === ruleId);
return !(
doc._source.signal.ancestors.some((ancestor) => ancestor.rule === ruleId) ||
doc._source.signal.rule.id === ruleId
Copy link
Contributor Author

@marshallmain marshallmain Sep 2, 2020

Choose a reason for hiding this comment

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

Since signal.ancestors no longer contains the rule id of the current signal we need to check signal.rule.id as well as the ancestors.

parent: Ancestor;
// parent is deprecated: new signals should populate parents instead
// both are optional until all signals with parent are gone and we can safely remove it
parent?: Ancestor;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

SignalSource represents the signal results coming from ES - we need to support pre-7.10 signals which may still have parent or may have parents.

@marshallmain marshallmain added Feature:Detection Rules Security Solution rules and Detection Engine release_note:fix Team:Endpoint Response Endpoint Response Team v7.10.0 v8.0.0 labels Sep 2, 2020
@marshallmain marshallmain marked this pull request as ready for review September 2, 2020 20:24
@marshallmain marshallmain requested review from a team as code owners September 2, 2020 20:24
@elasticmachine
Copy link
Contributor

Pinging @elastic/endpoint-response (Team:Endpoint Response)

@marshallmain
Copy link
Contributor Author

@elasticmachine merge upstream

* creating an array of N+1 ancestors.
* @param doc The parent signal/event for which to extend the ancestry.
*/
export const buildAncestorsSignal = (doc: SignalSourceHit): Signal['ancestors'] => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we rename this to buildAncestors? For parity with buildParent, and since this isn't actually building a signal...

Copy link
Contributor

@madirey madirey left a comment

Choose a reason for hiding this comment

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

Let's get confirmation that the semantics changes are something we can get away with (for existing API users), but otherwise LGTM. Would also like to get that function renamed.

@marshallmain
Copy link
Contributor Author

@elasticmachine merge upstream

@@ -22,11 +22,33 @@
}
}
},
"parents": {
"properties": {
"rule": {
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 the rule.id, not rule.rule_id right? Curious if there's any upside to using one over the other. Like if users often import/export rules, would we lose tracing by using the id?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, this is rule.id. I think for the parents/ancestry we want to know which specific instance of a rule generated the alert so id is a good choice.

We could consider making signal.parent.rule an object where we could have id, rule_id, and any other rule fields that could be useful in signal.parent.rule.*, but that would be a breaking change with the existing signal.parent and signal.ancestors so I think we'd need a migration.

depth: existingSignal.depth + 1,
// We first look for signal.depth and use that if it exists. If it doesn't exist, this should be a pre-7.10 signal
// and should have signal.parent.depth instead. signal.parent.depth in this case is treated as equivalent to signal.depth.
depth: doc._source.signal.depth ?? doc._source.signal?.parent?.depth ?? 1,
Copy link
Contributor

@yctercero yctercero Sep 8, 2020

Choose a reason for hiding this comment

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

Why is the ? needed after doc._source.signal - seems confusing since it was checked on line 16. Should I expect that value to exist in this block of code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, it's not needed - I'll remove it.

* creating an array of N+1 ancestors.
* @param doc The parent signal/event for which to extend the ancestry.
*/
export const buildAncestors = (doc: SignalSourceHit): Signal['ancestors'] => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Are Signal['ancestors'] and Ancestor[] equivalent? If they are, should we use Ancestor[] for consistency, just so as a dev quickly looking through here it's clear buildParent and buildAncestors are returning the same type just one is array and the other isn't?

@marshallmain
Copy link
Contributor Author

@elasticmachine merge upstream

@kibanamachine
Copy link
Contributor

💚 Build Succeeded

Build metrics

✅ unchanged

History

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

Copy link
Contributor

@dhurley14 dhurley14 left a comment

Choose a reason for hiding this comment

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

LGTM. Created a rule (rule-1) which produced some signals on master, then checked out this branch and ran that rule + another rule (rule-2) to populate the "parents" field on signals generated by rule-2. Did not see an issue with migrations or anything on the signals table breaking. Looks good!

@marshallmain marshallmain merged commit 42a6934 into elastic:master Sep 9, 2020
@marshallmain marshallmain deleted the refactor-ancestry branch September 9, 2020 18:05
gmmorris added a commit to gmmorris/kibana that referenced this pull request Sep 10, 2020
* master: (25 commits)
  [bugfix] Replace panel flyout opens 2 flyouts (elastic#76931)
  clean up test (elastic#76887)
  [Enterprise Search] Update shared API request handler (elastic#77112)
  [Maps] convert ESAggSource to TS (elastic#76999)
  Add plugin status API - take 2 (elastic#76732)
  Adds lens as a readable saved object for read-only dashboard users (elastic#77067)
  Skip checking for the reserved realm (elastic#76687)
  Reporting/diagnostics (elastic#74314)
  Reporting/Test: unskip non-screenshot tests (elastic#77088)
  Move metrics to setup and add cgroup metrics (elastic#76730)
  [Enterprise Search] Add Overview landing page/plugin (elastic#76734)
  First pass. Change TS type. Update OpenAPI (elastic#76434)
  [CI] Balance xpack ci groups a bit (elastic#77068)
  [Security_solution][Detections] Refactor signal ancestry to allow multiple parents (elastic#76531)
  [Maps] convert MetricsEditor to TS (elastic#76727)
  IndexMigrator: fix non blocking migration wrapper promise rejection (elastic#77018)
  [Enterprise Search] Update config data endpoint to v2 (elastic#76970)
  [ML] Add decision path charts to exploration results table (elastic#73561)
  Bump eventemitter3 from 4.0.0 to 4.0.7 (elastic#77016)
  [Ingest Pipelines] Add descriptions for ingest processors K-S (elastic#76981)
  ...
gmmorris added a commit to gmmorris/kibana that referenced this pull request Sep 10, 2020
* master: (41 commits)
  [bugfix] Replace panel flyout opens 2 flyouts (elastic#76931)
  clean up test (elastic#76887)
  [Enterprise Search] Update shared API request handler (elastic#77112)
  [Maps] convert ESAggSource to TS (elastic#76999)
  Add plugin status API - take 2 (elastic#76732)
  Adds lens as a readable saved object for read-only dashboard users (elastic#77067)
  Skip checking for the reserved realm (elastic#76687)
  Reporting/diagnostics (elastic#74314)
  Reporting/Test: unskip non-screenshot tests (elastic#77088)
  Move metrics to setup and add cgroup metrics (elastic#76730)
  [Enterprise Search] Add Overview landing page/plugin (elastic#76734)
  First pass. Change TS type. Update OpenAPI (elastic#76434)
  [CI] Balance xpack ci groups a bit (elastic#77068)
  [Security_solution][Detections] Refactor signal ancestry to allow multiple parents (elastic#76531)
  [Maps] convert MetricsEditor to TS (elastic#76727)
  IndexMigrator: fix non blocking migration wrapper promise rejection (elastic#77018)
  [Enterprise Search] Update config data endpoint to v2 (elastic#76970)
  [ML] Add decision path charts to exploration results table (elastic#73561)
  Bump eventemitter3 from 4.0.0 to 4.0.7 (elastic#77016)
  [Ingest Pipelines] Add descriptions for ingest processors K-S (elastic#76981)
  ...
jloleysens added a commit to jloleysens/kibana that referenced this pull request Sep 10, 2020
…rok/new-patterns-component-use-array

* 'master' of github.com:elastic/kibana: (39 commits)
  [APM] Always load esarchives from common (elastic#77139)
  [Ingest Manager] Handle Legacy ES client errors (elastic#76865)
  [Docs] URL Drilldown (elastic#76529)
  [bugfix] Replace panel flyout opens 2 flyouts (elastic#76931)
  clean up test (elastic#76887)
  [Enterprise Search] Update shared API request handler (elastic#77112)
  [Maps] convert ESAggSource to TS (elastic#76999)
  Add plugin status API - take 2 (elastic#76732)
  Adds lens as a readable saved object for read-only dashboard users (elastic#77067)
  Skip checking for the reserved realm (elastic#76687)
  Reporting/diagnostics (elastic#74314)
  Reporting/Test: unskip non-screenshot tests (elastic#77088)
  Move metrics to setup and add cgroup metrics (elastic#76730)
  [Enterprise Search] Add Overview landing page/plugin (elastic#76734)
  First pass. Change TS type. Update OpenAPI (elastic#76434)
  [CI] Balance xpack ci groups a bit (elastic#77068)
  [Security_solution][Detections] Refactor signal ancestry to allow multiple parents (elastic#76531)
  [Maps] convert MetricsEditor to TS (elastic#76727)
  IndexMigrator: fix non blocking migration wrapper promise rejection (elastic#77018)
  [Enterprise Search] Update config data endpoint to v2 (elastic#76970)
  ...

# Conflicts:
#	src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.ts
rylnd added a commit to rylnd/kibana that referenced this pull request Sep 10, 2020
* master: (38 commits)
  Reporting/Test: unskip non-screenshot tests (elastic#77088)
  Move metrics to setup and add cgroup metrics (elastic#76730)
  [Enterprise Search] Add Overview landing page/plugin (elastic#76734)
  First pass. Change TS type. Update OpenAPI (elastic#76434)
  [CI] Balance xpack ci groups a bit (elastic#77068)
  [Security_solution][Detections] Refactor signal ancestry to allow multiple parents (elastic#76531)
  [Maps] convert MetricsEditor to TS (elastic#76727)
  IndexMigrator: fix non blocking migration wrapper promise rejection (elastic#77018)
  [Enterprise Search] Update config data endpoint to v2 (elastic#76970)
  [ML] Add decision path charts to exploration results table (elastic#73561)
  Bump eventemitter3 from 4.0.0 to 4.0.7 (elastic#77016)
  [Ingest Pipelines] Add descriptions for ingest processors K-S (elastic#76981)
  [Metrics UI] Replace Snapshot API with Metrics API (elastic#76253)
  legacy utils cleanup (elastic#76608)
  [ML] Account for "properties" layer in find_file_structure mappings (elastic#77035)
  fixed typo
  Upgrade to Kea 2.2 (elastic#77047)
  a11y tests on spaces home page including feature control  (elastic#76515)
  [ML] Transforms list: persist pagination through refresh interval (elastic#76786)
  [ML] Replace all use of date_histogram interval with fixed_interval (elastic#76876)
  ...
@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create backports run node scripts/backport --pr 76531 or prevent reminders by adding the backport:skip label.

@kibanamachine kibanamachine added the backport missing Added to PRs automatically when the are determined to be missing a backport. label Sep 11, 2020
marshallmain added a commit to marshallmain/kibana that referenced this pull request Sep 14, 2020
…tiple parents (elastic#76531)

* Refactors signal ancestry to allow multiple parents

* Fix depth calculation for 7.10+ signals on pre-7.10 signals

* Comment build_signal functions

* Rename buildAncestorsSignal to buildAncestors

* Update detection engine depth test scripts and docs

* Update halting test readme

* Match up rule ids in readme

* Continue populating signal.parent along with signal.parents

* pr comments

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
marshallmain added a commit that referenced this pull request Sep 14, 2020
…tiple parents (#76531) (#77346)

* Refactors signal ancestry to allow multiple parents

* Fix depth calculation for 7.10+ signals on pre-7.10 signals

* Comment build_signal functions

* Rename buildAncestorsSignal to buildAncestors

* Update detection engine depth test scripts and docs

* Update halting test readme

* Match up rule ids in readme

* Continue populating signal.parent along with signal.parents

* pr comments

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
@kibanamachine kibanamachine removed the backport missing Added to PRs automatically when the are determined to be missing a backport. label Sep 14, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature:Detection Rules Security Solution rules and Detection Engine release_note:fix Team:Endpoint Response Endpoint Response Team v7.10.0 v8.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants