[Security Solution] Replace PrebuiltRuleAsset schema construction with Zod transform (#188092)

## Summary

Pending work from: https://github.com/elastic/kibana/pull/186615

- The previous implementation to create `PrebuiltRuleAsset` with some
RuleResponse fields ommited from it had the disadvantage of being built
with a discriminated union where all rule types had to be re-listed. If
a new type was added, then it would have required manually adding the
type to that union as well, which would have been surely forgotten.
- This replaces that schema construction to use a Zod transform which
simply eliminates the omitted fields using a Zod transform.


### For maintainers

- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
Juan Pablo Djeredjian 2024-07-12 15:24:16 +02:00 committed by GitHub
parent a120c510b9
commit ccfdd69223
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 39 additions and 34 deletions

View file

@ -36,7 +36,7 @@ describe('Prebuilt rule asset schema', () => {
// The PrebuiltRuleAsset schema is built out of the rule schema,
// but the following fields are manually omitted.
// See: detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.ts
const omittedFields = [
const omittedBaseFields = [
'actions',
'throttle',
'meta',
@ -47,10 +47,24 @@ describe('Prebuilt rule asset schema', () => {
'outcome',
];
test.each(omittedFields)('ignores %s since it`s an omitted field', (field) => {
test.each(omittedBaseFields)(
'ignores the base %s field since it`s an omitted field',
(field) => {
const payload: Partial<PrebuiltRuleAsset> & Record<string, unknown> = {
...getPrebuiltRuleMock(),
[field]: 'some value',
};
const result = PrebuiltRuleAsset.safeParse(payload);
expectParseSuccess(result);
expect(result.data).toEqual(getPrebuiltRuleMock());
}
);
test('ignores the type specific response_actions field since it`s an omitted field', () => {
const payload: Partial<PrebuiltRuleAsset> & Record<string, unknown> = {
...getPrebuiltRuleMock(),
[field]: 'some value',
response_actions: [{ action_type_id: `.osquery`, params: {} }],
};
const result = PrebuiltRuleAsset.safeParse(payload);

View file

@ -10,14 +10,7 @@ import {
RuleSignatureId,
RuleVersion,
BaseCreateProps,
EqlRuleCreateFields,
EsqlRuleCreateFields,
MachineLearningRuleCreateFields,
NewTermsRuleCreateFields,
QueryRuleCreateFields,
SavedQueryRuleCreateFields,
ThreatMatchRuleCreateFields,
ThresholdRuleCreateFields,
TypeSpecificCreateProps,
} from '../../../../../../common/api/detection_engine/model/rule_schema';
/**
@ -37,28 +30,26 @@ const BASE_PROPS_REMOVED_FROM_PREBUILT_RULE_ASSET = zodMaskFor<BaseCreateProps>(
'outcome',
]);
// `response_actions` is only part of the optional fields in QueryRuleCreateFields and SavedQueryRuleCreateFields
const TYPE_SPECIFIC_PROPS_REMOVED_FROM_PREBUILT_RULE_ASSET = zodMaskFor<
QueryRuleCreateFields | SavedQueryRuleCreateFields
>()(['response_actions']);
const QueryRuleAssetFields = QueryRuleCreateFields.omit(
TYPE_SPECIFIC_PROPS_REMOVED_FROM_PREBUILT_RULE_ASSET
);
const SavedQueryRuleAssetFields = SavedQueryRuleCreateFields.omit(
TYPE_SPECIFIC_PROPS_REMOVED_FROM_PREBUILT_RULE_ASSET
);
export const RuleAssetTypeSpecificCreateProps = z.discriminatedUnion('type', [
EqlRuleCreateFields,
QueryRuleAssetFields,
SavedQueryRuleAssetFields,
ThresholdRuleCreateFields,
ThreatMatchRuleCreateFields,
MachineLearningRuleCreateFields,
NewTermsRuleCreateFields,
EsqlRuleCreateFields,
]);
/**
* Aditionally remove fields which are part only of the optional fields in the rule types that make up
* the TypeSpecificCreateProps discriminatedUnion, by using a Zod transformation which extracts out the
* necessary fields in the rules types where they exist. Fields to extract:
* - response_actions: from Query and SavedQuery rules
*/
const TypeSpecificFields = TypeSpecificCreateProps.transform((val) => {
switch (val.type) {
case 'query': {
const { response_actions: _, ...rest } = val;
return rest;
}
case 'saved_query': {
const { response_actions: _, ...rest } = val;
return rest;
}
default:
return val;
}
});
function zodMaskFor<T>() {
return function <U extends keyof T>(props: U[]): Record<U, true> {
@ -85,7 +76,7 @@ function zodMaskFor<T>() {
*/
export type PrebuiltRuleAsset = z.infer<typeof PrebuiltRuleAsset>;
export const PrebuiltRuleAsset = BaseCreateProps.omit(BASE_PROPS_REMOVED_FROM_PREBUILT_RULE_ASSET)
.and(RuleAssetTypeSpecificCreateProps)
.and(TypeSpecificFields)
.and(
z.object({
rule_id: RuleSignatureId,