diff --git a/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.mock.ts index 3150cb9975f21..ff39d91be7e4a 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.mock.ts @@ -46,3 +46,13 @@ export const getCreateExceptionListMinimalSchemaMockWithoutId = (): CreateExcept name: NAME, type: ENDPOINT_TYPE, }); + +/** + * Useful for end to end testing with detections + */ +export const getCreateExceptionListDetectionSchemaMock = (): CreateExceptionListSchema => ({ + description: DESCRIPTION, + list_id: LIST_ID, + name: NAME, + type: 'detection', +}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts index 6be51d2a1adc2..26d2a2cff2910 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts @@ -40,9 +40,11 @@ export const getCreateSavedQueryRulesSchemaMock = (ruleId = 'rule-1'): SavedQuer }); export const getCreateThreatMatchRulesSchemaMock = ( - ruleId = 'rule-1' + ruleId = 'rule-1', + enabled = false ): ThreatMatchCreateSchema => ({ description: 'Detecting root and admin users', + enabled, name: 'Query with a rule id', query: 'user.name: root or user.name: admin', severity: 'high', diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts index 08c544b9246e0..1bf6b64db2427 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts @@ -115,12 +115,12 @@ export const getThreatMatchingSchemaMock = (anchorDate: string = ANCHOR_DATE): R * Useful for e2e backend tests where it doesn't have date time and other * server side properties attached to it. */ -export const getThreatMatchingSchemaPartialMock = (): Partial => { +export const getThreatMatchingSchemaPartialMock = (enabled = false): Partial => { return { author: [], created_by: 'elastic', description: 'Detecting root and admin users', - enabled: true, + enabled, false_positives: [], from: 'now-6m', immutable: false, diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts index c682c1f1f4640..b653d46905503 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts @@ -5,6 +5,7 @@ */ import expect from '@kbn/expect'; +import { PrePackagedRulesAndTimelinesSchema } from '../../../../plugins/security_solution/common/detection_engine/schemas/response'; import { DETECTION_ENGINE_PREPACKAGED_URL } from '../../../../plugins/security_solution/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -13,6 +14,7 @@ import { deleteAllAlerts, deleteAllTimelines, deleteSignalsIndex, + installPrePackagedRules, waitFor, } from '../../utils'; @@ -45,18 +47,27 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await deleteAllTimelines(es); }); - it('should contain rules_installed, rules_updated, timelines_installed, and timelines_updated', async () => { - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(Object.keys(body)).to.eql([ + it('should create the prepackaged rules and return a count greater than zero, rules_updated to be zero, and contain the correct keys', async () => { + let responseBody: unknown; + await waitFor(async () => { + const { body, status } = await supertest + .put(DETECTION_ENGINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send(); + if (status === 200) { + responseBody = body; + } + return status === 200; + }, DETECTION_ENGINE_PREPACKAGED_URL); + + const prepackagedRules = responseBody as PrePackagedRulesAndTimelinesSchema; + expect(prepackagedRules.rules_installed).to.be.greaterThan(0); + expect(prepackagedRules.rules_updated).to.eql(0); + expect(Object.keys(prepackagedRules)).to.eql([ 'rules_installed', 'rules_updated', 'timelines_installed', @@ -64,52 +75,8 @@ export default ({ getService }: FtrProviderContext): void => { ]); }); - it('should create the prepackaged rules and return a count greater than zero', async () => { - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(body.rules_installed).to.be.greaterThan(0); - }); - - it('should create the prepackaged timelines and return a count greater than zero', async () => { - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(body.timelines_installed).to.be.greaterThan(0); - }); - - it('should create the prepackaged rules that the rules_updated is of size zero', async () => { - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(body.rules_updated).to.eql(0); - }); - - it('should create the prepackaged timelines and the timelines_updated is of size zero', async () => { - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(body.timelines_updated).to.eql(0); - }); - - it('should be possible to call the API twice and the second time the number of rules installed should be zero', async () => { - await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); + it('should be possible to call the API twice and the second time the number of rules installed should be zero as well as timeline', async () => { + await installPrePackagedRules(supertest); // NOTE: I call the GET call until eventually it becomes consistent and that the number of rules to install are zero. // This is to reduce flakiness where it can for a short period of time try to install the same rule twice. @@ -119,39 +86,23 @@ export default ({ getService }: FtrProviderContext): void => { .set('kbn-xsrf', 'true') .expect(200); return body.rules_not_installed === 0; - }); - - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(body.rules_installed).to.eql(0); - }); - - it('should be possible to call the API twice and the second time the number of timelines installed should be zero', async () => { - await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); + }, `${DETECTION_ENGINE_PREPACKAGED_URL}/_status`); + let responseBody: unknown; await waitFor(async () => { - const { body } = await supertest - .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + const { body, status } = await supertest + .put(DETECTION_ENGINE_PREPACKAGED_URL) .set('kbn-xsrf', 'true') - .expect(200); - return body.timelines_not_installed === 0; - }); - - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(body.timelines_installed).to.eql(0); + .send(); + if (status === 200) { + responseBody = body; + } + return status === 200; + }, DETECTION_ENGINE_PREPACKAGED_URL); + + const prepackagedRules = responseBody as PrePackagedRulesAndTimelinesSchema; + expect(prepackagedRules.rules_installed).to.eql(0); + expect(prepackagedRules.timelines_installed).to.eql(0); }); }); }); diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts index 53a8f1f4ca5c0..a8a5f2abd072b 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts @@ -25,7 +25,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('create_rules', () => { describe('validation errors', () => { @@ -51,7 +50,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should create a single rule with a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules_bulk.ts index 6c3b1c45e202e..73be4154db1eb 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules_bulk.ts @@ -23,7 +23,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('create_rules_bulk', () => { describe('validation errors', () => { @@ -54,7 +53,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should create a single rule with a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules.ts index 7104e16f438c6..786e953843210 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules.ts @@ -24,7 +24,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('delete_rules', () => { describe('deleting rules', () => { @@ -34,7 +33,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should delete a single rule with a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules_bulk.ts index 35b31d2ccfefa..66aa43e8a3817 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules_bulk.ts @@ -24,7 +24,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('delete_rules_bulk', () => { describe('deleting rules bulk using DELETE', () => { @@ -34,7 +33,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should delete a single rule with a rule_id', async () => { @@ -146,7 +145,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should delete a single rule with a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/export_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/export_rules.ts index 2610796bdc384..4f76a0544a152 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/export_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/export_rules.ts @@ -22,7 +22,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('export_rules', () => { describe('exporting rules', () => { @@ -32,7 +31,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should set the response content types to be expected', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/find_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/find_rules.ts index f496d035d8e60..2f06a84c7223b 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/find_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/find_rules.ts @@ -23,7 +23,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('find_rules', () => { beforeEach(async () => { @@ -32,7 +31,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should return an empty find body correctly if no rules are loaded', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts b/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts index 9c20d58c5f4e5..fe80402b60731 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts @@ -30,7 +30,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await deleteAllRulesStatuses(es); }); @@ -45,7 +45,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should return a single rule status when a single rule is loaded from a find status with defaults added', async () => { - const resBody = await createRule(supertest, getSimpleRule()); + const resBody = await createRule(supertest, getSimpleRule('rule-1', true)); await waitForRuleSuccess(supertest, resBody.id); diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/get_prepackaged_rules_status.ts b/x-pack/test/detection_engine_api_integration/basic/tests/get_prepackaged_rules_status.ts index 1bbfce42d2baa..c72b2e50b39fc 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/get_prepackaged_rules_status.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/get_prepackaged_rules_status.ts @@ -32,7 +32,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await deleteAllTimelines(es); }); diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/import_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/import_rules.ts index c6294cfe6ec28..f5774e09bb5e9 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/import_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/import_rules.ts @@ -23,7 +23,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('import_rules', () => { describe('importing rules without an index', () => { @@ -39,7 +38,7 @@ export default ({ getService }: FtrProviderContext): void => { .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`) .send(); return body.status_code === 404; - }); + }, `${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`); // Try to fetch the rule which should still be a 404 (not found) const { body } = await supertest.get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`).send(); @@ -86,7 +85,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should set the response content types to be expected', async () => { @@ -129,7 +128,7 @@ export default ({ getService }: FtrProviderContext): void => { await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') - .attach('file', getSimpleRuleAsNdjson(['rule-1'], true), 'rules.ndjson') + .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') .expect(200); const { body } = await supertest @@ -138,7 +137,7 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(getSimpleRuleOutput('rule-1')); + expect(bodyToCompare).to.eql(getSimpleRuleOutput('rule-1', false)); }); it('should fail validation when importing a rule with malformed "from" params on the rules', async () => { @@ -330,7 +329,7 @@ export default ({ getService }: FtrProviderContext): void => { await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') - .attach('file', getSimpleRuleAsNdjson(['rule-1'], true), 'rules.ndjson') + .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') .expect(200); const simpleRule = getSimpleRule('rule-1'); @@ -422,17 +421,13 @@ export default ({ getService }: FtrProviderContext): void => { await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') - .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2'], true), 'rules.ndjson') + .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2']), 'rules.ndjson') .expect(200); await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') - .attach( - 'file', - getSimpleRuleAsNdjson(['rule-1', 'rule-2', 'rule-3'], true), - 'rules.ndjson' - ) + .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2', 'rule-3']), 'rules.ndjson') .expect(200); const { body: bodyOfRule1 } = await supertest diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/install_prepackaged_timelines.ts b/x-pack/test/detection_engine_api_integration/basic/tests/install_prepackaged_timelines.ts index 556217877968b..f70720cc752b2 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/install_prepackaged_timelines.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/install_prepackaged_timelines.ts @@ -29,7 +29,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await deleteAllTimelines(es); }); @@ -72,7 +72,7 @@ export default ({ getService }: FtrProviderContext): void => { .set('kbn-xsrf', 'true') .expect(200); return body.timelines_not_installed === 0; - }); + }, `${TIMELINE_PREPACKAGED_URL}/_status`); const { body } = await supertest .put(TIMELINE_PREPACKAGED_URL) diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/open_close_signals.ts b/x-pack/test/detection_engine_api_integration/basic/tests/open_close_signals.ts index a84d9845085e0..f8a25b0081ef9 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/open_close_signals.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/open_close_signals.ts @@ -18,19 +18,19 @@ import { deleteSignalsIndex, setSignalStatus, getSignalStatusEmptyResponse, - getSimpleRule, getQuerySignalIds, deleteAllAlerts, createRule, waitForSignalsToBePresent, - getAllSignals, + getSignalsByIds, + waitForRuleSuccess, + getRuleForSignalTesting, } from '../../utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - const es = getService('es'); describe('open_close_signals', () => { describe('validation checks', () => { @@ -66,29 +66,31 @@ export default ({ getService }: FtrProviderContext) => { describe('tests with auditbeat data', () => { beforeEach(async () => { - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await createSignalsIndex(supertest); await esArchiver.load('auditbeat/hosts'); }); afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await esArchiver.unload('auditbeat/hosts'); }); it('should be able to execute and get 10 signals', async () => { - const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 10); - const signalsOpen = await getAllSignals(supertest); + const rule = getRuleForSignalTesting(['auditbeat-*']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits.length).equal(10); }); it('should be have set the signals in an open state initially', async () => { - const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest); - const signalsOpen = await getAllSignals(supertest); + const rule = getRuleForSignalTesting(['auditbeat-*']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); const everySignalOpen = signalsOpen.hits.hits.every( ({ _source: { @@ -100,10 +102,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be able to get a count of 10 closed signals when closing 10', async () => { - const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 10); - const signalsOpen = await getAllSignals(supertest); + const rule = getRuleForSignalTesting(['auditbeat-*']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); // set all of the signals to the state of closed. There is no reason to use a waitUntil here @@ -126,10 +129,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be able close 10 signals immediately and they all should be closed', async () => { - const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest); - const signalsOpen = await getAllSignals(supertest); + const rule = getRuleForSignalTesting(['auditbeat-*']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); // set all of the signals to the state of closed. There is no reason to use a waitUntil here diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules.ts index 36a9649d875ca..28ea2e1ff8803 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules.ts @@ -23,7 +23,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('patch_rules', () => { describe('patch rules', () => { @@ -33,7 +32,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should patch a single rule property of name using a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules_bulk.ts index 69330a2bf682a..e32771d0d917c 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules_bulk.ts @@ -23,7 +23,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('patch_rules_bulk', () => { describe('patch rules bulk', () => { @@ -33,7 +32,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should patch a single rule property of name using a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/read_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/read_rules.ts index cfccb7436ea20..1697554441c16 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/read_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/read_rules.ts @@ -24,7 +24,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('read_rules', () => { describe('reading rules', () => { @@ -34,7 +33,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should be able to read a single rule using rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/update_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/update_rules.ts index 2f5a043881eeb..d8e9c650c8116 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/update_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/update_rules.ts @@ -25,7 +25,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('update_rules', () => { describe('update rules', () => { @@ -35,7 +34,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should update a single rule property of name using a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/update_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/basic/tests/update_rules_bulk.ts index 22aa40b0721a4..c5b65039aa116 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/update_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/update_rules_bulk.ts @@ -24,7 +24,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('update_rules_bulk', () => { describe('update rules bulk', () => { @@ -34,7 +33,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should update a single rule property of name using a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts index d473863e7d028..bbd85e353e095 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts @@ -24,7 +24,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('add_actions', () => { describe('adding actions', () => { @@ -34,7 +33,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should be able to create a new webhook action and attach it to a rule', async () => { @@ -60,7 +59,7 @@ export default ({ getService }: FtrProviderContext) => { .send(getWebHookAction()) .expect(200); - const rule = await createRule(supertest, getRuleWithWebHookAction(hookAction.id)); + const rule = await createRule(supertest, getRuleWithWebHookAction(hookAction.id, true)); await waitForRuleSuccess(supertest, rule.id); // expected result for status should be 'succeeded' @@ -82,7 +81,7 @@ export default ({ getService }: FtrProviderContext) => { // create a rule with the action attached and a meta field const ruleWithAction: CreateRulesSchema = { - ...getRuleWithWebHookAction(hookAction.id), + ...getRuleWithWebHookAction(hookAction.id, true), meta: {}, }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_prepackaged_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_prepackaged_rules.ts index c889e152759a8..b653d46905503 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_prepackaged_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_prepackaged_rules.ts @@ -5,6 +5,7 @@ */ import expect from '@kbn/expect'; +import { PrePackagedRulesAndTimelinesSchema } from '../../../../plugins/security_solution/common/detection_engine/schemas/response'; import { DETECTION_ENGINE_PREPACKAGED_URL } from '../../../../plugins/security_solution/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -13,6 +14,7 @@ import { deleteAllAlerts, deleteAllTimelines, deleteSignalsIndex, + installPrePackagedRules, waitFor, } from '../../utils'; @@ -45,18 +47,27 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await deleteAllTimelines(es); }); - it('should contain two output keys of rules_installed and rules_updated', async () => { - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(Object.keys(body)).to.eql([ + it('should create the prepackaged rules and return a count greater than zero, rules_updated to be zero, and contain the correct keys', async () => { + let responseBody: unknown; + await waitFor(async () => { + const { body, status } = await supertest + .put(DETECTION_ENGINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send(); + if (status === 200) { + responseBody = body; + } + return status === 200; + }, DETECTION_ENGINE_PREPACKAGED_URL); + + const prepackagedRules = responseBody as PrePackagedRulesAndTimelinesSchema; + expect(prepackagedRules.rules_installed).to.be.greaterThan(0); + expect(prepackagedRules.rules_updated).to.eql(0); + expect(Object.keys(prepackagedRules)).to.eql([ 'rules_installed', 'rules_updated', 'timelines_installed', @@ -64,74 +75,34 @@ export default ({ getService }: FtrProviderContext): void => { ]); }); - it('should create the prepackaged rules and return a count greater than zero', async () => { - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(body.rules_installed).to.be.greaterThan(0); - }); - - it('should create the prepackaged rules that the rules_updated is of size zero', async () => { - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(body.rules_updated).to.eql(0); - }); - - it('should be possible to call the API twice and the second time the number of rules installed should be zero', async () => { - await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); + it('should be possible to call the API twice and the second time the number of rules installed should be zero as well as timeline', async () => { + await installPrePackagedRules(supertest); // NOTE: I call the GET call until eventually it becomes consistent and that the number of rules to install are zero. - // This is to reduce flakiness where it can for a short period of time try to install the same rule the same rule twice. + // This is to reduce flakiness where it can for a short period of time try to install the same rule twice. await waitFor(async () => { const { body } = await supertest .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) .set('kbn-xsrf', 'true') .expect(200); return body.rules_not_installed === 0; - }); - - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(body.rules_installed).to.eql(0); - }); - - it('should be possible to call the API twice and the second time the number of timelines installed should be zero', async () => { - await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); + }, `${DETECTION_ENGINE_PREPACKAGED_URL}/_status`); + let responseBody: unknown; await waitFor(async () => { - const { body } = await supertest - .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + const { body, status } = await supertest + .put(DETECTION_ENGINE_PREPACKAGED_URL) .set('kbn-xsrf', 'true') - .expect(200); - return body.timelines_not_installed === 0; - }); - - const { body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(body.timelines_installed).to.eql(0); + .send(); + if (status === 200) { + responseBody = body; + } + return status === 200; + }, DETECTION_ENGINE_PREPACKAGED_URL); + + const prepackagedRules = responseBody as PrePackagedRulesAndTimelinesSchema; + expect(prepackagedRules.rules_installed).to.eql(0); + expect(prepackagedRules.timelines_installed).to.eql(0); }); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts index 651a7601ca95a..7e4a6ad86cda5 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts @@ -32,7 +32,7 @@ import { createExceptionList, createExceptionListItem, waitForSignalsToBePresent, - getAllSignals, + getSignalsByIds, } from '../../utils'; // eslint-disable-next-line import/no-default-export @@ -49,7 +49,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await deleteAllExceptions(es); }); @@ -101,6 +101,7 @@ export default ({ getService }: FtrProviderContext) => { const ruleWithException: CreateRulesSchema = { ...getSimpleRule(), + enabled: true, exceptions_list: [ { id, @@ -117,6 +118,7 @@ export default ({ getService }: FtrProviderContext) => { const expected: Partial = { ...getSimpleRuleOutput(), + enabled: true, exceptions_list: [ { id, @@ -397,7 +399,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await deleteAllExceptions(es); await esArchiver.unload('auditbeat/hosts'); }); @@ -441,9 +443,10 @@ export default ({ getService }: FtrProviderContext) => { }, ], }; - await createRule(supertest, ruleWithException); - await waitForSignalsToBePresent(supertest, 10); - const signalsOpen = await getAllSignals(supertest); + const { id: createdId } = await createRule(supertest, ruleWithException); + await waitForRuleSuccess(supertest, createdId); + await waitForSignalsToBePresent(supertest, 10, [createdId]); + const signalsOpen = await getSignalsByIds(supertest, [createdId]); expect(signalsOpen.hits.hits.length).equal(10); }); @@ -488,7 +491,7 @@ export default ({ getService }: FtrProviderContext) => { }; const rule = await createRule(supertest, ruleWithException); await waitForRuleSuccess(supertest, rule.id); - const signalsOpen = await getAllSignals(supertest); + const signalsOpen = await getSignalsByIds(supertest, [rule.id]); expect(signalsOpen.hits.hits.length).equal(0); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts index a18faf8543042..0da12ebba055a 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts @@ -25,12 +25,12 @@ import { getSimpleMlRule, getSimpleMlRuleOutput, waitForRuleSuccess, + getRuleForSignalTesting, } from '../../utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('create_rules', () => { describe('validation errors', () => { @@ -56,7 +56,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should create a single rule with a rule_id', async () => { @@ -90,7 +90,7 @@ export default ({ getService }: FtrProviderContext) => { this pops up again elsewhere. */ it('should create a single rule with a rule_id and validate it ran successfully', async () => { - const simpleRule = getSimpleRule(); + const simpleRule = getRuleForSignalTesting(['auditbeat-*']); const { body } = await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') @@ -105,8 +105,6 @@ export default ({ getService }: FtrProviderContext) => { .send({ ids: [body.id] }) .expect(200); - const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(getSimpleRuleOutput()); expect(statusBody[body.id].current_status.status).to.eql('succeeded'); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts index 58790dbfb759c..7ea47312a5030 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts @@ -15,6 +15,7 @@ import { createSignalsIndex, deleteAllAlerts, deleteSignalsIndex, + getRuleForSignalTesting, getSimpleRule, getSimpleRuleOutput, getSimpleRuleOutputWithoutRuleId, @@ -27,7 +28,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('create_rules_bulk', () => { describe('validation errors', () => { @@ -58,7 +58,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should create a single rule with a rule_id', async () => { @@ -92,7 +92,7 @@ export default ({ getService }: FtrProviderContext): void => { this pops up again elsewhere. */ it('should create a single rule with a rule_id and validate it ran successfully', async () => { - const simpleRule = getSimpleRule(); + const simpleRule = getRuleForSignalTesting(['auditbeat-*']); const { body } = await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_create`) .set('kbn-xsrf', 'true') @@ -107,8 +107,6 @@ export default ({ getService }: FtrProviderContext): void => { .send({ ids: [body[0].id] }) .expect(200); - const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(getSimpleRuleOutput()); expect(statusBody[body[0].id].current_status.status).to.eql('succeeded'); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts index 36cd8480998c5..21cfab3db6d6a 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts @@ -17,7 +17,7 @@ import { createSignalsIndex, deleteAllAlerts, deleteSignalsIndex, - getAllSignals, + getSignalsByIds, removeServerGeneratedProperties, waitForRuleSuccess, waitForSignalsToBePresent, @@ -30,7 +30,6 @@ import { getThreatMatchingSchemaPartialMock } from '../../../../plugins/security export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - const es = getService('es'); /** * Specific api integration tests for threat matching rule type @@ -59,7 +58,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should create a single rule with a rule_id', async () => { @@ -69,7 +68,10 @@ export default ({ getService }: FtrProviderContext) => { }); it('should create a single rule with a rule_id and validate it ran successfully', async () => { - const ruleResponse = await createRule(supertest, getCreateThreatMatchRulesSchemaMock()); + const ruleResponse = await createRule( + supertest, + getCreateThreatMatchRulesSchemaMock('rule-1', true) + ); await waitForRuleSuccess(supertest, ruleResponse.id); const { body: statusBody } = await supertest @@ -79,21 +81,21 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); const bodyToCompare = removeServerGeneratedProperties(ruleResponse); - expect(bodyToCompare).to.eql(getThreatMatchingSchemaPartialMock()); + expect(bodyToCompare).to.eql(getThreatMatchingSchemaPartialMock(true)); expect(statusBody[ruleResponse.id].current_status.status).to.eql('succeeded'); }); }); describe('tests with auditbeat data', () => { beforeEach(async () => { - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await createSignalsIndex(supertest); await esArchiver.load('auditbeat/hosts'); }); afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await esArchiver.unload('auditbeat/hosts'); }); @@ -125,9 +127,10 @@ export default ({ getService }: FtrProviderContext) => { threat_filters: [], }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 10); - const signalsOpen = await getAllSignals(supertest); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits.length).equal(10); }); @@ -161,7 +164,7 @@ export default ({ getService }: FtrProviderContext) => { const ruleResponse = await createRule(supertest, rule); await waitForRuleSuccess(supertest, ruleResponse.id); - const signalsOpen = await getAllSignals(supertest); + const signalsOpen = await getSignalsByIds(supertest, [ruleResponse.id]); expect(signalsOpen.hits.hits.length).equal(0); }); @@ -199,7 +202,7 @@ export default ({ getService }: FtrProviderContext) => { const ruleResponse = await createRule(supertest, rule); await waitForRuleSuccess(supertest, ruleResponse.id); - const signalsOpen = await getAllSignals(supertest); + const signalsOpen = await getSignalsByIds(supertest, [ruleResponse.id]); expect(signalsOpen.hits.hits.length).equal(0); }); @@ -237,7 +240,7 @@ export default ({ getService }: FtrProviderContext) => { const ruleResponse = await createRule(supertest, rule); await waitForRuleSuccess(supertest, ruleResponse.id); - const signalsOpen = await getAllSignals(supertest); + const signalsOpen = await getSignalsByIds(supertest, [ruleResponse.id]); expect(signalsOpen.hits.hits.length).equal(0); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules.ts index 7104e16f438c6..786e953843210 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules.ts @@ -24,7 +24,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('delete_rules', () => { describe('deleting rules', () => { @@ -34,7 +33,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should delete a single rule with a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules_bulk.ts index 35b31d2ccfefa..66aa43e8a3817 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules_bulk.ts @@ -24,7 +24,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('delete_rules_bulk', () => { describe('deleting rules bulk using DELETE', () => { @@ -34,7 +33,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should delete a single rule with a rule_id', async () => { @@ -146,7 +145,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should delete a single rule with a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/README.md b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/README.md new file mode 100644 index 0000000000000..d6beb912d7007 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/README.md @@ -0,0 +1,21 @@ +These are tests for rule exception lists where we test each data type +* date +* double +* float +* integer +* ip +* keyword +* long +* text + +Against the operator types of: +* "is" +* "is not" +* "is one of" +* "is not one of" +* "exists" +* "does not exist" +* "is in list" +* "is not in list" + +If you add a test here, ensure you add it to the ./index.ts" file as well \ No newline at end of file diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/date.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/date.ts new file mode 100644 index 0000000000000..09cc470defa08 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/date.ts @@ -0,0 +1,611 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { + createListsIndex, + deleteAllExceptions, + deleteListsIndex, + importFile, +} from '../../../../lists_api_integration/utils'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { + createRule, + createRuleWithExceptionEntries, + createSignalsIndex, + deleteAllAlerts, + deleteSignalsIndex, + getRuleForSignalTesting, + getSignalsById, + waitForRuleSuccess, + waitForSignalsToBePresent, +} from '../../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + + describe('Rule exception operators for data type date', () => { + beforeEach(async () => { + await createSignalsIndex(supertest); + await createListsIndex(supertest); + await esArchiver.load('rule_exceptions/date'); + }); + + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(supertest); + await deleteAllExceptions(es); + await deleteListsIndex(supertest); + await esArchiver.unload('rule_exceptions/date'); + }); + + describe('"is" operator', () => { + it('should find all the dates from the data set when no exceptions are set on the rule', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([ + '2020-10-01T05:08:53.000Z', + '2020-10-02T05:08:53.000Z', + '2020-10-03T05:08:53.000Z', + '2020-10-04T05:08:53.000Z', + ]); + }); + + it('should filter 1 single date if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'included', + type: 'match', + value: '2020-10-01T05:08:53.000Z', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([ + '2020-10-02T05:08:53.000Z', + '2020-10-03T05:08:53.000Z', + '2020-10-04T05:08:53.000Z', + ]); + }); + + it('should filter 2 dates if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'included', + type: 'match', + value: '2020-10-01T05:08:53.000Z', + }, + ], + [ + { + field: 'date', + operator: 'included', + type: 'match', + value: '2020-10-02T05:08:53.000Z', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql(['2020-10-03T05:08:53.000Z', '2020-10-04T05:08:53.000Z']); + }); + + it('should filter 3 dates if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'included', + type: 'match', + value: '2020-10-01T05:08:53.000Z', + }, + ], + [ + { + field: 'date', + operator: 'included', + type: 'match', + value: '2020-10-02T05:08:53.000Z', + }, + ], + [ + { + field: 'date', + operator: 'included', + type: 'match', + value: '2020-10-03T05:08:53.000Z', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql(['2020-10-04T05:08:53.000Z']); + }); + + it('should filter 4 dates if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'included', + type: 'match', + value: '2020-10-01T05:08:53.000Z', + }, + ], + [ + { + field: 'date', + operator: 'included', + type: 'match', + value: '2020-10-02T05:08:53.000Z', + }, + ], + [ + { + field: 'date', + operator: 'included', + type: 'match', + value: '2020-10-03T05:08:53.000Z', + }, + ], + [ + { + field: 'date', + operator: 'included', + type: 'match', + value: '2020-10-04T05:08:53.000Z', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'excluded', + type: 'match', + value: '2021-10-01T05:08:53.000Z', // date is not in data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([]); + }); + + it('will return just 1 result we excluded', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'excluded', + type: 'match', + value: '2020-10-01T05:08:53.000Z', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql(['2020-10-01T05:08:53.000Z']); + }); + + it('will return 0 results if we exclude two dates', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'excluded', + type: 'match', + value: '2020-10-01T05:08:53.000Z', + }, + ], + [ + { + field: 'date', + operator: 'excluded', + type: 'match', + value: '2020-10-02T05:08:53.000Z', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is one of" operator', () => { + it('should filter 1 single date if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'included', + type: 'match_any', + value: ['2020-10-01T05:08:53.000Z'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([ + '2020-10-02T05:08:53.000Z', + '2020-10-03T05:08:53.000Z', + '2020-10-04T05:08:53.000Z', + ]); + }); + + it('should filter 2 dates if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'included', + type: 'match_any', + value: ['2020-10-01T05:08:53.000Z', '2020-10-02T05:08:53.000Z'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql(['2020-10-03T05:08:53.000Z', '2020-10-04T05:08:53.000Z']); + }); + + it('should filter 3 dates if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'included', + type: 'match_any', + value: [ + '2020-10-01T05:08:53.000Z', + '2020-10-02T05:08:53.000Z', + '2020-10-03T05:08:53.000Z', + ], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql(['2020-10-04T05:08:53.000Z']); + }); + + it('should filter 4 dates if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'included', + type: 'match_any', + value: [ + '2020-10-01T05:08:53.000Z', + '2020-10-02T05:08:53.000Z', + '2020-10-03T05:08:53.000Z', + '2020-10-04T05:08:53.000Z', + ], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not one of" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'excluded', + type: 'match_any', + value: ['2021-10-01T05:08:53.000Z', '2022-10-01T05:08:53.000Z'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([]); + }); + + it('will return just the result we excluded', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'excluded', + type: 'match_any', + value: ['2020-10-01T05:08:53.000Z', '2020-10-04T05:08:53.000Z'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql(['2020-10-01T05:08:53.000Z', '2020-10-04T05:08:53.000Z']); + }); + }); + + describe('"exists" operator', () => { + it('will return 0 results if matching against date', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'included', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"does not exist" operator', () => { + it('will return 4 results if matching against date', async () => { + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + operator: 'excluded', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([ + '2020-10-01T05:08:53.000Z', + '2020-10-02T05:08:53.000Z', + '2020-10-03T05:08:53.000Z', + '2020-10-04T05:08:53.000Z', + ]); + }); + }); + + describe('"is in list" operator', () => { + it('will return 3 results if we have a list that includes 1 date', async () => { + await importFile(supertest, 'date', ['2020-10-01T05:08:53.000Z'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + list: { + id: 'list_items.txt', + type: 'date', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([ + '2020-10-02T05:08:53.000Z', + '2020-10-03T05:08:53.000Z', + '2020-10-04T05:08:53.000Z', + ]); + }); + + it('will return 2 results if we have a list that includes 2 dates', async () => { + await importFile( + supertest, + 'date', + ['2020-10-01T05:08:53.000Z', '2020-10-03T05:08:53.000Z'], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + list: { + id: 'list_items.txt', + type: 'date', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql(['2020-10-02T05:08:53.000Z', '2020-10-04T05:08:53.000Z']); + }); + + it('will return 0 results if we have a list that includes all dates', async () => { + await importFile( + supertest, + 'date', + [ + '2020-10-01T05:08:53.000Z', + '2020-10-02T05:08:53.000Z', + '2020-10-03T05:08:53.000Z', + '2020-10-04T05:08:53.000Z', + ], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + list: { + id: 'list_items.txt', + type: 'date', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not in list" operator', () => { + it('will return 1 result if we have a list that excludes 1 date', async () => { + await importFile(supertest, 'date', ['2020-10-01T05:08:53.000Z'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + list: { + id: 'list_items.txt', + type: 'date', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql(['2020-10-01T05:08:53.000Z']); + }); + + it('will return 2 results if we have a list that excludes 2 dates', async () => { + await importFile( + supertest, + 'date', + ['2020-10-01T05:08:53.000Z', '2020-10-03T05:08:53.000Z'], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + list: { + id: 'list_items.txt', + type: 'date', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql(['2020-10-01T05:08:53.000Z', '2020-10-03T05:08:53.000Z']); + }); + + it('will return 4 results if we have a list that excludes all dates', async () => { + await importFile( + supertest, + 'date', + [ + '2020-10-01T05:08:53.000Z', + '2020-10-02T05:08:53.000Z', + '2020-10-03T05:08:53.000Z', + '2020-10-04T05:08:53.000Z', + ], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['date']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'date', + list: { + id: 'list_items.txt', + type: 'date', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); + expect(hits).to.eql([ + '2020-10-01T05:08:53.000Z', + '2020-10-02T05:08:53.000Z', + '2020-10-03T05:08:53.000Z', + '2020-10-04T05:08:53.000Z', + ]); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/double.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/double.ts new file mode 100644 index 0000000000000..e29487880de6b --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/double.ts @@ -0,0 +1,744 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { + createListsIndex, + deleteAllExceptions, + deleteListsIndex, + importFile, +} from '../../../../lists_api_integration/utils'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { + createRule, + createRuleWithExceptionEntries, + createSignalsIndex, + deleteAllAlerts, + deleteSignalsIndex, + getRuleForSignalTesting, + getSignalsById, + waitForRuleSuccess, + waitForSignalsToBePresent, +} from '../../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + + describe('Rule exception operators for data type double', () => { + beforeEach(async () => { + await createSignalsIndex(supertest); + await createListsIndex(supertest); + await esArchiver.load('rule_exceptions/double'); + await esArchiver.load('rule_exceptions/double_as_string'); + }); + + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(supertest); + await deleteAllExceptions(es); + await deleteListsIndex(supertest); + await esArchiver.unload('rule_exceptions/double'); + await esArchiver.unload('rule_exceptions/double_as_string'); + }); + + describe('"is" operator', () => { + it('should find all the double from the data set when no exceptions are set on the rule', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); + }); + + it('should filter 1 single double if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'included', + type: 'match', + value: '1.0', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.1', '1.2', '1.3']); + }); + + it('should filter 2 double if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'included', + type: 'match', + value: '1.0', + }, + ], + [ + { + field: 'double', + operator: 'included', + type: 'match', + value: '1.1', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.2', '1.3']); + }); + + it('should filter 3 double if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'included', + type: 'match', + value: '1.0', + }, + ], + [ + { + field: 'double', + operator: 'included', + type: 'match', + value: '1.1', + }, + ], + [ + { + field: 'double', + operator: 'included', + type: 'match', + value: '1.2', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.3']); + }); + + it('should filter 4 double if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'included', + type: 'match', + value: '1.0', + }, + ], + [ + { + field: 'double', + operator: 'included', + type: 'match', + value: '1.1', + }, + ], + [ + { + field: 'double', + operator: 'included', + type: 'match', + value: '1.2', + }, + ], + [ + { + field: 'double', + operator: 'included', + type: 'match', + value: '1.3', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'excluded', + type: 'match', + value: '500.0', // this value is not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql([]); + }); + + it('will return just 1 result we excluded', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'excluded', + type: 'match', + value: '1.0', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.0']); + }); + + it('will return 0 results if we exclude two double', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'excluded', + type: 'match', + value: '1.0', + }, + ], + [ + { + field: 'double', + operator: 'excluded', + type: 'match', + value: '1.1', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is one of" operator', () => { + it('should filter 1 single double if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'included', + type: 'match_any', + value: ['1.0'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.1', '1.2', '1.3']); + }); + + it('should filter 2 double if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'included', + type: 'match_any', + value: ['1.0', '1.1'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.2', '1.3']); + }); + + it('should filter 3 double if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'included', + type: 'match_any', + value: ['1.0', '1.1', '1.2'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.3']); + }); + + it('should filter 4 double if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'included', + type: 'match_any', + value: ['1.0', '1.1', '1.2', '1.3'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not one of" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'excluded', + type: 'match_any', + value: ['500', '600'], // both these values are not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql([]); + }); + + it('will return just the result we excluded', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'excluded', + type: 'match_any', + value: ['1.0', '1.3'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.0', '1.3']); + }); + }); + + describe('"exists" operator', () => { + it('will return 0 results if matching against double', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'included', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"does not exist" operator', () => { + it('will return 4 results if matching against double', async () => { + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + operator: 'excluded', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); + }); + }); + + describe('"is in list" operator', () => { + // TODO: Enable this test once the bugs are fixed, we cannot use a list of strings that represent + // a double against an index that has the doubles stored as real doubles. + describe.skip('working against double values in the data set', () => { + it('will return 3 results if we have a list that includes 1 double', async () => { + await importFile(supertest, 'double', ['1.0'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.1', '1.2', '1.3']); + }); + + it('will return 2 results if we have a list that includes 2 double', async () => { + await importFile(supertest, 'double', ['1.0', '1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.1', '1.3']); + }); + + it('will return 0 results if we have a list that includes all double', async () => { + await importFile(supertest, 'double', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('working against string values in the data set', () => { + it('will return 3 results if we have a list that includes 1 double', async () => { + await importFile(supertest, 'double', ['1.0'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.1', '1.2', '1.3']); + }); + + it('will return 2 results if we have a list that includes 2 double', async () => { + await importFile(supertest, 'double', ['1.0', '1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.1', '1.3']); + }); + + it('will return 0 results if we have a list that includes all double', async () => { + await importFile(supertest, 'double', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql([]); + }); + + // TODO: Fix this bug and then unskip this test + it.skip('will return 1 result if we have a list which contains the double range of 1.0-1.2', async () => { + await importFile(supertest, 'double_range', ['1.0-1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(hits).to.eql(['1.3']); + }); + }); + }); + + describe('"is not in list" operator', () => { + // TODO: Enable this test once the bugs are fixed, we cannot use a list of strings that represent + // a double against an index that has the doubles stored as real doubles. + describe.skip('working against double values in the data set', () => { + it('will return 1 result if we have a list that excludes 1 double', async () => { + await importFile(supertest, 'double', ['1.0'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.0']); + }); + + it('will return 2 results if we have a list that excludes 2 double', async () => { + await importFile(supertest, 'double', ['1.0', '1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.0', '1.2']); + }); + + it('will return 4 results if we have a list that excludes all double', async () => { + await importFile(supertest, 'double', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); + }); + }); + + describe('working against string values in the data set', () => { + it('will return 1 result if we have a list that excludes 1 double', async () => { + await importFile(supertest, 'double', ['1.0'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.0']); + }); + + it('will return 2 results if we have a list that excludes 2 double', async () => { + await importFile(supertest, 'double', ['1.0', '1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.0', '1.2']); + }); + + it('will return 4 results if we have a list that excludes all double', async () => { + await importFile(supertest, 'double', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'double', + list: { + id: 'list_items.txt', + type: 'double', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); + expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); + }); + + // TODO: Fix this bug and then unskip this test + it.skip('will return 3 results if we have a list which contains the double range of 1.0-1.2', async () => { + await importFile(supertest, 'double_range', ['1.0-1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['double_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(hits).to.eql(['1.0', '1.1', '1.2']); + }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/float.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/float.ts new file mode 100644 index 0000000000000..d68f0f6a69277 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/float.ts @@ -0,0 +1,744 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { + createListsIndex, + deleteAllExceptions, + deleteListsIndex, + importFile, +} from '../../../../lists_api_integration/utils'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { + createRule, + createRuleWithExceptionEntries, + createSignalsIndex, + deleteAllAlerts, + deleteSignalsIndex, + getRuleForSignalTesting, + getSignalsById, + waitForRuleSuccess, + waitForSignalsToBePresent, +} from '../../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + + describe('Rule exception operators for data type float', () => { + beforeEach(async () => { + await createSignalsIndex(supertest); + await createListsIndex(supertest); + await esArchiver.load('rule_exceptions/float'); + await esArchiver.load('rule_exceptions/float_as_string'); + }); + + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(supertest); + await deleteAllExceptions(es); + await deleteListsIndex(supertest); + await esArchiver.unload('rule_exceptions/float'); + await esArchiver.unload('rule_exceptions/float_as_string'); + }); + + describe('"is" operator', () => { + it('should find all the float from the data set when no exceptions are set on the rule', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); + }); + + it('should filter 1 single float if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'included', + type: 'match', + value: '1.0', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.1', '1.2', '1.3']); + }); + + it('should filter 2 float if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'included', + type: 'match', + value: '1.0', + }, + ], + [ + { + field: 'float', + operator: 'included', + type: 'match', + value: '1.1', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.2', '1.3']); + }); + + it('should filter 3 float if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'included', + type: 'match', + value: '1.0', + }, + ], + [ + { + field: 'float', + operator: 'included', + type: 'match', + value: '1.1', + }, + ], + [ + { + field: 'float', + operator: 'included', + type: 'match', + value: '1.2', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.3']); + }); + + it('should filter 4 float if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'included', + type: 'match', + value: '1.0', + }, + ], + [ + { + field: 'float', + operator: 'included', + type: 'match', + value: '1.1', + }, + ], + [ + { + field: 'float', + operator: 'included', + type: 'match', + value: '1.2', + }, + ], + [ + { + field: 'float', + operator: 'included', + type: 'match', + value: '1.3', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'excluded', + type: 'match', + value: '500.0', // this value is not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql([]); + }); + + it('will return just 1 result we excluded', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'excluded', + type: 'match', + value: '1.0', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.0']); + }); + + it('will return 0 results if we exclude two float', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'excluded', + type: 'match', + value: '1.0', + }, + ], + [ + { + field: 'float', + operator: 'excluded', + type: 'match', + value: '1.1', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is one of" operator', () => { + it('should filter 1 single float if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'included', + type: 'match_any', + value: ['1.0'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.1', '1.2', '1.3']); + }); + + it('should filter 2 float if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'included', + type: 'match_any', + value: ['1.0', '1.1'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.2', '1.3']); + }); + + it('should filter 3 float if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'included', + type: 'match_any', + value: ['1.0', '1.1', '1.2'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.3']); + }); + + it('should filter 4 float if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'included', + type: 'match_any', + value: ['1.0', '1.1', '1.2', '1.3'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not one of" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'excluded', + type: 'match_any', + value: ['500', '600'], // both these values are not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql([]); + }); + + it('will return just the result we excluded', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'excluded', + type: 'match_any', + value: ['1.0', '1.3'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.0', '1.3']); + }); + }); + + describe('"exists" operator', () => { + it('will return 0 results if matching against float', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'included', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"does not exist" operator', () => { + it('will return 4 results if matching against float', async () => { + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + operator: 'excluded', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); + }); + }); + + describe('"is in list" operator', () => { + // TODO: Enable this test once the bugs are fixed, we cannot use a list of strings that represent + // a float against an index that has the floats stored as real floats. + describe.skip('working against float values in the data set', () => { + it('will return 3 results if we have a list that includes 1 float', async () => { + await importFile(supertest, 'float', ['1.0'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.1', '1.2', '1.3']); + }); + + it('will return 2 results if we have a list that includes 2 float', async () => { + await importFile(supertest, 'float', ['1.0', '1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.1', '1.3']); + }); + + it('will return 0 results if we have a list that includes all float', async () => { + await importFile(supertest, 'float', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('working against string values in the data set', () => { + it('will return 3 results if we have a list that includes 1 float', async () => { + await importFile(supertest, 'float', ['1.0'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.1', '1.2', '1.3']); + }); + + it('will return 2 results if we have a list that includes 2 float', async () => { + await importFile(supertest, 'float', ['1.0', '1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.1', '1.3']); + }); + + it('will return 0 results if we have a list that includes all float', async () => { + await importFile(supertest, 'float', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql([]); + }); + + // TODO: Fix this bug and then unskip this test + it.skip('will return 1 result if we have a list which contains the float range of 1.0-1.2', async () => { + await importFile(supertest, 'float_range', ['1.0-1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(hits).to.eql(['1.3']); + }); + }); + }); + + describe('"is not in list" operator', () => { + // TODO: Enable this test once the bugs are fixed, we cannot use a list of strings that represent + // a float against an index that has the floats stored as real floats. + describe.skip('working against float values in the data set', () => { + it('will return 1 result if we have a list that excludes 1 float', async () => { + await importFile(supertest, 'float', ['1.0'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.0']); + }); + + it('will return 2 results if we have a list that excludes 2 float', async () => { + await importFile(supertest, 'float', ['1.0', '1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.0', '1.2']); + }); + + it('will return 4 results if we have a list that excludes all float', async () => { + await importFile(supertest, 'float', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); + }); + }); + + describe('working against string values in the data set', () => { + it('will return 1 result if we have a list that excludes 1 float', async () => { + await importFile(supertest, 'float', ['1.0'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.0']); + }); + + it('will return 2 results if we have a list that excludes 2 float', async () => { + await importFile(supertest, 'float', ['1.0', '1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.0', '1.2']); + }); + + it('will return 4 results if we have a list that excludes all float', async () => { + await importFile(supertest, 'float', ['1.0', '1.1', '1.2', '1.3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'float', + list: { + id: 'list_items.txt', + type: 'float', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); + expect(hits).to.eql(['1.0', '1.1', '1.2', '1.3']); + }); + + // TODO: Fix this bug and then unskip this test + it.skip('will return 3 results if we have a list which contains the float range of 1.0-1.2', async () => { + await importFile(supertest, 'float_range', ['1.0-1.2'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['float_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(hits).to.eql(['1.0', '1.1', '1.2']); + }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/index.ts new file mode 100644 index 0000000000000..d2aca34e27399 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default ({ loadTestFile }: FtrProviderContext): void => { + describe('Detection exceptions data types and operators', function () { + this.tags('ciGroup1'); + + loadTestFile(require.resolve('./date')); + loadTestFile(require.resolve('./double')); + loadTestFile(require.resolve('./float')); + loadTestFile(require.resolve('./integer')); + loadTestFile(require.resolve('./ip')); + loadTestFile(require.resolve('./keyword')); + loadTestFile(require.resolve('./long')); + loadTestFile(require.resolve('./text')); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/integer.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/integer.ts new file mode 100644 index 0000000000000..9b38f0f7cbb42 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/integer.ts @@ -0,0 +1,744 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { + createListsIndex, + deleteAllExceptions, + deleteListsIndex, + importFile, +} from '../../../../lists_api_integration/utils'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { + createRule, + createRuleWithExceptionEntries, + createSignalsIndex, + deleteAllAlerts, + deleteSignalsIndex, + getRuleForSignalTesting, + getSignalsById, + waitForRuleSuccess, + waitForSignalsToBePresent, +} from '../../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + + describe('Rule exception operators for data type integer', () => { + beforeEach(async () => { + await createSignalsIndex(supertest); + await createListsIndex(supertest); + await esArchiver.load('rule_exceptions/integer'); + await esArchiver.load('rule_exceptions/integer_as_string'); + }); + + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(supertest); + await deleteAllExceptions(es); + await deleteListsIndex(supertest); + await esArchiver.unload('rule_exceptions/integer'); + await esArchiver.unload('rule_exceptions/integer_as_string'); + }); + + describe('"is" operator', () => { + it('should find all the integer from the data set when no exceptions are set on the rule', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['1', '2', '3', '4']); + }); + + it('should filter 1 single integer if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'included', + type: 'match', + value: '1', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['2', '3', '4']); + }); + + it('should filter 2 integer if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'included', + type: 'match', + value: '1', + }, + ], + [ + { + field: 'integer', + operator: 'included', + type: 'match', + value: '2', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['3', '4']); + }); + + it('should filter 3 integer if all 3 are as exceptions', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'included', + type: 'match', + value: '1', + }, + ], + [ + { + field: 'integer', + operator: 'included', + type: 'match', + value: '2', + }, + ], + [ + { + field: 'integer', + operator: 'included', + type: 'match', + value: '3', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['4']); + }); + + it('should filter 4 integer if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'included', + type: 'match', + value: '1', + }, + ], + [ + { + field: 'integer', + operator: 'included', + type: 'match', + value: '2', + }, + ], + [ + { + field: 'integer', + operator: 'included', + type: 'match', + value: '3', + }, + ], + [ + { + field: 'integer', + operator: 'included', + type: 'match', + value: '4', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'excluded', + type: 'match', + value: '500.0', // this value is not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql([]); + }); + + it('will return just 1 result we excluded', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'excluded', + type: 'match', + value: '1', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['1']); + }); + + it('will return 0 results if we exclude two integer', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'excluded', + type: 'match', + value: '1', + }, + ], + [ + { + field: 'integer', + operator: 'excluded', + type: 'match', + value: '2', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is one of" operator', () => { + it('should filter 1 single integer if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'included', + type: 'match_any', + value: ['1'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['2', '3', '4']); + }); + + it('should filter 2 integer if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'included', + type: 'match_any', + value: ['1', '2'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['3', '4']); + }); + + it('should filter 3 integer if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'included', + type: 'match_any', + value: ['1', '2', '3'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['4']); + }); + + it('should filter 4 integer if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'included', + type: 'match_any', + value: ['1', '2', '3', '4'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not one of" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'excluded', + type: 'match_any', + value: ['500', '600'], // both these values are not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql([]); + }); + + it('will return just the result we excluded', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'excluded', + type: 'match_any', + value: ['1', '4'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['1', '4']); + }); + }); + + describe('"exists" operator', () => { + it('will return 0 results if matching against integer', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'included', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"does not exist" operator', () => { + it('will return 4 results if matching against integer', async () => { + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + operator: 'excluded', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['1', '2', '3', '4']); + }); + }); + + describe('"is in list" operator', () => { + // TODO: Enable this test once the bugs are fixed, we cannot use a list of strings that represent + // a integer against an index that has the integers stored as real integers. + describe.skip('working against integer values in the data set', () => { + it('will return 3 results if we have a list that includes 1 integer', async () => { + await importFile(supertest, 'integer', ['1'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['2', '3', '4']); + }); + + it('will return 2 results if we have a list that includes 2 integer', async () => { + await importFile(supertest, 'integer', ['1', '3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['2', '4']); + }); + + it('will return 0 results if we have a list that includes all integer', async () => { + await importFile(supertest, 'integer', ['1', '2', '3', '4'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('working against string values in the data set', () => { + it('will return 3 results if we have a list that includes 1 integer', async () => { + await importFile(supertest, 'integer', ['1'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['2', '3', '4']); + }); + + it('will return 2 results if we have a list that includes 2 integer', async () => { + await importFile(supertest, 'integer', ['1', '3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['2', '4']); + }); + + it('will return 0 results if we have a list that includes all integer', async () => { + await importFile(supertest, 'integer', ['1', '2', '3', '4'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql([]); + }); + + // TODO: Fix this bug and then unskip this test + it.skip('will return 1 result if we have a list which contains the integer range of 1-3', async () => { + await importFile(supertest, 'integer_range', ['1-3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(hits).to.eql(['4']); + }); + }); + }); + + describe('"is not in list" operator', () => { + // TODO: Enable this test once the bugs are fixed, we cannot use a list of strings that represent + // a integer against an index that has the integers stored as real integers. + describe.skip('working against integer values in the data set', () => { + it('will return 1 result if we have a list that excludes 1 integer', async () => { + await importFile(supertest, 'integer', ['1'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['1']); + }); + + it('will return 2 results if we have a list that excludes 2 integer', async () => { + await importFile(supertest, 'integer', ['1', '3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['1', '3']); + }); + + it('will return 4 results if we have a list that excludes all integer', async () => { + await importFile(supertest, 'integer', ['1', '2', '3', '4'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['1', '2', '3', '4']); + }); + }); + + describe('working against string values in the data set', () => { + it('will return 1 result if we have a list that excludes 1 integer', async () => { + await importFile(supertest, 'integer', ['1'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['1']); + }); + + it('will return 2 results if we have a list that excludes 2 integer', async () => { + await importFile(supertest, 'integer', ['1', '3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['1', '3']); + }); + + it('will return 4 results if we have a list that excludes all integer', async () => { + await importFile(supertest, 'integer', ['1', '2', '3', '4'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'integer', + list: { + id: 'list_items.txt', + type: 'integer', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); + expect(hits).to.eql(['1', '2', '3', '4']); + }); + + // TODO: Fix this bug and then unskip this test + it.skip('will return 3 results if we have a list which contains the integer range of 1-3', async () => { + await importFile(supertest, 'integer_range', ['1-3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['integer_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(hits).to.eql(['1', '2', '3']); + }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/ip.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/ip.ts new file mode 100644 index 0000000000000..c3537efc12de7 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/ip.ts @@ -0,0 +1,622 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { + createListsIndex, + deleteAllExceptions, + deleteListsIndex, + importFile, +} from '../../../../lists_api_integration/utils'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { + createRule, + createRuleWithExceptionEntries, + createSignalsIndex, + deleteAllAlerts, + deleteSignalsIndex, + getRuleForSignalTesting, + getSignalsById, + waitForRuleSuccess, + waitForSignalsToBePresent, +} from '../../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + + describe('Rule exception operators for data type ip', () => { + beforeEach(async () => { + await createSignalsIndex(supertest); + await createListsIndex(supertest); + await esArchiver.load('rule_exceptions/ip'); + }); + + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(supertest); + await deleteAllExceptions(es); + await deleteListsIndex(supertest); + await esArchiver.unload('rule_exceptions/ip'); + }); + + describe('"is" operator', () => { + it('should find all the ips from the data set when no exceptions are set on the rule', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4']); + }); + + it('should filter 1 single ip if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'included', + type: 'match', + value: '127.0.0.1', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.2', '127.0.0.3', '127.0.0.4']); + }); + + it('should filter 2 ips if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'included', + type: 'match', + value: '127.0.0.1', + }, + ], + [ + { + field: 'ip', + operator: 'included', + type: 'match', + value: '127.0.0.2', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.3', '127.0.0.4']); + }); + + it('should filter 3 ips if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'included', + type: 'match', + value: '127.0.0.1', + }, + ], + [ + { + field: 'ip', + operator: 'included', + type: 'match', + value: '127.0.0.2', + }, + ], + [ + { + field: 'ip', + operator: 'included', + type: 'match', + value: '127.0.0.3', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.4']); + }); + + it('should filter 4 ips if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'included', + type: 'match', + value: '127.0.0.1', + }, + ], + [ + { + field: 'ip', + operator: 'included', + type: 'match', + value: '127.0.0.2', + }, + ], + [ + { + field: 'ip', + operator: 'included', + type: 'match', + value: '127.0.0.3', + }, + ], + [ + { + field: 'ip', + operator: 'included', + type: 'match', + value: '127.0.0.4', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql([]); + }); + + it('should filter a CIDR range of 127.0.0.1/30', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'included', + type: 'match', + value: '127.0.0.1/30', // CIDR IP Range is 127.0.0.0 - 127.0.0.3 + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.4']); + }); + }); + + describe('"is not" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'excluded', + type: 'match', + value: '192.168.0.1', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql([]); + }); + + it('will return just 1 result we excluded', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'excluded', + type: 'match', + value: '127.0.0.1', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.1']); + }); + + it('will return 0 results if we exclude two ips', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'excluded', + type: 'match', + value: '127.0.0.1', + }, + ], + [ + { + field: 'ip', + operator: 'excluded', + type: 'match', + value: '127.0.0.2', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql([]); + }); + }); + + describe('"is one of" operator', () => { + it('should filter 1 single ip if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'included', + type: 'match_any', + value: ['127.0.0.1'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.2', '127.0.0.3', '127.0.0.4']); + }); + + it('should filter 2 ips if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'included', + type: 'match_any', + value: ['127.0.0.1', '127.0.0.2'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.3', '127.0.0.4']); + }); + + it('should filter 3 ips if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'included', + type: 'match_any', + value: ['127.0.0.1', '127.0.0.2', '127.0.0.3'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.4']); + }); + + it('should filter 4 ips if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'included', + type: 'match_any', + value: ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql([]); + }); + }); + + describe('"is not one of" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'excluded', + type: 'match_any', + value: ['192.168.0.1', '192.168.0.2'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql([]); + }); + + it('will return just the result we excluded', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'excluded', + type: 'match_any', + value: ['127.0.0.1', '127.0.0.4'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.1', '127.0.0.4']); + }); + }); + + describe('"exists" operator', () => { + it('will return 0 results if matching against ip', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'included', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql([]); + }); + }); + + describe('"does not exist" operator', () => { + it('will return 4 results if matching against ip', async () => { + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + operator: 'excluded', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4']); + }); + }); + + describe('"is in list" operator', () => { + it('will return 3 results if we have a list that includes 1 ip', async () => { + await importFile(supertest, 'ip', ['127.0.0.1'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.2', '127.0.0.3', '127.0.0.4']); + }); + + it('will return 2 results if we have a list that includes 2 ips', async () => { + await importFile(supertest, 'ip', ['127.0.0.1', '127.0.0.3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.2', '127.0.0.4']); + }); + + it('will return 0 results if we have a list that includes all ips', async () => { + await importFile( + supertest, + 'ip', + ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql([]); + }); + + // TODO: Fix this bug and then unskip this test + it.skip('will return 1 result if we have a list which contains the CIDR range of 127.0.0.1/30', async () => { + await importFile(supertest, 'ip_range', ['127.0.0.1/30'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.4']); + }); + }); + + describe('"is not in list" operator', () => { + it('will return 1 result if we have a list that excludes 1 ip', async () => { + await importFile(supertest, 'ip', ['127.0.0.1'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.1']); + }); + + it('will return 2 results if we have a list that excludes 2 ips', async () => { + await importFile(supertest, 'ip', ['127.0.0.1', '127.0.0.3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.1', '127.0.0.3']); + }); + + it('will return 4 results if we have a list that excludes all ips', async () => { + await importFile( + supertest, + 'ip', + ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4']); + }); + + // TODO: Fix this bug and then unskip this test + it.skip('will return 3 results if we have a list which contains the CIDR range of 127.0.0.1/30', async () => { + await importFile(supertest, 'ip_range', ['127.0.0.1/30'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['ip']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(ips).to.eql(['127.0.0.1', '127.0.0.2', '127.0.0.3']); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/keyword.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/keyword.ts new file mode 100644 index 0000000000000..0c227c9acc38c --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/keyword.ts @@ -0,0 +1,555 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { + createListsIndex, + deleteAllExceptions, + deleteListsIndex, + importFile, +} from '../../../../lists_api_integration/utils'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { + createRule, + createRuleWithExceptionEntries, + createSignalsIndex, + deleteAllAlerts, + deleteSignalsIndex, + getRuleForSignalTesting, + getSignalsById, + waitForRuleSuccess, + waitForSignalsToBePresent, +} from '../../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + + describe('Rule exception operators for data type keyword', () => { + beforeEach(async () => { + await createSignalsIndex(supertest); + await createListsIndex(supertest); + await esArchiver.load('rule_exceptions/keyword'); + }); + + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(supertest); + await deleteAllExceptions(es); + await deleteListsIndex(supertest); + await esArchiver.unload('rule_exceptions/keyword'); + }); + + describe('"is" operator', () => { + it('should find all the keyword from the data set when no exceptions are set on the rule', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); + }); + + it('should filter 1 single keyword if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'included', + type: 'match', + value: 'word one', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four', 'word three', 'word two']); + }); + + it('should filter 2 keyword if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'included', + type: 'match', + value: 'word one', + }, + ], + [ + { + field: 'keyword', + operator: 'included', + type: 'match', + value: 'word two', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four', 'word three']); + }); + + it('should filter 3 keyword if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'included', + type: 'match', + value: 'word one', + }, + ], + [ + { + field: 'keyword', + operator: 'included', + type: 'match', + value: 'word two', + }, + ], + [ + { + field: 'keyword', + operator: 'included', + type: 'match', + value: 'word three', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four']); + }); + + it('should filter 4 keyword if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'included', + type: 'match', + value: 'word one', + }, + ], + [ + { + field: 'keyword', + operator: 'included', + type: 'match', + value: 'word two', + }, + ], + [ + { + field: 'keyword', + operator: 'included', + type: 'match', + value: 'word three', + }, + ], + [ + { + field: 'keyword', + operator: 'included', + type: 'match', + value: 'word four', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'excluded', + type: 'match', + value: '500.0', // this value is not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql([]); + }); + + it('will return just 1 result we excluded', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'excluded', + type: 'match', + value: 'word one', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word one']); + }); + + it('will return 0 results if we exclude two keyword', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'excluded', + type: 'match', + value: 'word one', + }, + ], + [ + { + field: 'keyword', + operator: 'excluded', + type: 'match', + value: 'word two', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is one of" operator', () => { + it('should filter 1 single keyword if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'included', + type: 'match_any', + value: ['word one'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four', 'word three', 'word two']); + }); + + it('should filter 2 keyword if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'included', + type: 'match_any', + value: ['word one', 'word two'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four', 'word three']); + }); + + it('should filter 3 keyword if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'included', + type: 'match_any', + value: ['word one', 'word three', 'word two'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four']); + }); + + it('should filter 4 keyword if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'included', + type: 'match_any', + value: ['word four', 'word one', 'word three', 'word two'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not one of" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'excluded', + type: 'match_any', + value: ['500', '600'], // both these values are not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql([]); + }); + + it('will return just the result we excluded', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'excluded', + type: 'match_any', + value: ['word one', 'word four'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four', 'word one']); + }); + }); + + describe('"exists" operator', () => { + it('will return 0 results if matching against keyword', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'included', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"does not exist" operator', () => { + it('will return 4 results if matching against keyword', async () => { + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + operator: 'excluded', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); + }); + }); + + describe('"is in list" operator', () => { + it('will return 3 results if we have a list that includes 1 keyword', async () => { + await importFile(supertest, 'keyword', ['word one'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + list: { + id: 'list_items.txt', + type: 'keyword', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four', 'word three', 'word two']); + }); + + it('will return 2 results if we have a list that includes 2 keyword', async () => { + await importFile(supertest, 'keyword', ['word one', 'word three'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + list: { + id: 'list_items.txt', + type: 'keyword', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four', 'word two']); + }); + + it('will return 0 results if we have a list that includes all keyword', async () => { + await importFile( + supertest, + 'keyword', + ['word one', 'word two', 'word three', 'word four'], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + list: { + id: 'list_items.txt', + type: 'keyword', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not in list" operator', () => { + it('will return 1 result if we have a list that excludes 1 keyword', async () => { + await importFile(supertest, 'keyword', ['word one'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + list: { + id: 'list_items.txt', + type: 'keyword', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word one']); + }); + + it('will return 2 results if we have a list that excludes 2 keyword', async () => { + await importFile(supertest, 'keyword', ['word one', 'word three'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + list: { + id: 'list_items.txt', + type: 'keyword', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word one', 'word three']); + }); + + it('will return 4 results if we have a list that excludes all keyword', async () => { + await importFile( + supertest, + 'keyword', + ['word one', 'word two', 'word three', 'word four'], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['keyword']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'keyword', + list: { + id: 'list_items.txt', + type: 'keyword', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); + expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/long.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/long.ts new file mode 100644 index 0000000000000..5c110996c2198 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/long.ts @@ -0,0 +1,744 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { + createListsIndex, + deleteAllExceptions, + deleteListsIndex, + importFile, +} from '../../../../lists_api_integration/utils'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { + createRule, + createRuleWithExceptionEntries, + createSignalsIndex, + deleteAllAlerts, + deleteSignalsIndex, + getRuleForSignalTesting, + getSignalsById, + waitForRuleSuccess, + waitForSignalsToBePresent, +} from '../../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + + describe('Rule exception operators for data type long', () => { + beforeEach(async () => { + await createSignalsIndex(supertest); + await createListsIndex(supertest); + await esArchiver.load('rule_exceptions/long'); + await esArchiver.load('rule_exceptions/long_as_string'); + }); + + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(supertest); + await deleteAllExceptions(es); + await deleteListsIndex(supertest); + await esArchiver.unload('rule_exceptions/long'); + await esArchiver.unload('rule_exceptions/long_as_string'); + }); + + describe('"is" operator', () => { + it('should find all the long from the data set when no exceptions are set on the rule', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['1', '2', '3', '4']); + }); + + it('should filter 1 single long if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'included', + type: 'match', + value: '1', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['2', '3', '4']); + }); + + it('should filter 2 long if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'included', + type: 'match', + value: '1', + }, + ], + [ + { + field: 'long', + operator: 'included', + type: 'match', + value: '2', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['3', '4']); + }); + + it('should filter 3 long if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'included', + type: 'match', + value: '1', + }, + ], + [ + { + field: 'long', + operator: 'included', + type: 'match', + value: '2', + }, + ], + [ + { + field: 'long', + operator: 'included', + type: 'match', + value: '3', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['4']); + }); + + it('should filter 4 long if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'included', + type: 'match', + value: '1', + }, + ], + [ + { + field: 'long', + operator: 'included', + type: 'match', + value: '2', + }, + ], + [ + { + field: 'long', + operator: 'included', + type: 'match', + value: '3', + }, + ], + [ + { + field: 'long', + operator: 'included', + type: 'match', + value: '4', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'excluded', + type: 'match', + value: '500.0', // this value is not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql([]); + }); + + it('will return just 1 result we excluded', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'excluded', + type: 'match', + value: '1', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['1']); + }); + + it('will return 0 results if we exclude two long', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'excluded', + type: 'match', + value: '1', + }, + ], + [ + { + field: 'long', + operator: 'excluded', + type: 'match', + value: '2', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is one of" operator', () => { + it('should filter 1 single long if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'included', + type: 'match_any', + value: ['1'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['2', '3', '4']); + }); + + it('should filter 2 long if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'included', + type: 'match_any', + value: ['1', '2'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['3', '4']); + }); + + it('should filter 3 long if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'included', + type: 'match_any', + value: ['1', '2', '3'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['4']); + }); + + it('should filter 4 long if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'included', + type: 'match_any', + value: ['1', '2', '3', '4'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not one of" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'excluded', + type: 'match_any', + value: ['500', '600'], // both these values are not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql([]); + }); + + it('will return just the result we excluded', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'excluded', + type: 'match_any', + value: ['1', '4'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['1', '4']); + }); + }); + + describe('"exists" operator', () => { + it('will return 0 results if matching against long', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'included', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"does not exist" operator', () => { + it('will return 4 results if matching against long', async () => { + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + operator: 'excluded', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['1', '2', '3', '4']); + }); + }); + + describe('"is in list" operator', () => { + // TODO: Enable this test once the bugs are fixed, we cannot use a list of strings that represent + // a long against an index that has the longs stored as real longs. + describe.skip('working against long values in the data set', () => { + it('will return 3 results if we have a list that includes 1 long', async () => { + await importFile(supertest, 'long', ['1'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['2', '3', '4']); + }); + + it('will return 2 results if we have a list that includes 2 long', async () => { + await importFile(supertest, 'long', ['1', '3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['2', '4']); + }); + + it('will return 0 results if we have a list that includes all long', async () => { + await importFile(supertest, 'long', ['1', '2', '3', '4'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('working against string values in the data set', () => { + it('will return 3 results if we have a list that includes 1 long', async () => { + await importFile(supertest, 'long', ['1'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['2', '3', '4']); + }); + + it('will return 2 results if we have a list that includes 2 long', async () => { + await importFile(supertest, 'long', ['1', '3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['2', '4']); + }); + + it('will return 0 results if we have a list that includes all long', async () => { + await importFile(supertest, 'long', ['1', '2', '3', '4'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql([]); + }); + + // TODO: Fix this bug and then unskip this test + it.skip('will return 1 result if we have a list which contains the long range of 1-3', async () => { + await importFile(supertest, 'long_range', ['1-3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(hits).to.eql(['4']); + }); + }); + }); + + describe('"is not in list" operator', () => { + // TODO: Enable this test once the bugs are fixed, we cannot use a list of strings that represent + // a long against an index that has the longs stored as real longs. + describe.skip('working against long values in the data set', () => { + it('will return 1 result if we have a list that excludes 1 long', async () => { + await importFile(supertest, 'long', ['1'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['1']); + }); + + it('will return 2 results if we have a list that excludes 2 long', async () => { + await importFile(supertest, 'long', ['1', '3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['1', '3']); + }); + + it('will return 4 results if we have a list that excludes all long', async () => { + await importFile(supertest, 'long', ['1', '2', '3', '4'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['1', '2', '3', '4']); + }); + }); + + describe('working against string values in the data set', () => { + it('will return 1 result if we have a list that excludes 1 long', async () => { + await importFile(supertest, 'long', ['1'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['1']); + }); + + it('will return 2 results if we have a list that excludes 2 long', async () => { + await importFile(supertest, 'long', ['1', '3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['1', '3']); + }); + + it('will return 4 results if we have a list that excludes all long', async () => { + await importFile(supertest, 'long', ['1', '2', '3', '4'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'long', + list: { + id: 'list_items.txt', + type: 'long', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); + expect(hits).to.eql(['1', '2', '3', '4']); + }); + + // TODO: Fix this bug and then unskip this test + it.skip('will return 3 results if we have a list which contains the long range of 1-3', async () => { + await importFile(supertest, 'long_range', ['1-3'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['long_as_string']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'ip', + list: { + id: 'list_items.txt', + type: 'ip', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); + expect(hits).to.eql(['1', '2', '3']); + }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/text.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/text.ts new file mode 100644 index 0000000000000..d2066b1023d3c --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/text.ts @@ -0,0 +1,827 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { + createListsIndex, + deleteAllExceptions, + deleteListsIndex, + importFile, + importTextFile, +} from '../../../../lists_api_integration/utils'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { + createRule, + createRuleWithExceptionEntries, + createSignalsIndex, + deleteAllAlerts, + deleteSignalsIndex, + getRuleForSignalTesting, + getSignalsById, + waitForRuleSuccess, + waitForSignalsToBePresent, +} from '../../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + + describe('Rule exception operators for data type text', () => { + beforeEach(async () => { + await createSignalsIndex(supertest); + await createListsIndex(supertest); + await esArchiver.load('rule_exceptions/text'); + await esArchiver.load('rule_exceptions/text_no_spaces'); + }); + + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(supertest); + await deleteAllExceptions(es); + await deleteListsIndex(supertest); + await esArchiver.unload('rule_exceptions/text'); + await esArchiver.unload('rule_exceptions/text_no_spaces'); + }); + + describe('"is" operator', () => { + it('should find all the text from the data set when no exceptions are set on the rule', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); + }); + + it('should filter 1 single text if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'word one', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word three', 'word two']); + }); + + it('should filter 2 text if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'word one', + }, + ], + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'word two', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word three']); + }); + + it('should filter 3 text if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'word one', + }, + ], + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'word two', + }, + ], + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'word three', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four']); + }); + + it('should filter 4 text if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'word one', + }, + ], + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'word two', + }, + ], + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'word three', + }, + ], + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'word four', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql([]); + }); + + it('should filter 1 single text using a single word', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'one', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word three', 'word two']); + }); + + it('should filter all words using a common piece of text', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'word', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql([]); + }); + + it('should filter 1 single text with punctuation added', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'match', + value: 'one.', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word three', 'word two']); + }); + }); + + describe('"is not" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'excluded', + type: 'match', + value: '500.0', // this value is not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql([]); + }); + + it('will return just 1 result we excluded', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'excluded', + type: 'match', + value: 'word one', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word one']); + }); + + it('will return 0 results if we exclude two text', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'excluded', + type: 'match', + value: 'word one', + }, + ], + [ + { + field: 'text', + operator: 'excluded', + type: 'match', + value: 'word two', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql([]); + }); + + it('should filter 1 single text using a single word', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'excluded', + type: 'match', + value: 'one', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word one']); + }); + + it('should filter all words using a common piece of text', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'excluded', + type: 'match', + value: 'word', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); + }); + + it('should filter 1 single text with punctuation added', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'excluded', + type: 'match', + value: 'one.', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word one']); + }); + }); + + describe('"is one of" operator', () => { + it('should filter 1 single text if it is set as an exception', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'match_any', + value: ['word one'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word three', 'word two']); + }); + + it('should filter 2 text if both are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'match_any', + value: ['word one', 'word two'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word three']); + }); + + it('should filter 3 text if all 3 are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'match_any', + value: ['word one', 'word three', 'word two'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four']); + }); + + it('should filter 4 text if all are set as exceptions', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'match_any', + value: ['word four', 'word one', 'word three', 'word two'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"is not one of" operator', () => { + it('will return 0 results if it cannot find what it is excluding', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'excluded', + type: 'match_any', + value: ['500', '600'], // both these values are not in the data set + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql([]); + }); + + it('will return just the result we excluded', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'excluded', + type: 'match_any', + value: ['word one', 'word four'], + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word one']); + }); + }); + + describe('"exists" operator', () => { + it('will return 0 results if matching against text', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'included', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql([]); + }); + }); + + describe('"does not exist" operator', () => { + it('will return 4 results if matching against text', async () => { + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + operator: 'excluded', + type: 'exists', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); + }); + }); + + describe('"is in list" operator', () => { + describe('working against text values without spaces', () => { + it('will return 3 results if we have a list that includes 1 text', async () => { + await importFile(supertest, 'text', ['one'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['text_no_spaces']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 3, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['four', 'three', 'two']); + }); + + it('will return 2 results if we have a list that includes 2 text', async () => { + await importFile(supertest, 'text', ['one', 'three'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['text_no_spaces']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['four', 'two']); + }); + + it('will return 0 results if we have a list that includes all text', async () => { + await importTextFile( + supertest, + 'text', + ['one', 'two', 'three', 'four'], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['text_no_spaces']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql([]); + }); + }); + + // TODO: Unskip these once this is fixed + describe.skip('working against text values with spaces', () => { + it('will return 3 results if we have a list that includes 1 text', async () => { + await importFile(supertest, 'text', ['one'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word three', 'word two']); + }); + + it('will return 2 results if we have a list that includes 2 text', async () => { + await importFile(supertest, 'text', ['one', 'three'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word two']); + }); + + it('will return 0 results if we have a list that includes all text', async () => { + await importTextFile( + supertest, + 'text', + ['one', 'two', 'three', 'four'], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'included', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql([]); + }); + }); + }); + + describe('"is not in list" operator', () => { + describe('working against text values without spaces', () => { + it('will return 1 result if we have a list that excludes 1 text', async () => { + await importTextFile(supertest, 'text', ['one'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['text_no_spaces']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['one']); + }); + + it('will return 2 results if we have a list that excludes 2 text', async () => { + await importTextFile(supertest, 'text', ['one', 'three'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['text_no_spaces']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 2, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['one', 'three']); + }); + + it('will return 4 results if we have a list that excludes all text', async () => { + await importTextFile( + supertest, + 'text', + ['one', 'two', 'three', 'four'], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['text_no_spaces']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 4, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['four', 'one', 'three', 'two']); + }); + }); + + // TODO: Unskip these once this is fixed + describe.skip('working against text values with spaces', () => { + it('will return 1 result if we have a list that excludes 1 text', async () => { + await importTextFile(supertest, 'text', ['one'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word one']); + }); + + it('will return 2 results if we have a list that excludes 2 text', async () => { + await importTextFile(supertest, 'text', ['one', 'three'], 'list_items.txt'); + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word one', 'word three']); + }); + + it('will return 4 results if we have a list that excludes all text', async () => { + await importTextFile( + supertest, + 'text', + ['one', 'two', 'three', 'four'], + 'list_items.txt' + ); + const rule = getRuleForSignalTesting(['text']); + const { id } = await createRuleWithExceptionEntries(supertest, rule, [ + [ + { + field: 'text', + list: { + id: 'list_items.txt', + type: 'text', + }, + operator: 'excluded', + type: 'list', + }, + ], + ]); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsById(supertest, id); + const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); + expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); + }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/export_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/export_rules.ts index 2610796bdc384..4f76a0544a152 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/export_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/export_rules.ts @@ -22,7 +22,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('export_rules', () => { describe('exporting rules', () => { @@ -32,7 +31,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should set the response content types to be expected', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_rules.ts index f496d035d8e60..2f06a84c7223b 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_rules.ts @@ -23,7 +23,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('find_rules', () => { beforeEach(async () => { @@ -32,7 +31,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should return an empty find body correctly if no rules are loaded', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts index fac1fbaaf9675..8bb4c45d91bdd 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts @@ -30,7 +30,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await deleteAllRulesStatuses(es); }); @@ -64,7 +64,7 @@ export default ({ getService }: FtrProviderContext): void => { this pops up again elsewhere. */ it('should return a single rule status when a single rule is loaded from a find status with defaults added', async () => { - const resBody = await createRule(supertest, getSimpleRule()); + const resBody = await createRule(supertest, getSimpleRule('rule-1', true)); await waitForRuleSuccess(supertest, resBody.id); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts index f76bdb4ebc718..0db3013503a33 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts @@ -17,9 +17,11 @@ import { createSignalsIndex, deleteAllAlerts, deleteSignalsIndex, - getAllSignals, + getRuleForSignalTesting, + getSignalsByIds, getSignalsByRuleIds, getSimpleRule, + waitForRuleSuccess, waitForSignalsToBePresent, } from '../../utils'; @@ -33,17 +35,15 @@ export const ID = 'BhbXBmkBR346wHgn4PeZ'; export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - const es = getService('es'); describe('Generating signals from source indexes', () => { beforeEach(async () => { - await deleteAllAlerts(es); await createSignalsIndex(supertest); }); afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); describe('Signals from audit beat are of the expected structure', () => { @@ -57,37 +57,37 @@ export default ({ getService }: FtrProviderContext) => { it('should have the specific audit record for _id or none of these tests below will pass', async () => { const rule: QueryCreateSchema = { - ...getSimpleRule(), - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['auditbeat-*']), query: `_id:${ID}`, }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); - const signalsOpen = await getAllSignals(supertest); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits.length).greaterThan(0); }); it('should have recorded the rule_id within the signal', async () => { const rule: QueryCreateSchema = { - ...getSimpleRule(), - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['auditbeat-*']), query: `_id:${ID}`, }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); - const signalsOpen = await getAllSignals(supertest); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits[0]._source.signal.rule.rule_id).eql(getSimpleRule().rule_id); }); it('should query and get back expected signal structure using a basic KQL query', async () => { const rule: QueryCreateSchema = { - ...getSimpleRule(), - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['auditbeat-*']), query: `_id:${ID}`, }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); - const signalsOpen = await getAllSignals(supertest); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); // remove rule to cut down on touch points for test changes when the rule format changes const { rule: removedRule, ...signalNoRule } = signalsOpen.hits.hits[0]._source.signal; expect(signalNoRule).eql({ @@ -126,25 +126,23 @@ export default ({ getService }: FtrProviderContext) => { }); it('should query and get back expected signal structure when it is a signal on a signal', async () => { - // create a 1 signal from 1 auditbeat record const rule: QueryCreateSchema = { - ...getSimpleRule(), - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['auditbeat-*']), query: `_id:${ID}`, }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); + const { id: createdId } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, createdId); + await waitForSignalsToBePresent(supertest, 1, [createdId]); // Run signals on top of that 1 signal which should create a single signal (on top of) a signal const ruleForSignals: QueryCreateSchema = { - ...getSimpleRule(), + ...getRuleForSignalTesting([`${DEFAULT_SIGNALS_INDEX}*`]), rule_id: 'signal-on-signal', - index: [`${DEFAULT_SIGNALS_INDEX}*`], - from: '1900-01-01T00:00:00.000Z', - query: '*:*', }; - await createRule(supertest, ruleForSignals); - await waitForSignalsToBePresent(supertest, 2); + + const { id } = await createRule(supertest, ruleForSignals); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); // Get our single signal on top of a signal const signalsOpen = await getSignalsByRuleIds(supertest, ['signal-on-signal']); @@ -198,15 +196,15 @@ export default ({ getService }: FtrProviderContext) => { describe('EQL Rules', () => { it('generates signals from EQL sequences in the expected form', async () => { const rule: EqlCreateSchema = { - ...getSimpleRule(), - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['auditbeat-*']), rule_id: 'eql-rule', type: 'eql', language: 'eql', query: 'sequence by host.name [any where true] [any where true]', }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); const signals = await getSignalsByRuleIds(supertest, ['eql-rule']); const signal = signals.hits.hits[0]._source.signal; @@ -250,15 +248,15 @@ export default ({ getService }: FtrProviderContext) => { it('generates building block signals from EQL sequences in the expected form', async () => { const rule: EqlCreateSchema = { - ...getSimpleRule(), - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['auditbeat-*']), rule_id: 'eql-rule', type: 'eql', language: 'eql', query: 'sequence by host.name [any where true] [any where true]', }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsByRuleIds(supertest, ['eql-rule']); const sequenceSignal = signalsOpen.hits.hits.find( (signal) => signal._source.signal.depth === 2 @@ -337,40 +335,39 @@ export default ({ getService }: FtrProviderContext) => { it('should have the specific audit record for _id or none of these tests below will pass', async () => { const rule: QueryCreateSchema = { - ...getSimpleRule(), - index: ['signal_name_clash'], - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['signal_name_clash']), query: '_id:1', }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); - const signalsOpen = await getAllSignals(supertest); + + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits.length).greaterThan(0); }); it('should have recorded the rule_id within the signal', async () => { const rule: QueryCreateSchema = { - ...getSimpleRule(), - index: ['signal_name_clash'], - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['signal_name_clash']), query: '_id:1', }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); - const signalsOpen = await getAllSignals(supertest); + + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits[0]._source.signal.rule.rule_id).eql(getSimpleRule().rule_id); }); it('should query and get back expected signal structure using a basic KQL query', async () => { const rule: QueryCreateSchema = { - ...getSimpleRule(), - index: ['signal_name_clash'], - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['signal_name_clash']), query: '_id:1', }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); - const signalsOpen = await getAllSignals(supertest); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); // remove rule to cut down on touch points for test changes when the rule format changes const { rule: removedRule, ...signalNoRule } = signalsOpen.hits.hits[0]._source.signal; expect(signalNoRule).eql({ @@ -404,26 +401,22 @@ export default ({ getService }: FtrProviderContext) => { }); it('should query and get back expected signal structure when it is a signal on a signal', async () => { - // create a 1 signal from 1 auditbeat record const rule: QueryCreateSchema = { - ...getSimpleRule(), - index: ['signal_name_clash'], - from: '1900-01-01T00:00:00.000Z', - query: `_id:1`, + ...getRuleForSignalTesting(['signal_name_clash']), + query: '_id:1', }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); // Run signals on top of that 1 signal which should create a single signal (on top of) a signal const ruleForSignals: QueryCreateSchema = { - ...getSimpleRule(), + ...getRuleForSignalTesting([`${DEFAULT_SIGNALS_INDEX}*`]), rule_id: 'signal-on-signal', - index: [`${DEFAULT_SIGNALS_INDEX}*`], - from: '1900-01-01T00:00:00.000Z', - query: '*:*', }; - await createRule(supertest, ruleForSignals); - await waitForSignalsToBePresent(supertest, 2); + const { id: createdId } = await createRule(supertest, ruleForSignals); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [createdId]); // Get our single signal on top of a signal const signalsOpen = await getSignalsByRuleIds(supertest, ['signal-on-signal']); @@ -479,7 +472,7 @@ export default ({ getService }: FtrProviderContext) => { * You should see the "signal" object/clash being copied to "original_signal" underneath * the signal object and no errors when they do have a clash. */ - describe('Signals generated from name clashes', () => { + describe('Signals generated from object clashes', () => { beforeEach(async () => { await esArchiver.load('signals/object_clash'); }); @@ -490,40 +483,37 @@ export default ({ getService }: FtrProviderContext) => { it('should have the specific audit record for _id or none of these tests below will pass', async () => { const rule: QueryCreateSchema = { - ...getSimpleRule(), - index: ['signal_object_clash'], - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['signal_object_clash']), query: '_id:1', }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); - const signalsOpen = await getAllSignals(supertest); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits.length).greaterThan(0); }); it('should have recorded the rule_id within the signal', async () => { const rule: QueryCreateSchema = { - ...getSimpleRule(), - index: ['signal_object_clash'], - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['signal_object_clash']), query: '_id:1', }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); - const signalsOpen = await getAllSignals(supertest); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits[0]._source.signal.rule.rule_id).eql(getSimpleRule().rule_id); }); it('should query and get back expected signal structure using a basic KQL query', async () => { const rule: QueryCreateSchema = { - ...getSimpleRule(), - index: ['signal_object_clash'], - from: '1900-01-01T00:00:00.000Z', + ...getRuleForSignalTesting(['signal_object_clash']), query: '_id:1', }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); - const signalsOpen = await getAllSignals(supertest); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); // remove rule to cut down on touch points for test changes when the rule format changes const { rule: removedRule, ...signalNoRule } = signalsOpen.hits.hits[0]._source.signal; expect(signalNoRule).eql({ @@ -563,26 +553,22 @@ export default ({ getService }: FtrProviderContext) => { }); it('should query and get back expected signal structure when it is a signal on a signal', async () => { - // create a 1 signal from 1 auditbeat record const rule: QueryCreateSchema = { - ...getSimpleRule(), - index: ['signal_object_clash'], - from: '1900-01-01T00:00:00.000Z', - query: `_id:1`, + ...getRuleForSignalTesting(['signal_object_clash']), + query: '_id:1', }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 1); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); // Run signals on top of that 1 signal which should create a single signal (on top of) a signal const ruleForSignals: QueryCreateSchema = { - ...getSimpleRule(), + ...getRuleForSignalTesting([`${DEFAULT_SIGNALS_INDEX}*`]), rule_id: 'signal-on-signal', - index: [`${DEFAULT_SIGNALS_INDEX}*`], - from: '1900-01-01T00:00:00.000Z', - query: '*:*', }; - await createRule(supertest, ruleForSignals); - await waitForSignalsToBePresent(supertest, 2); + const { id: createdId } = await createRule(supertest, ruleForSignals); + await waitForRuleSuccess(supertest, createdId); + await waitForSignalsToBePresent(supertest, 1, [createdId]); // Get our single signal on top of a signal const signalsOpen = await getSignalsByRuleIds(supertest, ['signal-on-signal']); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/get_prepackaged_rules_status.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/get_prepackaged_rules_status.ts index 1bbfce42d2baa..c72b2e50b39fc 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/get_prepackaged_rules_status.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/get_prepackaged_rules_status.ts @@ -32,7 +32,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await deleteAllTimelines(es); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts index 664077d5a4fab..4ae953ead9df7 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts @@ -23,7 +23,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); - const es = getService('es'); describe('import_rules', () => { describe('importing rules without an index', () => { @@ -39,7 +38,7 @@ export default ({ getService }: FtrProviderContext): void => { .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`) .send(); return body.status_code === 404; - }); + }, `within should not create a rule if the index does not exist, ${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`); // Try to fetch the rule which should still be a 404 (not found) const { body } = await supertest.get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`).send(); @@ -86,7 +85,7 @@ export default ({ getService }: FtrProviderContext): void => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should set the response content types to be expected', async () => { @@ -129,7 +128,7 @@ export default ({ getService }: FtrProviderContext): void => { await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') - .attach('file', getSimpleRuleAsNdjson(['rule-1'], true), 'rules.ndjson') + .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') .expect(200); const { body } = await supertest @@ -138,7 +137,7 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(getSimpleRuleOutput('rule-1')); + expect(bodyToCompare).to.eql(getSimpleRuleOutput('rule-1', false)); }); it('should be able to import two rules', async () => { @@ -243,7 +242,7 @@ export default ({ getService }: FtrProviderContext): void => { await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') - .attach('file', getSimpleRuleAsNdjson(['rule-1'], true), 'rules.ndjson') + .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') .expect(200); const simpleRule = getSimpleRule('rule-1'); @@ -335,17 +334,13 @@ export default ({ getService }: FtrProviderContext): void => { await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') - .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2'], true), 'rules.ndjson') + .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2']), 'rules.ndjson') .expect(200); await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') - .attach( - 'file', - getSimpleRuleAsNdjson(['rule-1', 'rule-2', 'rule-3'], true), - 'rules.ndjson' - ) + .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2', 'rule-3']), 'rules.ndjson') .expect(200); const { body: bodyOfRule1 } = await supertest diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts index 962ae53b1241f..97d5b079fd206 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts @@ -19,6 +19,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./create_exceptions')); loadTestFile(require.resolve('./delete_rules')); loadTestFile(require.resolve('./delete_rules_bulk')); + loadTestFile(require.resolve('./exception_operators_data_types/index')); loadTestFile(require.resolve('./export_rules')); loadTestFile(require.resolve('./find_rules')); loadTestFile(require.resolve('./find_statuses')); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts index bbc3943b75955..87e3b145ed6fd 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts @@ -18,12 +18,13 @@ import { deleteSignalsIndex, setSignalStatus, getSignalStatusEmptyResponse, - getSimpleRule, getQuerySignalIds, deleteAllAlerts, createRule, waitForSignalsToBePresent, - getAllSignals, + getSignalsByIds, + waitForRuleSuccess, + getRuleForSignalTesting, } from '../../utils'; import { createUserAndRole } from '../roles_users_utils'; import { ROLES } from '../../../../plugins/security_solution/common/test'; @@ -32,7 +33,6 @@ import { ROLES } from '../../../../plugins/security_solution/common/test'; export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - const es = getService('es'); const supertestWithoutAuth = getService('supertestWithoutAuth'); const securityService = getService('security'); @@ -69,29 +69,31 @@ export default ({ getService }: FtrProviderContext) => { describe('tests with auditbeat data', () => { beforeEach(async () => { - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await createSignalsIndex(supertest); await esArchiver.load('auditbeat/hosts'); }); afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); await esArchiver.unload('auditbeat/hosts'); }); it('should be able to execute and get 10 signals', async () => { - const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 10); - const signalsOpen = await getAllSignals(supertest); + const rule = getRuleForSignalTesting(['auditbeat-*']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits.length).equal(10); }); it('should be have set the signals in an open state initially', async () => { - const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest); - const signalsOpen = await getAllSignals(supertest); + const rule = getRuleForSignalTesting(['auditbeat-*']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); const everySignalOpen = signalsOpen.hits.hits.every( ({ _source: { @@ -103,10 +105,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be able to get a count of 10 closed signals when closing 10', async () => { - const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest, 10); - const signalsOpen = await getAllSignals(supertest); + const rule = getRuleForSignalTesting(['auditbeat-*']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 10, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); // set all of the signals to the state of closed. There is no reason to use a waitUntil here @@ -129,10 +132,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be able close signals immediately and they all should be closed', async () => { - const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest); - const signalsOpen = await getAllSignals(supertest); + const rule = getRuleForSignalTesting(['auditbeat-*']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); + const signalsOpen = await getSignalsByIds(supertest, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); // set all of the signals to the state of closed. There is no reason to use a waitUntil here @@ -163,11 +167,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('should NOT be able to close signals with t1 analyst user', async () => { - const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest); + const rule = getRuleForSignalTesting(['auditbeat-*']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); await createUserAndRole(securityService, ROLES.t1_analyst); - const signalsOpen = await getAllSignals(supertest); + const signalsOpen = await getSignalsByIds(supertest, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); // Try to set all of the signals to the state of closed. @@ -200,12 +205,13 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be able to close signals with soc_manager user', async () => { - const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; - await createRule(supertest, rule); - await waitForSignalsToBePresent(supertest); + const rule = getRuleForSignalTesting(['auditbeat-*']); + const { id } = await createRule(supertest, rule); + await waitForRuleSuccess(supertest, id); + await waitForSignalsToBePresent(supertest, 1, [id]); const userAndRole = ROLES.soc_manager; await createUserAndRole(securityService, userAndRole); - const signalsOpen = await getAllSignals(supertest); + const signalsOpen = await getSignalsByIds(supertest, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); // Try to set all of the signals to the state of closed. diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules.ts index dbe66741e06c7..4de8abefe16fc 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules.ts @@ -25,7 +25,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('patch_rules', () => { describe('patch rules', () => { @@ -35,7 +34,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should patch a single rule property of name using a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules_bulk.ts index 69330a2bf682a..e32771d0d917c 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules_bulk.ts @@ -23,7 +23,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('patch_rules_bulk', () => { describe('patch rules bulk', () => { @@ -33,7 +32,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should patch a single rule property of name using a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/read_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/read_rules.ts index cfccb7436ea20..1697554441c16 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/read_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/read_rules.ts @@ -24,7 +24,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('read_rules', () => { describe('reading rules', () => { @@ -34,7 +33,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should be able to read a single rule using rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules.ts index 23a8776b14631..59dbe97557157 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules.ts @@ -27,7 +27,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('update_rules', () => { describe('update rules', () => { @@ -37,7 +36,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should update a single rule property of name using a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts index 22aa40b0721a4..c5b65039aa116 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts @@ -24,7 +24,6 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es = getService('es'); describe('update_rules_bulk', () => { describe('update rules bulk', () => { @@ -34,7 +33,7 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await deleteSignalsIndex(supertest); - await deleteAllAlerts(es); + await deleteAllAlerts(supertest); }); it('should update a single rule property of name using a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/utils.ts b/x-pack/test/detection_engine_api_integration/utils.ts index f458fe118dcf7..06d33da8f1f55 100644 --- a/x-pack/test/detection_engine_api_integration/utils.ts +++ b/x-pack/test/detection_engine_api_integration/utils.ts @@ -9,6 +9,8 @@ import { SuperTest } from 'supertest'; import supertestAsPromised from 'supertest-as-promised'; import { Context } from '@elastic/elasticsearch/lib/Transport'; import { SearchResponse } from 'elasticsearch'; +import { NonEmptyEntriesArray } from '../../plugins/lists/common/schemas'; +import { getCreateExceptionListDetectionSchemaMock } from '../../plugins/lists/common/schemas/request/create_exception_list_schema.mock'; import { CreateRulesSchema, UpdateRulesSchema, @@ -35,6 +37,7 @@ import { DETECTION_ENGINE_RULES_URL, INTERNAL_RULE_ID_KEY, } from '../../plugins/security_solution/common/constants'; +import { getCreateExceptionListItemMinimalSchemaMockWithoutId } from '../../plugins/lists/common/schemas/request/create_exception_list_item_schema.mock'; /** * This will remove server generated properties such as date times, etc... @@ -76,9 +79,9 @@ export const removeServerGeneratedPropertiesIncludingRuleId = ( /** * This is a typical simple rule for testing that is easy for most basic testing * @param ruleId - * @param enabled Enables the rule on creation or not. Defaulted to false to enable it on import + * @param enabled Enables the rule on creation or not. Defaulted to true. */ -export const getSimpleRule = (ruleId = 'rule-1', enabled = true): QueryCreateSchema => ({ +export const getSimpleRule = (ruleId = 'rule-1', enabled = false): QueryCreateSchema => ({ name: 'Simple Rule Query', description: 'Simple Rule Query', enabled, @@ -90,13 +93,39 @@ export const getSimpleRule = (ruleId = 'rule-1', enabled = true): QueryCreateSch query: 'user.name: root or user.name: admin', }); +/** + * This is a typical signal testing rule that is easy for most basic testing of output of signals. + * It starts out in an enabled true state. The from is set very far back to test the basics of signal + * creation and testing by getting all the signals at once. + * @param ruleId The optional ruleId which is rule-1 by default. + * @param enabled Enables the rule on creation or not. Defaulted to true. + */ +export const getRuleForSignalTesting = ( + index: string[], + ruleId = 'rule-1', + enabled = true +): QueryCreateSchema => ({ + name: 'Signal Testing Query', + description: 'Tests a simple query', + enabled, + risk_score: 1, + rule_id: ruleId, + severity: 'high', + index, + type: 'query', + query: '*:*', + from: '1900-01-01T00:00:00.000Z', +}); + /** * This is a typical simple rule for testing that is easy for most basic testing - * @param ruleId + * @param ruleId The rule id + * @param enabled Set to tru to enable it, by default it is off */ -export const getSimpleRuleUpdate = (ruleId = 'rule-1'): UpdateRulesSchema => ({ +export const getSimpleRuleUpdate = (ruleId = 'rule-1', enabled = false): UpdateRulesSchema => ({ name: 'Simple Rule Query', description: 'Simple Rule Query', + enabled, risk_score: 1, rule_id: ruleId, severity: 'high', @@ -107,11 +136,13 @@ export const getSimpleRuleUpdate = (ruleId = 'rule-1'): UpdateRulesSchema => ({ /** * This is a representative ML rule payload as expected by the server - * @param ruleId + * @param ruleId The rule id + * @param enabled Set to tru to enable it, by default it is off */ -export const getSimpleMlRule = (ruleId = 'rule-1'): CreateRulesSchema => ({ +export const getSimpleMlRule = (ruleId = 'rule-1', enabled = false): CreateRulesSchema => ({ name: 'Simple ML Rule', description: 'Simple Machine Learning Rule', + enabled, anomaly_threshold: 44, risk_score: 1, rule_id: ruleId, @@ -120,9 +151,15 @@ export const getSimpleMlRule = (ruleId = 'rule-1'): CreateRulesSchema => ({ type: 'machine_learning', }); -export const getSimpleMlRuleUpdate = (ruleId = 'rule-1'): UpdateRulesSchema => ({ +/** + * This is a representative ML rule payload as expected by the server for an update + * @param ruleId The rule id + * @param enabled Set to tru to enable it, by default it is off + */ +export const getSimpleMlRuleUpdate = (ruleId = 'rule-1', enabled = false): UpdateRulesSchema => ({ name: 'Simple ML Rule', description: 'Simple Machine Learning Rule', + enabled, anomaly_threshold: 44, risk_score: 1, rule_id: ruleId, @@ -160,6 +197,19 @@ export const getQuerySignalsRuleId = (ruleIds: string[]) => ({ }, }); +/** + * Given an array of ids for a test this will get the signals + * created from that rule's regular id. + * @param ruleIds The rule_id to search for signals + */ +export const getQuerySignalsId = (ids: string[]) => ({ + query: { + terms: { + 'signal.rule.id': ids, + }, + }, +}); + export const setSignalStatus = ({ signalIds, status, @@ -216,12 +266,12 @@ export const binaryToString = (res: any, callback: any): void => { * This is the typical output of a simple rule that Kibana will output with all the defaults * except for the server generated properties. Useful for testing end to end tests. */ -export const getSimpleRuleOutput = (ruleId = 'rule-1'): Partial => ({ +export const getSimpleRuleOutput = (ruleId = 'rule-1', enabled = false): Partial => ({ actions: [], author: [], created_by: 'elastic', description: 'Simple Rule Query', - enabled: true, + enabled, false_positives: [], from: 'now-6m', immutable: false, @@ -274,21 +324,38 @@ export const getSimpleMlRuleOutput = (ruleId = 'rule-1'): Partial = }; /** - * Remove all alerts from the .kibana index - * This will retry 20 times before giving up and hopefully still not interfere with other tests - * @param es The ElasticSearch handle + * Removes all rules by looping over any found and removing them from REST. + * @param supertest The supertest agent. */ -export const deleteAllAlerts = async (es: Client): Promise => { - return countDownES(async () => { - return es.deleteByQuery({ - index: '.kibana', - q: 'type:alert', - wait_for_completion: true, - refresh: true, - conflicts: 'proceed', - body: {}, - }); - }, 'deleteAllAlerts'); +export const deleteAllAlerts = async ( + supertest: SuperTest +): Promise => { + await countDownTest( + async () => { + const { body } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}/_find?per_page=9999`) + .set('kbn-xsrf', 'true') + .send(); + + const ids = body.data.map((rule: FullResponseSchema) => ({ + id: rule.id, + })); + + await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .send(ids) + .set('kbn-xsrf', 'true'); + + const { body: finalCheck } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}/_find`) + .set('kbn-xsrf', 'true') + .send(); + return finalCheck.data.length === 0; + }, + 'deleteAllAlerts', + 50, + 1000 + ); }; export const downgradeImmutableRule = async (es: Client, ruleId: string): Promise => { @@ -331,7 +398,7 @@ export const deleteAllTimelines = async (es: Client): Promise => { * This will retry 20 times before giving up and hopefully still not interfere with other tests * @param es The ElasticSearch handle */ -export const deleteAllRulesStatuses = async (es: Client, retryCount = 20): Promise => { +export const deleteAllRulesStatuses = async (es: Client): Promise => { return countDownES(async () => { return es.deleteByQuery({ index: '.kibana', @@ -585,8 +652,8 @@ export const getWebHookAction = () => ({ name: 'Some connector', }); -export const getRuleWithWebHookAction = (id: string): CreateRulesSchema => ({ - ...getSimpleRule(), +export const getRuleWithWebHookAction = (id: string, enabled = false): CreateRulesSchema => ({ + ...getSimpleRule('rule-1', enabled), throttle: 'rule', actions: [ { @@ -618,7 +685,8 @@ export const getSimpleRuleOutputWithWebHookAction = (actionId: string): Partial< // Similar to ReactJs's waitFor from here: https://testing-library.com/docs/dom-testing-library/api-async#waitfor export const waitFor = async ( functionToTest: () => Promise, - maxTimeout: number = 5000, + functionName: string, + maxTimeout: number = 10000, timeoutWait: number = 10 ): Promise => { await new Promise(async (resolve, reject) => { @@ -636,7 +704,9 @@ export const waitFor = async ( if (found) { resolve(); } else { - reject(new Error('timed out waiting for function condition to be true')); + reject( + new Error(`timed out waiting for function condition to be true within ${functionName}`) + ); } }); }; @@ -807,7 +877,7 @@ export const waitForRuleSuccess = async ( .send({ ids: [id] }) .expect(200); return body[id]?.current_status?.status === 'succeeded'; - }); + }, 'waitForRuleSuccess'); }; /** @@ -818,51 +888,77 @@ export const waitForRuleSuccess = async ( */ export const waitForSignalsToBePresent = async ( supertest: SuperTest, - numberOfSignals = 1 + numberOfSignals = 1, + signalIds: string[] ): Promise => { await waitFor(async () => { - const { - body: signalsOpen, - }: { body: SearchResponse<{ signal: Signal }> } = await supertest - .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) - .set('kbn-xsrf', 'true') - .send(getQueryAllSignals()) - .expect(200); + const signalsOpen = await getSignalsByIds(supertest, signalIds); return signalsOpen.hits.hits.length >= numberOfSignals; - }); + }, 'waitForSignalsToBePresent'); }; /** - * Returns all signals both closed and opened + * Returns all signals both closed and opened by ruleId * @param supertest Deps */ -export const getAllSignals = async ( - supertest: SuperTest +export const getSignalsByRuleIds = async ( + supertest: SuperTest, + ruleIds: string[] ): Promise< SearchResponse<{ signal: Signal; + [x: string]: unknown; }> > => { const { body: signalsOpen }: { body: SearchResponse<{ signal: Signal }> } = await supertest .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) .set('kbn-xsrf', 'true') - .send(getQueryAllSignals()) + .send(getQuerySignalsRuleId(ruleIds)) .expect(200); return signalsOpen; }; -export const getSignalsByRuleIds = async ( +/** + * Given an array of rule ids this will return only signals based on that rule id both + * open and closed + * @param supertest agent + * @param ids Array of the rule ids + */ +export const getSignalsByIds = async ( supertest: SuperTest, - ruleIds: string[] + ids: string[] ): Promise< SearchResponse<{ signal: Signal; + [x: string]: unknown; }> > => { const { body: signalsOpen }: { body: SearchResponse<{ signal: Signal }> } = await supertest .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) .set('kbn-xsrf', 'true') - .send(getQuerySignalsRuleId(ruleIds)) + .send(getQuerySignalsId(ids)) + .expect(200); + return signalsOpen; +}; + +/** + * Given a single rule id this will return only signals based on that rule id. + * @param supertest agent + * @param ids Rule id + */ +export const getSignalsById = async ( + supertest: SuperTest, + id: string +): Promise< + SearchResponse<{ + signal: Signal; + [x: string]: unknown; + }> +> => { + const { body: signalsOpen }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQuerySignalsId([id])) .expect(200); return signalsOpen; }; @@ -870,5 +966,77 @@ export const getSignalsByRuleIds = async ( export const installPrePackagedRules = async ( supertest: SuperTest ): Promise => { - await supertest.put(DETECTION_ENGINE_PREPACKAGED_URL).set('kbn-xsrf', 'true').send().expect(200); + await countDownTest(async () => { + const { status } = await supertest + .put(DETECTION_ENGINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send(); + return status === 200; + }, 'installPrePackagedRules'); +}; + +/** + * Convenience testing function where you can pass in just the entries and you will + * get a rule created with the entries added to an exception list and exception list item + * all auto-created at once. + * @param supertest super test agent + * @param rule The rule to create and attach an exception list to + * @param entries The entries to create the rule and exception list from + */ +export const createRuleWithExceptionEntries = async ( + supertest: SuperTest, + rule: QueryCreateSchema, + entries: NonEmptyEntriesArray[] +): Promise => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { id, list_id, namespace_type, type } = await createExceptionList( + supertest, + getCreateExceptionListDetectionSchemaMock() + ); + + await Promise.all( + entries.map((entry) => { + const exceptionListItem: CreateExceptionListItemSchema = { + ...getCreateExceptionListItemMinimalSchemaMockWithoutId(), + entries: entry, + }; + return createExceptionListItem(supertest, exceptionListItem); + }) + ); + + // To reduce the odds of in-determinism and/or bugs we ensure we have + // the same length of entries before continuing. + await waitFor(async () => { + const { body } = await supertest.get( + `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ + getCreateExceptionListDetectionSchemaMock().list_id + }` + ); + return body.data.length === entries.length; + }, `within createRuleWithExceptionEntries ${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${getCreateExceptionListDetectionSchemaMock().list_id}`); + + // create the rule but don't run it immediately as running it immediately can cause + // the rule to sometimes not filter correctly the first time with an exception list + // or other timing issues. Then afterwards wait for the rule to have succeeded before + // returning. + const ruleWithException: QueryCreateSchema = { + ...rule, + enabled: false, + exceptions_list: [ + { + id, + list_id, + namespace_type, + type, + }, + ], + }; + const ruleResponse = await createRule(supertest, ruleWithException); + await supertest + .patch(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send({ rule_id: ruleResponse.rule_id, enabled: true }) + .expect(200); + + return ruleResponse; }; diff --git a/x-pack/test/functional/es_archives/rule_exceptions/README.md b/x-pack/test/functional/es_archives/rule_exceptions/README.md new file mode 100644 index 0000000000000..1fbf4962d55fe --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/README.md @@ -0,0 +1,11 @@ +Within this folder is input test data for tests such as: + +```ts +security_and_spaces/tests/rule_exceptions.ts +``` + +where these are small ECS compliant input indexes that try to express tests that exercise different parts of +the detection engine around creating and validating that the exceptions part of the detection engine functions. +Compliant meaning that these might contain extra fields but should not clash with ECS. Nothing stopping anyone +from being ECS strict and not having additional extra fields but the extra fields and mappings are to just try +and keep these tests simple and small. diff --git a/x-pack/test/functional/es_archives/rule_exceptions/date/data.json b/x-pack/test/functional/es_archives/rule_exceptions/date/data.json new file mode 100644 index 0000000000000..dd1609070a19d --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/date/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "date", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "date": "2020-10-01T05:08:53.000Z" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "date", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "date": "2020-10-02T05:08:53.000Z" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "date", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "date": "2020-10-03T05:08:53.000Z" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "date", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "date": "2020-10-04T05:08:53.000Z" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/date/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/date/mappings.json new file mode 100644 index 0000000000000..28c0158cdc852 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/date/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "date", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "date": { "type": "date" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/double/data.json b/x-pack/test/functional/es_archives/rule_exceptions/double/data.json new file mode 100644 index 0000000000000..1f7a5969f5872 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/double/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "double", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "double": 1.0 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "double", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "double": 1.1 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "double", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "double": 1.2 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "double", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "double": 1.3 + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/double/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/double/mappings.json new file mode 100644 index 0000000000000..bd69ae19ed148 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/double/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "double", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "double": { "type": "double" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/double_as_string/data.json b/x-pack/test/functional/es_archives/rule_exceptions/double_as_string/data.json new file mode 100644 index 0000000000000..2bdd685fae4c9 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/double_as_string/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "double_as_string", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "double": "1.0" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "double_as_string", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "double": "1.1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "double_as_string", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "double": "1.2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "double_as_string", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "double": "1.3" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/double_as_string/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/double_as_string/mappings.json new file mode 100644 index 0000000000000..a3b3fc52325a5 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/double_as_string/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "double_as_string", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "double": { "type": "double" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/float/data.json b/x-pack/test/functional/es_archives/rule_exceptions/float/data.json new file mode 100644 index 0000000000000..888be5ff20a32 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/float/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "float", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "float": 1.0 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "float", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "float": 1.1 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "float", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "float": 1.2 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "float", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "float": 1.3 + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/float/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/float/mappings.json new file mode 100644 index 0000000000000..b0a7b1a7fc60c --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/float/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "float", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "float": { "type": "float" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/float_as_string/data.json b/x-pack/test/functional/es_archives/rule_exceptions/float_as_string/data.json new file mode 100644 index 0000000000000..4d8575d3ccb9c --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/float_as_string/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "float_as_string", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "float": "1.0" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "float_as_string", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "float": "1.1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "float_as_string", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "float": "1.2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "float_as_string", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "float": "1.3" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/float_as_string/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/float_as_string/mappings.json new file mode 100644 index 0000000000000..7e66ace5eb5c6 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/float_as_string/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "float_as_string", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "float": { "type": "float" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/integer/data.json b/x-pack/test/functional/es_archives/rule_exceptions/integer/data.json new file mode 100644 index 0000000000000..5e2f1295397e6 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/integer/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "integer", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "integer": 1 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "integer", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "integer": 2 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "integer", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "integer": 3 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "integer", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "integer": 4 + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/integer/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/integer/mappings.json new file mode 100644 index 0000000000000..a05f3ec4e3186 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/integer/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "integer", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "integer": { "type": "integer" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/integer_as_string/data.json b/x-pack/test/functional/es_archives/rule_exceptions/integer_as_string/data.json new file mode 100644 index 0000000000000..5d0ac56e27d00 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/integer_as_string/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "integer_as_string", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "integer": "1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "integer_as_string", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "integer": "2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "integer_as_string", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "integer": "3" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "integer_as_string", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "integer": "4" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/integer_as_string/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/integer_as_string/mappings.json new file mode 100644 index 0000000000000..e98d0d89214dd --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/integer_as_string/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "integer_as_string", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "integer": { "type": "integer" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/ip/data.json b/x-pack/test/functional/es_archives/rule_exceptions/ip/data.json new file mode 100644 index 0000000000000..5dde1cba8f884 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/ip/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "ip", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "ip": "127.0.0.1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "ip", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "ip": "127.0.0.2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "ip", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "ip": "127.0.0.3" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "ip", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "ip": "127.0.0.4" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/ip/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/ip/mappings.json new file mode 100644 index 0000000000000..ceb58bc933507 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/ip/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "ip", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "ip": { "type": "ip" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/keyword/data.json b/x-pack/test/functional/es_archives/rule_exceptions/keyword/data.json new file mode 100644 index 0000000000000..09c54843f32c9 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/keyword/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "keyword", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "keyword": "word one" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "keyword", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "keyword": "word two" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "keyword", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "keyword": "word three" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "keyword", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "keyword": "word four" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/keyword/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/keyword/mappings.json new file mode 100644 index 0000000000000..bc8becbe07f45 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/keyword/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "keyword", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "keyword": { "type": "keyword" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/long/data.json b/x-pack/test/functional/es_archives/rule_exceptions/long/data.json new file mode 100644 index 0000000000000..807314bd28173 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/long/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "long", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "long": 1 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "long", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "long": 2 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "long", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "long": 3 + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "long", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "long": 4 + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/long/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/long/mappings.json new file mode 100644 index 0000000000000..75b156805af78 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/long/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "long", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "long": { "type": "long" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/long_as_string/data.json b/x-pack/test/functional/es_archives/rule_exceptions/long_as_string/data.json new file mode 100644 index 0000000000000..3604026d2cdb0 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/long_as_string/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "long_as_string", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "long": "1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "long_as_string", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "long": "2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "long_as_string", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "long": "3" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "long_as_string", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "long": "4" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/long_as_string/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/long_as_string/mappings.json new file mode 100644 index 0000000000000..8fe9af08127d1 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/long_as_string/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "long_as_string", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "long": { "type": "long" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/text/data.json b/x-pack/test/functional/es_archives/rule_exceptions/text/data.json new file mode 100644 index 0000000000000..8d3da48224cc3 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/text/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "text", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "text": "word one" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "text", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "text": "word two" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "text", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "text": "word three" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "text", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "text": "word four" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/text/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/text/mappings.json new file mode 100644 index 0000000000000..5d3304fc202d5 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/text/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "text", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "text": { "type": "text" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/text_no_spaces/data.json b/x-pack/test/functional/es_archives/rule_exceptions/text_no_spaces/data.json new file mode 100644 index 0000000000000..a0caf9d9eb2d3 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/text_no_spaces/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "text_no_spaces", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "text": "one" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "text_no_spaces", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "text": "two" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "text_no_spaces", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "text": "three" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "text_no_spaces", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "text": "four" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/text_no_spaces/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/text_no_spaces/mappings.json new file mode 100644 index 0000000000000..b981af7937124 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/text_no_spaces/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "text_no_spaces", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "text": { "type": "text" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/wildcard/data.json b/x-pack/test/functional/es_archives/rule_exceptions/wildcard/data.json new file mode 100644 index 0000000000000..40dd24f83c0d2 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/wildcard/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "wildcard", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "wildcard": "word one" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "wildcard", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "wildcard": "word two" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "wildcard": "wildcard", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "wildcard": "word three" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "wildcard", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "wildcard": "word four" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/rule_exceptions/wildcard/mappings.json b/x-pack/test/functional/es_archives/rule_exceptions/wildcard/mappings.json new file mode 100644 index 0000000000000..1b6a697ecbb8f --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_exceptions/wildcard/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "wildcard", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "wildcard": { "type": "wildcard" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/signals/README.md b/x-pack/test/functional/es_archives/signals/README.md new file mode 100644 index 0000000000000..4b147a414f8b3 --- /dev/null +++ b/x-pack/test/functional/es_archives/signals/README.md @@ -0,0 +1,22 @@ +Within this folder is input test data for tests such as: + +```ts +security_and_spaces/tests/generating_signals.ts +``` + +where these are small ECS compliant input indexes that try to express tests that exercise different parts of +the detection engine signals. Compliant meaning that these might contain extra fields but should not clash with ECS. +Nothing stopping anyone from being ECS strict and not having additional extra fields but the extra fields and mappings +are to just try and keep these tests simple and small. Examples are: + + +This is an ECS document that has a numeric name clash with a signal structure +``` +numeric_name_clash +``` + +This is an ECS document that has an object name clash with a signal structure +``` +object_clash +``` + diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/import_list_items.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/import_list_items.ts index 7b7a6173fb408..ae9814e603b74 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/import_list_items.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/import_list_items.ts @@ -94,7 +94,7 @@ export default ({ getService }: FtrProviderContext): void => { .get(`${LIST_ITEM_URL}?list_id=list_items.txt&value=127.0.0.1`) .send(); return status !== 404; - }); + }, `${LIST_ITEM_URL}?list_id=list_items.txt&value=127.0.0.1`); const { body } = await supertest .get(`${LIST_ITEM_URL}?list_id=list_items.txt&value=127.0.0.1`) .send() diff --git a/x-pack/test/lists_api_integration/utils.ts b/x-pack/test/lists_api_integration/utils.ts index 5870239b73ed1..224048e868d7f 100644 --- a/x-pack/test/lists_api_integration/utils.ts +++ b/x-pack/test/lists_api_integration/utils.ts @@ -8,13 +8,15 @@ import { SuperTest } from 'supertest'; import supertestAsPromised from 'supertest-as-promised'; import { Client } from '@elastic/elasticsearch'; +import { getImportListItemAsBuffer } from '../../plugins/lists/common/schemas/request/import_list_item_schema.mock'; import { ListItemSchema, ExceptionListSchema, ExceptionListItemSchema, + Type, } from '../../plugins/lists/common/schemas'; import { ListSchema } from '../../plugins/lists/common'; -import { LIST_INDEX } from '../../plugins/lists/common/constants'; +import { LIST_INDEX, LIST_ITEM_URL } from '../../plugins/lists/common/constants'; import { countDownES, countDownTest } from '../detection_engine_api_integration/utils'; /** @@ -109,6 +111,7 @@ export const removeExceptionListServerGeneratedProperties = ( // Similar to ReactJs's waitFor from here: https://testing-library.com/docs/dom-testing-library/api-async#waitfor export const waitFor = async ( functionToTest: () => Promise, + functionName: string, maxTimeout: number = 5000, timeoutWait: number = 10 ) => { @@ -127,7 +130,7 @@ export const waitFor = async ( if (found) { resolve(); } else { - reject(new Error('timed out waiting for function condition to be true')); + reject(new Error(`timed out waiting for function ${functionName} condition to be true`)); } }); }; @@ -164,3 +167,134 @@ export const deleteAllExceptions = async (es: Client): Promise => { }); }, 'deleteAllExceptions'); }; + +/** + * Convenience function for quickly importing a given type and contents and then + * waiting to ensure they're there before continuing + * @param supertest The super test agent + * @param type The type to import as + * @param contents The contents of the import + * @param fileName filename to import as + */ +export const importFile = async ( + supertest: SuperTest, + type: Type, + contents: string[], + fileName: string +): Promise => { + await supertest + .post(`${LIST_ITEM_URL}/_import?type=${type}`) + .set('kbn-xsrf', 'true') + .attach('file', getImportListItemAsBuffer(contents), fileName) + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(200); + + // although we have pushed the list and its items, it is async so we + // have to wait for the contents before continuing + await waitForListItems(supertest, contents, fileName); +}; + +/** + * Convenience function for quickly importing a given type and contents and then + * waiting to ensure they're there before continuing. This specifically checks tokens + * from text file + * @param supertest The super test agent + * @param type The type to import as + * @param contents The contents of the import + * @param fileName filename to import as + */ +export const importTextFile = async ( + supertest: SuperTest, + type: Type, + contents: string[], + fileName: string +): Promise => { + await supertest + .post(`${LIST_ITEM_URL}/_import?type=${type}`) + .set('kbn-xsrf', 'true') + .attach('file', getImportListItemAsBuffer(contents), fileName) + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(200); + + // although we have pushed the list and its items, it is async so we + // have to wait for the contents before continuing + await waitForTextListItems(supertest, contents, fileName); +}; + +/** + * Convenience function for waiting for a particular file uploaded + * and a particular item value to be available before continuing. + * @param supertest The super test agent + * @param fileName The filename imported + * @param itemValue The item value to wait for + */ +export const waitForListItem = async ( + supertest: SuperTest, + itemValue: string, + fileName: string +): Promise => { + await waitFor(async () => { + const { status } = await supertest + .get(`${LIST_ITEM_URL}?list_id=${fileName}&value=${itemValue}`) + .send(); + + return status === 200; + }, `waitForListItem fileName: "${fileName}" itemValue: "${itemValue}"`); +}; + +/** + * Convenience function for waiting for a particular file uploaded + * and particular item values to be available before continuing. + * @param supertest The super test agent + * @param fileName The filename imported + * @param itemValue The item value to wait for + */ +export const waitForListItems = async ( + supertest: SuperTest, + itemValues: string[], + fileName: string +): Promise => { + await Promise.all(itemValues.map((item) => waitForListItem(supertest, item, fileName))); +}; + +/** + * Convenience function for waiting for a particular file uploaded + * and a particular item value to be available before continuing. + * @param supertest The super test agent + * @param fileName The filename imported + * @param itemValue The item value to wait for + */ +export const waitForTextListItem = async ( + supertest: SuperTest, + itemValue: string, + fileName: string +): Promise => { + const tokens = itemValue.split(' '); + await waitFor(async () => { + const promises = await Promise.all( + tokens.map(async (token) => { + const { status } = await supertest + .get(`${LIST_ITEM_URL}?list_id=${fileName}&value=${token}`) + .send(); + return status === 200; + }) + ); + return promises.every((one) => one); + }, `waitForTextListItem fileName: "${fileName}" itemValue: "${itemValue}"`); +}; + +/** + * Convenience function for waiting for a particular file uploaded + * and particular item values to be available before continuing. This works + * specifically with text types and does tokenization to ensure all words are uploaded + * @param supertest The super test agent + * @param fileName The filename imported + * @param itemValue The item value to wait for + */ +export const waitForTextListItems = async ( + supertest: SuperTest, + itemValues: string[], + fileName: string +): Promise => { + await Promise.all(itemValues.map((item) => waitForTextListItem(supertest, item, fileName))); +};