-
Notifications
You must be signed in to change notification settings - Fork 8.2k
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
[Security Solution] Converge detection engine on single schema representation #96186
Conversation
a72bff4
to
184e0dc
Compare
x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts
Show resolved
Hide resolved
@@ -159,12 +159,11 @@ const commonParams = { | |||
tags, | |||
interval, | |||
enabled, | |||
throttle: throttleOrNull, | |||
throttle, |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fun 😂
Threshold work looks good 👍 |
@elasticmachine merge upstream |
? params.exceptions_list | ||
: params.lists != null | ||
? params.lists | ||
: [], |
There was a problem hiding this comment.
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?
There was a problem hiding this 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`; |
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😎
x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts
Outdated
Show resolved
Hide resolved
x-pack/plugins/security_solution/server/lib/detection_engine/notifications/types.test.ts
Outdated
Show resolved
Hide resolved
}: SingleBulkCreateParams): Promise<SingleBulkCreateResponse> => { | ||
const ruleParams = ruleSO.attributes.params; |
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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>> => { |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts
Outdated
Show resolved
Hide resolved
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', () => { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
@elasticmachine merge upstream |
💚 Build Succeeded
Metrics [docs]Page load bundle
History
To update your PR or re-run it, just comment with: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
…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>
💚 Backport successful
This backport PR will be merged automatically after passing CI. |
…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>
Summary
In this PR we:
null
values toundefined
and also normalizethreshold.field
in the SO so we don't have to normalize it at runtimesignalParamsSchema
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 typesRuleTypeParams
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)RuleTypeParams
and fields from the rule SO were converted to take the rule SO as an entire object instead (see e.g.buildBulkBody
)rule_schemas.mock.ts
and started reducing mock data duplicationFuture 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