[SIEM][Detections Engine] Add note markdown field to backend (#59796) (#59947)

* add new note markdown field to DE backend

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Yara Tercero 2020-03-11 18:51:47 -04:00 committed by GitHub
parent b438a530ac
commit 25df364e05
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 612 additions and 2 deletions

View file

@ -59,6 +59,7 @@ export const mockPrepackagedRule = (): PrepackagedRules => ({
version: 1,
false_positives: [],
max_signals: 100,
note: '',
timeline_id: 'timeline-id',
timeline_title: 'timeline-title',
});
@ -392,6 +393,7 @@ export const getResult = (): RuleAlertType => ({
},
],
references: ['http://www.example.com', 'https://ww.example.com'],
note: '# Investigative notes',
version: 1,
},
createdAt: new Date('2019-12-13T16:40:33.400Z'),

View file

@ -133,6 +133,9 @@
}
}
},
"note": {
"type": "text"
},
"type": {
"type": "keyword"
},

View file

@ -78,6 +78,7 @@ export const createRulesBulkRoute = (router: IRouter) => {
to,
type,
references,
note,
timeline_id: timelineId,
timeline_title: timelineTitle,
version,
@ -131,6 +132,7 @@ export const createRulesBulkRoute = (router: IRouter) => {
type,
threat,
references,
note,
version,
});
return transformValidateBulkError(ruleIdOrUuid, createdRule);

View file

@ -55,6 +55,7 @@ export const createRulesRoute = (router: IRouter): void => {
to,
type,
references,
note,
} = request.body;
const siemResponse = buildSiemResponse(response);
@ -117,6 +118,7 @@ export const createRulesRoute = (router: IRouter): void => {
type,
threat,
references,
note,
version: 1,
});
const ruleStatuses = await savedObjectsClient.find<

View file

@ -134,6 +134,7 @@ export const importRulesRoute = (router: IRouter, config: LegacyServices['config
to,
type,
references,
note,
timeline_id: timelineId,
timeline_title: timelineTitle,
version,
@ -183,6 +184,7 @@ export const importRulesRoute = (router: IRouter, config: LegacyServices['config
type,
threat,
references,
note,
version,
});
resolve({ rule_id: ruleId, status_code: 200 });
@ -217,6 +219,7 @@ export const importRulesRoute = (router: IRouter, config: LegacyServices['config
type,
threat,
references,
note,
version,
});
resolve({ rule_id: ruleId, status_code: 200 });

View file

@ -71,6 +71,7 @@ export const patchRulesBulkRoute = (router: IRouter) => {
type,
threat,
references,
note,
version,
} = payloadRule;
const idOrRuleIdOrUnknown = id ?? ruleId ?? '(unknown id)';
@ -104,6 +105,7 @@ export const patchRulesBulkRoute = (router: IRouter) => {
type,
threat,
references,
note,
version,
});
if (rule != null) {

View file

@ -55,6 +55,7 @@ export const patchRulesRoute = (router: IRouter) => {
type,
threat,
references,
note,
version,
} = request.body;
const siemResponse = buildSiemResponse(response);
@ -101,6 +102,7 @@ export const patchRulesRoute = (router: IRouter) => {
type,
threat,
references,
note,
version,
});
if (rule != null) {

View file

@ -72,6 +72,7 @@ export const updateRulesBulkRoute = (router: IRouter) => {
type,
threat,
references,
note,
version,
} = payloadRule;
const finalIndex = outputIndex ?? siemClient.signalsIndex;
@ -107,6 +108,7 @@ export const updateRulesBulkRoute = (router: IRouter) => {
type,
threat,
references,
note,
version,
});
if (rule != null) {

View file

@ -55,6 +55,7 @@ export const updateRulesRoute = (router: IRouter) => {
type,
threat,
references,
note,
version,
} = request.body;
const siemResponse = buildSiemResponse(response);
@ -103,6 +104,7 @@ export const updateRulesRoute = (router: IRouter) => {
type,
threat,
references,
note,
version,
});
if (rule != null) {

View file

@ -92,6 +92,7 @@ describe('utils', () => {
timeline_title: 'some-timeline-title',
to: 'now',
type: 'query',
note: '# Investigative notes',
version: 1,
};
expect(rule).toEqual(expected);
@ -154,6 +155,7 @@ describe('utils', () => {
timeline_title: 'some-timeline-title',
to: 'now',
type: 'query',
note: '# Investigative notes',
version: 1,
};
expect(omitData).toEqual(expected);
@ -218,6 +220,7 @@ describe('utils', () => {
timeline_title: 'some-timeline-title',
to: 'now',
type: 'query',
note: '# Investigative notes',
version: 1,
};
expect(rule).toEqual(expected);
@ -282,6 +285,7 @@ describe('utils', () => {
timeline_title: 'some-timeline-title',
to: 'now',
type: 'query',
note: '# Investigative notes',
version: 1,
};
expect(rule).toEqual(expected);
@ -344,6 +348,7 @@ describe('utils', () => {
timeline_title: 'some-timeline-title',
to: 'now',
type: 'query',
note: '# Investigative notes',
version: 1,
};
expect(omitData).toEqual(expected);
@ -409,6 +414,7 @@ describe('utils', () => {
timeline_title: 'some-timeline-title',
to: 'now',
type: 'query',
note: '# Investigative notes',
version: 1,
};
expect(ruleWithEnabledFalse).toEqual(expected);
@ -474,6 +480,7 @@ describe('utils', () => {
timeline_title: 'some-timeline-title',
to: 'now',
type: 'query',
note: '# Investigative notes',
version: 1,
};
expect(ruleWithEnabledFalse).toEqual(expected);
@ -539,6 +546,7 @@ describe('utils', () => {
timeline_title: 'some-timeline-title',
to: 'now',
type: 'query',
note: '# Investigative notes',
version: 1,
};
expect(rule).toEqual(expected);
@ -688,6 +696,7 @@ describe('utils', () => {
},
timeline_id: 'some-timeline-id',
timeline_title: 'some-timeline-title',
note: '# Investigative notes',
version: 1,
};
expect(output).toEqual({
@ -769,6 +778,7 @@ describe('utils', () => {
},
timeline_id: 'some-timeline-id',
timeline_title: 'some-timeline-title',
note: '# Investigative notes',
version: 1,
};
expect(output).toEqual(expected);
@ -941,6 +951,7 @@ describe('utils', () => {
},
timeline_id: 'some-timeline-id',
timeline_title: 'some-timeline-title',
note: '# Investigative notes',
version: 1,
};
expect(output).toEqual(expected);
@ -1053,6 +1064,7 @@ describe('utils', () => {
type: 'query',
updated_at: '2019-12-13T16:40:33.400Z',
updated_by: 'elastic',
note: '# Investigative notes',
version: 1,
},
]);
@ -1112,6 +1124,7 @@ describe('utils', () => {
type: 'query',
updated_at: '2019-12-13T16:40:33.400Z',
updated_by: 'elastic',
note: '# Investigative notes',
version: 1,
},
{
@ -1160,6 +1173,7 @@ describe('utils', () => {
type: 'query',
updated_at: '2019-12-13T16:40:33.400Z',
updated_by: 'elastic',
note: '# Investigative notes',
version: 1,
},
]);

View file

@ -131,6 +131,7 @@ export const transformAlertToRule = (
to: alert.params.to,
type: alert.params.type,
threat: alert.params.threat,
note: alert.params.note,
version: alert.params.version,
status: ruleStatus?.attributes.status,
status_date: ruleStatus?.attributes.statusDate,

View file

@ -72,6 +72,7 @@ export const ruleOutput: RulesSchema = {
meta: {
someMeta: 'someField',
},
note: '# Investigative notes',
timeline_title: 'some-timeline-title',
timeline_id: 'some-timeline-id',
};

View file

@ -1274,4 +1274,62 @@ describe('add prepackaged rules schema', () => {
'child "severity" fails because ["severity" must be one of [low, medium, high, critical]]'
);
});
describe('note', () => {
test('You can set note to any string you want', () => {
expect(
addPrepackagedRulesSchema.validate<Partial<PrepackagedRules>>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
from: 'now-5m',
to: 'now',
index: ['index-1'],
name: 'some-name',
severity: 'low',
interval: '5m',
type: 'query',
references: ['index-1'],
query: 'some query',
language: 'kuery',
max_signals: 1,
meta: {
somethingMadeUp: { somethingElse: true },
},
note: '# test header',
version: 1,
}).error
).toBeFalsy();
});
test('You cannot create note as anything other than a string', () => {
expect(
addPrepackagedRulesSchema.validate<
Partial<Omit<PrepackagedRules, 'note'> & { note: object }>
>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
from: 'now-5m',
to: 'now',
index: ['index-1'],
name: 'some-name',
severity: 'low',
interval: '5m',
type: 'query',
references: ['index-1'],
query: 'some query',
language: 'kuery',
max_signals: 1,
meta: {
somethingMadeUp: { somethingElse: true },
},
note: {
somethingMadeUp: { somethingElse: true },
},
version: 1,
}).error.message
).toEqual('child "note" fails because ["note" must be a string]');
});
});
});

View file

@ -32,6 +32,7 @@ import {
type,
threat,
references,
note,
version,
} from './schemas';
/* eslint-enable @typescript-eslint/camelcase */
@ -79,5 +80,6 @@ export const addPrepackagedRulesSchema = Joi.object({
type: type.required(),
threat: threat.default([]),
references: references.default([]),
note: note.allow(''),
version: version.required(),
});

View file

@ -141,4 +141,71 @@ describe('create_rules_bulk_schema', () => {
'"value" at position 0 fails because [child "severity" fails because ["severity" must be one of [low, medium, high, critical]]]'
);
});
test('You can set "note" to a string', () => {
expect(
createRulesBulkSchema.validate<Partial<PatchRuleAlertParamsRest>>([
{
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
name: 'some-name',
severity: 'low',
type: 'query',
references: ['index-1'],
query: 'some query',
language: 'kuery',
max_signals: 1,
note: '# test markdown',
version: 1,
},
]).error
).toBeFalsy();
});
test('You can set "note" to an empty string', () => {
expect(
createRulesBulkSchema.validate<Partial<PatchRuleAlertParamsRest>>([
{
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
name: 'some-name',
severity: 'low',
type: 'query',
references: ['index-1'],
query: 'some query',
language: 'kuery',
max_signals: 1,
note: '',
version: 1,
},
]).error
).toBeFalsy();
});
test('You cannot set "note" to anything other than string', () => {
expect(
createRulesBulkSchema.validate<Partial<PatchRuleAlertParamsRest>>([
{
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
name: 'some-name',
severity: 'low',
type: 'query',
references: ['index-1'],
query: 'some query',
language: 'kuery',
max_signals: 1,
note: {
something: 'some object',
},
version: 1,
},
]).error.message
).toEqual(
'"value" at position 0 fails because [child "note" fails because ["note" must be a string]]'
);
});
});

View file

@ -1224,4 +1224,95 @@ describe('create rules schema', () => {
'child "severity" fails because ["severity" must be one of [low, medium, high, critical]]'
);
});
describe('note', () => {
test('You can set note to a string', () => {
expect(
createRulesSchema.validate<Partial<RuleAlertParamsRest>>({
rule_id: 'rule-1',
output_index: '.siem-signals',
risk_score: 50,
description: 'some description',
from: 'now-5m',
to: 'now',
index: ['index-1'],
name: 'some-name',
severity: 'low',
interval: '5m',
type: 'query',
references: ['index-1'],
query: 'some query',
language: 'kuery',
max_signals: 1,
note: '# documentation markdown here',
}).error
).toBeFalsy();
});
test('You can set note to an emtpy string', () => {
expect(
createRulesSchema.validate<Partial<RuleAlertParamsRest>>({
rule_id: 'rule-1',
output_index: '.siem-signals',
risk_score: 50,
description: 'some description',
from: 'now-5m',
to: 'now',
index: ['index-1'],
name: 'some-name',
severity: 'low',
interval: '5m',
type: 'query',
references: ['index-1'],
query: 'some query',
language: 'kuery',
max_signals: 1,
note: '',
}).error
).toBeFalsy();
});
test('You cannot create note as an object', () => {
expect(
createRulesSchema.validate<Partial<Omit<RuleAlertParamsRest, 'note'> & { note: object }>>({
rule_id: 'rule-1',
output_index: '.siem-signals',
risk_score: 50,
description: 'some description',
from: 'now-5m',
to: 'now',
index: ['index-1'],
name: 'some-name',
severity: 'low',
interval: '5m',
type: 'query',
references: ['index-1'],
query: 'some query',
language: 'kuery',
max_signals: 1,
note: {
somethingHere: 'something else',
},
}).error.message
).toEqual('child "note" fails because ["note" must be a string]');
});
test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, note] does validate', () => {
expect(
createRulesSchema.validate<Partial<RuleAlertParamsRest>>({
rule_id: 'rule-1',
description: 'some description',
from: 'now-5m',
to: 'now',
index: ['index-1'],
name: 'some-name',
severity: 'low',
interval: '5m',
type: 'query',
risk_score: 50,
note: '# some markdown',
}).error
).toBeFalsy();
});
});
});

View file

@ -32,6 +32,7 @@ import {
type,
threat,
references,
note,
version,
} from './schemas';
/* eslint-enable @typescript-eslint/camelcase */
@ -67,5 +68,6 @@ export const createRulesSchema = Joi.object({
type: type.required(),
threat: threat.default([]),
references: references.default([]),
note: note.allow(''),
version: version.default(1),
});

View file

@ -1423,4 +1423,116 @@ describe('import rules schema', () => {
'child "severity" fails because ["severity" must be one of [low, medium, high, critical]]'
);
});
describe('note', () => {
test('You can set note to a string', () => {
expect(
importRulesSchema.validate<Partial<ImportRuleAlertRest>>({
rule_id: 'rule-1',
output_index: '.siem-signals',
risk_score: 50,
description: 'some description',
from: 'now-5m',
to: 'now',
immutable: false,
index: ['index-1'],
name: 'some-name',
severity: 'low',
interval: '5m',
type: 'query',
references: ['index-1'],
query: 'some query',
language: 'kuery',
max_signals: 1,
meta: {
somethingMadeUp: { somethingElse: true },
},
note: '# test header',
}).error
).toBeFalsy();
});
test('You can set note to an empty string', () => {
expect(
importRulesSchema.validate<Partial<ImportRuleAlertRest>>({
rule_id: 'rule-1',
output_index: '.siem-signals',
risk_score: 50,
description: 'some description',
from: 'now-5m',
to: 'now',
immutable: false,
index: ['index-1'],
name: 'some-name',
severity: 'low',
interval: '5m',
type: 'query',
references: ['index-1'],
query: 'some query',
language: 'kuery',
max_signals: 1,
meta: {
somethingMadeUp: { somethingElse: true },
},
note: '',
}).error
).toBeFalsy();
});
test('You cannot create note set to null', () => {
expect(
importRulesSchema.validate<Partial<ImportRuleAlertRest>>({
rule_id: 'rule-1',
output_index: '.siem-signals',
risk_score: 50,
description: 'some description',
from: 'now-5m',
to: 'now',
immutable: false,
index: ['index-1'],
name: 'some-name',
severity: 'low',
interval: '5m',
type: 'query',
references: ['index-1'],
query: 'some query',
language: 'kuery',
max_signals: 1,
meta: {
somethingMadeUp: { somethingElse: true },
},
note: null,
}).error.message
).toEqual('child "note" fails because ["note" must be a string]');
});
test('You cannot create note as something other than a string', () => {
expect(
importRulesSchema.validate<Partial<Omit<ImportRuleAlertRest, 'note'> & { note: object }>>({
rule_id: 'rule-1',
output_index: '.siem-signals',
risk_score: 50,
description: 'some description',
from: 'now-5m',
to: 'now',
immutable: false,
index: ['index-1'],
name: 'some-name',
severity: 'low',
interval: '5m',
type: 'query',
references: ['index-1'],
query: 'some query',
language: 'kuery',
max_signals: 1,
meta: {
somethingMadeUp: { somethingElse: true },
},
note: {
somethingMadeUp: { somethingElse: true },
},
}).error.message
).toEqual('child "note" fails because ["note" must be a string]');
});
});
});

View file

@ -38,6 +38,7 @@ import {
type,
threat,
references,
note,
version,
} from './schemas';
/* eslint-enable @typescript-eslint/camelcase */
@ -84,6 +85,7 @@ export const importRulesSchema = Joi.object({
type: type.required(),
threat: threat.default([]),
references: references.default([]),
note: note.allow(''),
version: version.default(1),
created_at,
updated_at,

View file

@ -49,4 +49,43 @@ describe('patch_rules_bulk_schema', () => {
]).error
).toBeFalsy();
});
test('can set "note" to be a string', () => {
expect(
patchRulesBulkSchema.validate<Array<Partial<PatchRuleAlertParamsRest>>>([
{
id: 'rule-1',
note: 'hi',
},
]).error
).toBeFalsy();
});
test('can set "note" to be an empty string', () => {
expect(
patchRulesBulkSchema.validate<Array<Partial<PatchRuleAlertParamsRest>>>([
{
id: 'rule-1',
note: '',
},
]).error
).toBeFalsy();
});
test('cannot set "note" to be anything other than a string', () => {
expect(
patchRulesBulkSchema.validate<
Array<Partial<Omit<PatchRuleAlertParamsRest, 'note'> & { note: object }>>
>([
{
id: 'rule-1',
note: {
someprop: 'some value here',
},
},
]).error.message
).toEqual(
'"value" at position 0 fails because [child "note" fails because ["note" must be a string]]'
);
});
});

View file

@ -1012,4 +1012,45 @@ describe('patch rules schema', () => {
'child "severity" fails because ["severity" must be one of [low, medium, high, critical]]'
);
});
describe('note', () => {
test('[rule_id, description, from, to, index, name, severity, interval, type, note] does validate', () => {
expect(
patchRulesSchema.validate<Partial<PatchRuleAlertParamsRest>>({
rule_id: 'rule-1',
description: 'some description',
from: 'now-5m',
to: 'now',
index: ['index-1'],
name: 'some-name',
severity: 'low',
interval: '5m',
type: 'query',
note: '# some documentation markdown',
}).error
).toBeFalsy();
});
test('note can be patched', () => {
expect(
patchRulesSchema.validate<Partial<PatchRuleAlertParamsRest>>({
id: 'rule-1',
note: '# new documentation markdown',
}).error
).toBeFalsy();
});
test('You cannot patch note as an object', () => {
expect(
patchRulesSchema.validate<
Partial<Omit<PatchRuleAlertParamsRest, 'note'> & { note: object }>
>({
id: 'rule-1',
note: {
someProperty: 'something else here',
},
}).error.message
).toEqual('child "note" fails because ["note" must be a string]');
});
});
});

View file

@ -32,6 +32,7 @@ import {
type,
threat,
references,
note,
id,
version,
} from './schemas';
@ -63,5 +64,6 @@ export const patchRulesSchema = Joi.object({
type,
threat,
references,
note: note.allow(''),
version,
}).xor('id', 'rule_id');

View file

@ -48,6 +48,7 @@ import {
version,
filters,
meta,
note,
} from './schemas';
/**
@ -113,6 +114,7 @@ export const partialRulesSchema = t.partial({
filters,
meta,
index,
note,
});
/**

View file

@ -128,3 +128,4 @@ export const success_count = PositiveInteger;
export const rules_custom_installed = PositiveInteger;
export const rules_not_installed = PositiveInteger;
export const rules_not_updated = PositiveInteger;
export const note = t.string;

View file

@ -105,3 +105,4 @@ export const updated_by = Joi.string();
export const version = Joi.number()
.integer()
.min(1);
export const note = Joi.string();

View file

@ -1243,4 +1243,101 @@ describe('create rules schema', () => {
'child "severity" fails because ["severity" must be one of [low, medium, high, critical]]'
);
});
describe('note', () => {
test('You can set note to a string', () => {
expect(
updateRulesSchema.validate<Partial<RuleAlertParamsRest>>({
rule_id: 'rule-1',
output_index: '.siem-signals',
risk_score: 50,
description: 'some description',
from: 'now-5m',
to: 'now',
index: ['index-1'],
name: 'some-name',
severity: 'low',
interval: '5m',
type: 'query',
references: ['index-1'],
query: 'some query',
language: 'kuery',
max_signals: 1,
note: '# some documentation title',
}).error
).toBeFalsy();
});
test('You can set note to an empty string', () => {
expect(
updateRulesSchema.validate<Partial<RuleAlertParamsRest>>({
rule_id: 'rule-1',
output_index: '.siem-signals',
risk_score: 50,
description: 'some description',
from: 'now-5m',
to: 'now',
index: ['index-1'],
name: 'some-name',
severity: 'low',
interval: '5m',
type: 'query',
references: ['index-1'],
query: 'some query',
language: 'kuery',
max_signals: 1,
note: '',
}).error
).toBeFalsy();
});
// Note: If you're looking to remove `note`, omit `note` entirely
test('You cannot set note to null', () => {
expect(
updateRulesSchema.validate<Partial<RuleAlertParamsRest>>({
rule_id: 'rule-1',
output_index: '.siem-signals',
risk_score: 50,
description: 'some description',
from: 'now-5m',
to: 'now',
index: ['index-1'],
name: 'some-name',
severity: 'low',
interval: '5m',
type: 'query',
references: ['index-1'],
query: 'some query',
language: 'kuery',
max_signals: 1,
note: null,
}).error.message
).toEqual('child "note" fails because ["note" must be a string]');
});
test('You cannot set note as an object', () => {
expect(
updateRulesSchema.validate<Partial<Omit<RuleAlertParamsRest, 'note'> & { note: object }>>({
rule_id: 'rule-1',
output_index: '.siem-signals',
risk_score: 50,
description: 'some description',
from: 'now-5m',
to: 'now',
index: ['index-1'],
name: 'some-name',
severity: 'low',
interval: '5m',
type: 'query',
references: ['index-1'],
query: 'some query',
language: 'kuery',
max_signals: 1,
note: {
somethingMadeUp: { somethingElse: true },
},
}).error.message
).toEqual('child "note" fails because ["note" must be a string]');
});
});
});

View file

@ -33,6 +33,7 @@ import {
threat,
references,
id,
note,
version,
} from './schemas';
/* eslint-enable @typescript-eslint/camelcase */
@ -76,5 +77,6 @@ export const updateRulesSchema = Joi.object({
type: type.required(),
threat: threat.default([]),
references: references.default([]),
note: note.allow(''),
version,
}).xor('id', 'rule_id');

View file

@ -37,6 +37,7 @@ export const createRules = ({
to,
type,
references,
note,
version,
}: CreateRuleParams): Promise<Alert> => {
return alertsClient.create({
@ -67,6 +68,7 @@ export const createRules = ({
to,
type,
references,
note,
version,
},
schedule: { interval },

View file

@ -21,7 +21,7 @@ describe('getExportAll', () => {
const exports = await getExportAll(alertsClient);
expect(exports).toEqual({
rulesNdjson:
'{"created_at":"2019-12-13T16:40:33.400Z","updated_at":"2019-12-13T16:40:33.400Z","created_by":"elastic","description":"Detecting root and admin users","enabled":true,"false_positives":[],"filters":[{"query":{"match_phrase":{"host.name":"some-host"}}}],"from":"now-6m","id":"04128c15-0d1b-4716-a4c5-46997ac7f3bd","immutable":false,"index":["auditbeat-*","filebeat-*","packetbeat-*","winlogbeat-*"],"interval":"5m","rule_id":"rule-1","language":"kuery","output_index":".siem-signals","max_signals":100,"risk_score":50,"name":"Detect Root/Admin Users","query":"user.name: root or user.name: admin","references":["http://www.example.com","https://ww.example.com"],"timeline_id":"some-timeline-id","timeline_title":"some-timeline-title","meta":{"someMeta":"someField"},"severity":"high","updated_by":"elastic","tags":[],"to":"now","type":"query","threat":[{"framework":"MITRE ATT&CK","tactic":{"id":"TA0040","name":"impact","reference":"https://attack.mitre.org/tactics/TA0040/"},"technique":[{"id":"T1499","name":"endpoint denial of service","reference":"https://attack.mitre.org/techniques/T1499/"}]}],"version":1}\n',
'{"created_at":"2019-12-13T16:40:33.400Z","updated_at":"2019-12-13T16:40:33.400Z","created_by":"elastic","description":"Detecting root and admin users","enabled":true,"false_positives":[],"filters":[{"query":{"match_phrase":{"host.name":"some-host"}}}],"from":"now-6m","id":"04128c15-0d1b-4716-a4c5-46997ac7f3bd","immutable":false,"index":["auditbeat-*","filebeat-*","packetbeat-*","winlogbeat-*"],"interval":"5m","rule_id":"rule-1","language":"kuery","output_index":".siem-signals","max_signals":100,"risk_score":50,"name":"Detect Root/Admin Users","query":"user.name: root or user.name: admin","references":["http://www.example.com","https://ww.example.com"],"timeline_id":"some-timeline-id","timeline_title":"some-timeline-title","meta":{"someMeta":"someField"},"severity":"high","updated_by":"elastic","tags":[],"to":"now","type":"query","threat":[{"framework":"MITRE ATT&CK","tactic":{"id":"TA0040","name":"impact","reference":"https://attack.mitre.org/tactics/TA0040/"},"technique":[{"id":"T1499","name":"endpoint denial of service","reference":"https://attack.mitre.org/techniques/T1499/"}]}],"note":"# Investigative notes","version":1}\n',
exportDetails: '{"exported_count":1,"missing_rules":[],"missing_rules_count":0}\n',
});
});

View file

@ -29,7 +29,7 @@ describe('get_export_by_object_ids', () => {
const exports = await getExportByObjectIds(alertsClient, objects);
expect(exports).toEqual({
rulesNdjson:
'{"created_at":"2019-12-13T16:40:33.400Z","updated_at":"2019-12-13T16:40:33.400Z","created_by":"elastic","description":"Detecting root and admin users","enabled":true,"false_positives":[],"filters":[{"query":{"match_phrase":{"host.name":"some-host"}}}],"from":"now-6m","id":"04128c15-0d1b-4716-a4c5-46997ac7f3bd","immutable":false,"index":["auditbeat-*","filebeat-*","packetbeat-*","winlogbeat-*"],"interval":"5m","rule_id":"rule-1","language":"kuery","output_index":".siem-signals","max_signals":100,"risk_score":50,"name":"Detect Root/Admin Users","query":"user.name: root or user.name: admin","references":["http://www.example.com","https://ww.example.com"],"timeline_id":"some-timeline-id","timeline_title":"some-timeline-title","meta":{"someMeta":"someField"},"severity":"high","updated_by":"elastic","tags":[],"to":"now","type":"query","threat":[{"framework":"MITRE ATT&CK","tactic":{"id":"TA0040","name":"impact","reference":"https://attack.mitre.org/tactics/TA0040/"},"technique":[{"id":"T1499","name":"endpoint denial of service","reference":"https://attack.mitre.org/techniques/T1499/"}]}],"version":1}\n',
'{"created_at":"2019-12-13T16:40:33.400Z","updated_at":"2019-12-13T16:40:33.400Z","created_by":"elastic","description":"Detecting root and admin users","enabled":true,"false_positives":[],"filters":[{"query":{"match_phrase":{"host.name":"some-host"}}}],"from":"now-6m","id":"04128c15-0d1b-4716-a4c5-46997ac7f3bd","immutable":false,"index":["auditbeat-*","filebeat-*","packetbeat-*","winlogbeat-*"],"interval":"5m","rule_id":"rule-1","language":"kuery","output_index":".siem-signals","max_signals":100,"risk_score":50,"name":"Detect Root/Admin Users","query":"user.name: root or user.name: admin","references":["http://www.example.com","https://ww.example.com"],"timeline_id":"some-timeline-id","timeline_title":"some-timeline-title","meta":{"someMeta":"someField"},"severity":"high","updated_by":"elastic","tags":[],"to":"now","type":"query","threat":[{"framework":"MITRE ATT&CK","tactic":{"id":"TA0040","name":"impact","reference":"https://attack.mitre.org/tactics/TA0040/"},"technique":[{"id":"T1499","name":"endpoint denial of service","reference":"https://attack.mitre.org/techniques/T1499/"}]}],"note":"# Investigative notes","version":1}\n',
exportDetails: '{"exported_count":1,"missing_rules":[],"missing_rules_count":0}\n',
});
});
@ -117,6 +117,7 @@ describe('get_export_by_object_ids', () => {
],
},
],
note: '# Investigative notes',
version: 1,
},
],

View file

@ -42,6 +42,7 @@ export const installPrepackagedRules = (
type,
threat,
references,
note,
version,
} = rule;
return [
@ -74,6 +75,7 @@ export const installPrepackagedRules = (
type,
threat,
references,
note,
version,
}),
];

View file

@ -42,6 +42,7 @@ export const patchRules = async ({
to,
type,
references,
note,
version,
throttle,
}: PatchRuleParams): Promise<PartialAlert | null> => {
@ -75,6 +76,7 @@ export const patchRules = async ({
references,
version,
throttle,
note,
});
const nextParams = defaults(
@ -102,6 +104,7 @@ export const patchRules = async ({
to,
type,
references,
note,
version: calculatedVersion,
}
);

View file

@ -42,6 +42,7 @@ export const updatePrepackagedRules = async (
references,
version,
throttle,
note,
} = rule;
// Note: we do not pass down enabled as we do not want to suddenly disable
@ -75,6 +76,7 @@ export const updatePrepackagedRules = async (
references,
version,
throttle,
note,
});
});
};

View file

@ -43,6 +43,7 @@ export const updateRules = async ({
references,
version,
throttle,
note,
}: UpdateRuleParams): Promise<PartialAlert | null> => {
const rule = await readRules({ alertsClient, ruleId, id });
if (rule == null) {
@ -74,6 +75,7 @@ export const updateRules = async ({
references,
version,
throttle,
note,
});
const update = await alertsClient.update({
@ -106,6 +108,7 @@ export const updateRules = async ({
to,
type,
references,
note,
version: calculatedVersion,
},
},

View file

@ -0,0 +1,4 @@
{
"rule_id": "query-with-note",
"note": " # Changes only the note to this new value"
}

View file

@ -78,5 +78,6 @@
],
"timeline_id": "timeline_id",
"timeline_title": "timeline_title",
"note": "# note markdown",
"version": 1
}

View file

@ -0,0 +1,10 @@
{
"name": "Query with a note",
"description": "Query with a note",
"rule_id": "query-with-note",
"risk_score": 1,
"severity": "high",
"type": "query",
"query": "user.name: root or user.name: admin",
"note": "# investigative note markdown header"
}

View file

@ -0,0 +1,10 @@
{
"name": "Query with a note",
"description": "Query with a note",
"rule_id": "query-with-note",
"risk_score": 1,
"severity": "high",
"type": "query",
"query": "user.name: root or user.name: admin",
"note": "# Changes only note to this new value on update"
}

View file

@ -28,6 +28,7 @@ export const sampleRuleAlertParams = (
references: ['http://google.com'],
riskScore: riskScore ? riskScore : 50,
maxSignals: maxSignals ? maxSignals : 10000,
note: '',
filters: undefined,
savedId: undefined,
timelineId: undefined,
@ -340,6 +341,7 @@ export const sampleRule = (): Partial<OutputRuleAlertRest> => {
tags: ['some fake tag 1', 'some fake tag 2'],
to: 'now',
type: 'query',
note: '',
};
};

View file

@ -79,6 +79,7 @@ describe('buildBulkBody', () => {
tags: ['some fake tag 1', 'some fake tag 2'],
type: 'query',
to: 'now',
note: '',
enabled: true,
created_by: 'elastic',
updated_by: 'elastic',
@ -168,6 +169,7 @@ describe('buildBulkBody', () => {
tags: ['some fake tag 1', 'some fake tag 2'],
type: 'query',
to: 'now',
note: '',
enabled: true,
created_by: 'elastic',
updated_by: 'elastic',
@ -255,6 +257,7 @@ describe('buildBulkBody', () => {
tags: ['some fake tag 1', 'some fake tag 2'],
type: 'query',
to: 'now',
note: '',
enabled: true,
created_by: 'elastic',
updated_by: 'elastic',
@ -335,6 +338,7 @@ describe('buildBulkBody', () => {
tags: ['some fake tag 1', 'some fake tag 2'],
type: 'query',
to: 'now',
note: '',
enabled: true,
created_by: 'elastic',
updated_by: 'elastic',

View file

@ -60,6 +60,7 @@ describe('buildRule', () => {
tags: ['some fake tag 1', 'some fake tag 2'],
to: 'now',
type: 'query',
note: '',
updated_by: 'elastic',
updated_at: rule.updated_at,
created_at: rule.created_at,
@ -116,6 +117,7 @@ describe('buildRule', () => {
tags: ['some fake tag 1', 'some fake tag 2'],
to: 'now',
type: 'query',
note: '',
updated_by: 'elastic',
version: 1,
updated_at: rule.updated_at,
@ -161,6 +163,7 @@ describe('buildRule', () => {
tags: ['some fake tag 1', 'some fake tag 2'],
to: 'now',
type: 'query',
note: '',
updated_by: 'elastic',
version: 1,
updated_at: rule.updated_at,

View file

@ -44,6 +44,7 @@ export const buildRule = ({
risk_score: ruleParams.riskScore,
output_index: ruleParams.outputIndex,
description: ruleParams.description,
note: ruleParams.note,
from: ruleParams.from,
immutable: ruleParams.immutable,
index: ruleParams.index,

View file

@ -66,6 +66,7 @@ describe('buildSignal', () => {
tags: ['some fake tag 1', 'some fake tag 2'],
to: 'now',
type: 'query',
note: '',
updated_at: signal.rule.updated_at,
created_at: signal.rule.created_at,
},
@ -131,6 +132,7 @@ describe('buildSignal', () => {
tags: ['some fake tag 1', 'some fake tag 2'],
to: 'now',
type: 'query',
note: '',
updated_at: signal.rule.updated_at,
created_at: signal.rule.created_at,
},
@ -202,6 +204,7 @@ describe('buildSignal', () => {
tags: ['some fake tag 1', 'some fake tag 2'],
to: 'now',
type: 'query',
note: '',
updated_at: signal.rule.updated_at,
created_at: signal.rule.created_at,
},

View file

@ -55,6 +55,7 @@ export const signalRulesAlertType = ({
validate: {
params: schema.object({
description: schema.string(),
note: schema.nullable(schema.string()),
falsePositives: schema.arrayOf(schema.string(), { defaultValue: [] }),
from: schema.string(),
ruleId: schema.string(),

View file

@ -24,6 +24,7 @@ export interface ThreatParams {
export interface RuleAlertParams {
description: string;
note: string | undefined | null;
enabled: boolean;
falsePositives: string[];
filters: PartialFilter[] | undefined | null;

View file

@ -285,6 +285,7 @@ export const getComplexRule = (ruleId = 'rule-1'): Partial<OutputRuleAlertRest>
],
timeline_id: 'timeline_id',
timeline_title: 'timeline_title',
note: '# some investigation documentation',
version: 1,
query: 'user.name: root or user.name: admin',
});
@ -370,6 +371,7 @@ export const getComplexRuleOutput = (ruleId = 'rule-1'): Partial<OutputRuleAlert
timeline_id: 'timeline_id',
timeline_title: 'timeline_title',
updated_by: 'elastic',
note: '# some investigation documentation',
version: 1,
query: 'user.name: root or user.name: admin',
});