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] Create Map of upgradable rule fields by type #190128

Merged
merged 33 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const {{@key}}: z.ZodType<{{@key}}, ZodTypeDef, {{@key}}Input> = {{> zod_
{{#if (shouldCastExplicitly this)}}
{{!-- We need this temporary type to infer from it below, but in the end we want to export as a casted {{@key}} type --}}
{{!-- error TS7056: The inferred type of this node exceeds the maximum length the compiler will serialize. An explicit type annotation is needed. --}}
const {{@key}}Internal = {{> zod_schema_item}};
export const {{@key}}Internal = {{> zod_schema_item}};
Copy link
Contributor Author

@jpdjere jpdjere Sep 11, 2024

Choose a reason for hiding this comment

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

@maximpn @tomsonpl

I'm introducing this change as a follow-up to #191830.

There's some use cases where the Zod type needs to be used before being casted as z.ZodType<{{@key}}>: in the case of this PR, once a Zod discriminated union is typed as ZodType, it looses some of its useful properties such as options.

I'm exporting these Internal schemas as well where they need to be used externally. Any objections?


export type {{@key}} = z.infer<typeof {{@key}}Internal>;
export const {{@key}} = {{@key}}Internal as z.ZodType<{{@key}}>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const ExceptionListItemEntryExists = z.object({
operator: ExceptionListItemEntryOperator,
});

const ExceptionListItemEntryNestedEntryItemInternal = z.union([
export const ExceptionListItemEntryNestedEntryItemInternal = z.union([
ExceptionListItemEntryMatch,
ExceptionListItemEntryMatchAny,
ExceptionListItemEntryExists,
Expand Down Expand Up @@ -89,7 +89,7 @@ export const ExceptionListItemEntryMatchWildcard = z.object({
operator: ExceptionListItemEntryOperator,
});

const ExceptionListItemEntryInternal = z.discriminatedUnion('type', [
export const ExceptionListItemEntryInternal = z.discriminatedUnion('type', [
ExceptionListItemEntryMatch,
ExceptionListItemEntryMatchAny,
ExceptionListItemEntryList,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ export const EsqlRuleUpdateProps = SharedUpdateProps.merge(EsqlRuleCreateFields)
export type EsqlRulePatchProps = z.infer<typeof EsqlRulePatchProps>;
export const EsqlRulePatchProps = SharedPatchProps.merge(EsqlRulePatchFields.partial());

const TypeSpecificCreatePropsInternal = z.discriminatedUnion('type', [
export const TypeSpecificCreatePropsInternal = z.discriminatedUnion('type', [
EqlRuleCreateFields,
QueryRuleCreateFields,
SavedQueryRuleCreateFields,
Expand All @@ -612,7 +612,7 @@ export type TypeSpecificCreateProps = z.infer<typeof TypeSpecificCreatePropsInte
export const TypeSpecificCreateProps =
TypeSpecificCreatePropsInternal as z.ZodType<TypeSpecificCreateProps>;

const TypeSpecificPatchPropsInternal = z.union([
export const TypeSpecificPatchPropsInternal = z.union([
EqlRulePatchFields,
QueryRulePatchFields,
SavedQueryRulePatchFields,
Expand All @@ -627,7 +627,7 @@ export type TypeSpecificPatchProps = z.infer<typeof TypeSpecificPatchPropsIntern
export const TypeSpecificPatchProps =
TypeSpecificPatchPropsInternal as z.ZodType<TypeSpecificPatchProps>;

const TypeSpecificResponseInternal = z.discriminatedUnion('type', [
export const TypeSpecificResponseInternal = z.discriminatedUnion('type', [
EqlRuleResponseFields,
QueryRuleResponseFields,
SavedQueryRuleResponseFields,
Expand All @@ -641,7 +641,7 @@ const TypeSpecificResponseInternal = z.discriminatedUnion('type', [
export type TypeSpecificResponse = z.infer<typeof TypeSpecificResponseInternal>;
export const TypeSpecificResponse = TypeSpecificResponseInternal as z.ZodType<TypeSpecificResponse>;

const RuleCreatePropsInternal = z.discriminatedUnion('type', [
export const RuleCreatePropsInternal = z.discriminatedUnion('type', [
EqlRuleCreateProps,
QueryRuleCreateProps,
SavedQueryRuleCreateProps,
Expand All @@ -655,7 +655,7 @@ const RuleCreatePropsInternal = z.discriminatedUnion('type', [
export type RuleCreateProps = z.infer<typeof RuleCreatePropsInternal>;
export const RuleCreateProps = RuleCreatePropsInternal as z.ZodType<RuleCreateProps>;

const RuleUpdatePropsInternal = z.discriminatedUnion('type', [
export const RuleUpdatePropsInternal = z.discriminatedUnion('type', [
EqlRuleUpdateProps,
QueryRuleUpdateProps,
SavedQueryRuleUpdateProps,
Expand All @@ -669,7 +669,7 @@ const RuleUpdatePropsInternal = z.discriminatedUnion('type', [
export type RuleUpdateProps = z.infer<typeof RuleUpdatePropsInternal>;
export const RuleUpdateProps = RuleUpdatePropsInternal as z.ZodType<RuleUpdateProps>;

const RulePatchPropsInternal = z.union([
export const RulePatchPropsInternal = z.union([
EqlRulePatchProps,
QueryRulePatchProps,
SavedQueryRulePatchProps,
Expand All @@ -683,7 +683,7 @@ const RulePatchPropsInternal = z.union([
export type RulePatchProps = z.infer<typeof RulePatchPropsInternal>;
export const RulePatchProps = RulePatchPropsInternal as z.ZodType<RulePatchProps>;

const RuleResponseInternal = z.discriminatedUnion('type', [
export const RuleResponseInternal = z.discriminatedUnion('type', [
EqlRule,
QueryRule,
SavedQueryRule,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { DiffableFieldsByTypeUnion, DiffableAllFields, DiffableRuleTypes } from './diffable_rule';

describe('Diffable rule schema', () => {
describe('DiffableAllFields', () => {
it('includes all possible rules types listed in the diffable rule schemas', () => {
const diffableAllFieldsRuleTypes = DiffableAllFields.shape.type.options.map((x) => x.value);
const diffableRuleTypes = DiffableRuleTypes.options.map((x) => x.value);
expect(diffableAllFieldsRuleTypes).toStrictEqual(diffableRuleTypes);
});
});

describe('DiffableRule', () => {
it('includes all possible rules types listed in the diffable rule schemas', () => {
const diffableRuleTypes = DiffableFieldsByTypeUnion.options.map((x) => x.shape.type.value);
const ruleTypes = DiffableRuleTypes.options.map((x) => x.value);
expect(diffableRuleTypes).toStrictEqual(ruleTypes);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
*/

import { z } from '@kbn/zod';

import {
AlertSuppression,
AnomalyThreshold,
EventCategoryOverride,
HistoryWindowStart,
InvestigationFields,
InvestigationGuide,
KqlQueryLanguage,
MachineLearningJobId,
MaxSignals,
NewTermsFields,
Expand All @@ -37,6 +39,7 @@ import {
ThreatIndicatorPath,
ThreatMapping,
Threshold,
ThresholdAlertSuppression,
TiebreakerField,
TimestampField,
} from '../../../../model/rule_schema';
Expand Down Expand Up @@ -88,6 +91,7 @@ export const DiffableCommonFields = z.object({
max_signals: MaxSignals,

// Optional fields
investigation_fields: InvestigationFields.optional(),
rule_name_override: RuleNameOverrideObject.optional(), // NOTE: new field
timestamp_override: TimestampOverrideObject.optional(), // NOTE: new field
timeline_template: TimelineTemplateReference.optional(), // NOTE: new field
Expand All @@ -99,13 +103,15 @@ export const DiffableCustomQueryFields = z.object({
type: z.literal('query'),
kql_query: RuleKqlQuery, // NOTE: new field
data_source: RuleDataSource.optional(), // NOTE: new field
alert_suppression: AlertSuppression.optional(),
});

export type DiffableSavedQueryFields = z.infer<typeof DiffableSavedQueryFields>;
export const DiffableSavedQueryFields = z.object({
type: z.literal('saved_query'),
kql_query: RuleKqlQuery, // NOTE: new field
data_source: RuleDataSource.optional(), // NOTE: new field
alert_suppression: AlertSuppression.optional(),
});

export type DiffableEqlFields = z.infer<typeof DiffableEqlFields>;
Expand All @@ -116,6 +122,7 @@ export const DiffableEqlFields = z.object({
event_category_override: EventCategoryOverride.optional(),
timestamp_field: TimestampField.optional(),
tiebreaker_field: TiebreakerField.optional(),
alert_suppression: AlertSuppression.optional(),
});

// this is a new type of rule, no prebuilt rules created yet.
Expand All @@ -124,6 +131,7 @@ export type DiffableEsqlFields = z.infer<typeof DiffableEsqlFields>;
export const DiffableEsqlFields = z.object({
type: z.literal('esql'),
esql_query: RuleEsqlQuery, // NOTE: new field
alert_suppression: AlertSuppression.optional(),
});

export type DiffableThreatMatchFields = z.infer<typeof DiffableThreatMatchFields>;
Expand All @@ -135,6 +143,8 @@ export const DiffableThreatMatchFields = z.object({
threat_mapping: ThreatMapping,
data_source: RuleDataSource.optional(), // NOTE: new field
threat_indicator_path: ThreatIndicatorPath.optional(),
threat_language: KqlQueryLanguage.optional(),
alert_suppression: AlertSuppression.optional(),
});

export type DiffableThresholdFields = z.infer<typeof DiffableThresholdFields>;
Expand All @@ -143,13 +153,15 @@ export const DiffableThresholdFields = z.object({
kql_query: RuleKqlQuery, // NOTE: new field
threshold: Threshold,
data_source: RuleDataSource.optional(), // NOTE: new field
alert_suppression: ThresholdAlertSuppression.optional(),
});

export type DiffableMachineLearningFields = z.infer<typeof DiffableMachineLearningFields>;
export const DiffableMachineLearningFields = z.object({
type: z.literal('machine_learning'),
machine_learning_job_id: MachineLearningJobId,
anomaly_threshold: AnomalyThreshold,
alert_suppression: AlertSuppression.optional(),
});

export type DiffableNewTermsFields = z.infer<typeof DiffableNewTermsFields>;
Expand All @@ -159,6 +171,7 @@ export const DiffableNewTermsFields = z.object({
new_terms_fields: NewTermsFields,
history_window_start: HistoryWindowStart,
data_source: RuleDataSource.optional(), // NOTE: new field
alert_suppression: AlertSuppression.optional(),
});

/**
Expand Down Expand Up @@ -188,36 +201,48 @@ export const DiffableNewTermsFields = z.object({
* top-level fields.
*/

export const DiffableFieldsByTypeUnion = z.discriminatedUnion('type', [
DiffableCustomQueryFields,
DiffableSavedQueryFields,
DiffableEqlFields,
DiffableEsqlFields,
DiffableThreatMatchFields,
DiffableThresholdFields,
DiffableMachineLearningFields,
DiffableNewTermsFields,
]);

export type DiffableRule = z.infer<typeof DiffableRule>;
const DiffableRule = z.intersection(
DiffableCommonFields,
z.discriminatedUnion('type', [
DiffableCustomQueryFields,
DiffableSavedQueryFields,
DiffableEqlFields,
DiffableEsqlFields,
DiffableThreatMatchFields,
DiffableThresholdFields,
DiffableMachineLearningFields,
DiffableNewTermsFields,
])
);
export const DiffableRule = z.intersection(DiffableCommonFields, DiffableFieldsByTypeUnion);

/**
* Union of all possible rule types
*/
export type DiffableRuleTypes = z.infer<typeof DiffableRuleTypes>;
export const DiffableRuleTypes = z.union([
DiffableCustomQueryFields.shape.type,
DiffableSavedQueryFields.shape.type,
DiffableEqlFields.shape.type,
DiffableEsqlFields.shape.type,
DiffableThreatMatchFields.shape.type,
DiffableThresholdFields.shape.type,
DiffableMachineLearningFields.shape.type,
DiffableNewTermsFields.shape.type,
]);

/**
* This is a merge of all fields from all rule types into a single TS type.
* This is NOT a union discriminated by rule type, as DiffableRule is.
*/
export type DiffableAllFields = DiffableCommonFields &
Omit<DiffableCustomQueryFields, 'type'> &
Omit<DiffableSavedQueryFields, 'type'> &
Omit<DiffableEqlFields, 'type'> &
Omit<DiffableEsqlFields, 'type'> &
Omit<DiffableThreatMatchFields, 'type'> &
Omit<DiffableThresholdFields, 'type'> &
Omit<DiffableMachineLearningFields, 'type'> &
Omit<DiffableNewTermsFields, 'type'> &
DiffableRuleTypeField;

interface DiffableRuleTypeField {
type: DiffableRule['type'];
}
export type DiffableAllFields = z.infer<typeof DiffableAllFields>;
export const DiffableAllFields = DiffableCommonFields.merge(
DiffableCustomQueryFields.omit({ type: true })
)
.merge(DiffableSavedQueryFields.omit({ type: true }))
.merge(DiffableEqlFields.omit({ type: true }))
.merge(DiffableEsqlFields.omit({ type: true }))
.merge(DiffableThreatMatchFields.omit({ type: true }))
.merge(DiffableThresholdFields.omit({ type: true }))
.merge(DiffableMachineLearningFields.omit({ type: true }))
.merge(DiffableNewTermsFields.omit({ type: true }))
.merge(z.object({ type: DiffableRuleTypes }));
Loading