diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts index cce781f82e64f..bfdfe281ec038 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -151,6 +151,7 @@ describe('rules_notification_alert_type', () => { const value: Partial = { statusCode: 200, body: { + indices: ['index1', 'index2', 'index3', 'index4'], fields: { '@timestamp': { date: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 2b0abdfdfa090..a5df5983dc0d0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -9,7 +9,7 @@ import { Logger, KibanaRequest } from 'src/core/server'; import isEmpty from 'lodash/isEmpty'; import { chain, tryCatch } from 'fp-ts/lib/TaskEither'; -import { flow, pipe } from 'fp-ts/lib/function'; +import { flow } from 'fp-ts/lib/function'; import { toError, toPromise } from '../../../../common/fp_utils'; @@ -188,22 +188,14 @@ export const signalRulesAlertType = ({ try { if (!isEmpty(index)) { const hasTimestampOverride = timestampOverride != null && !isEmpty(timestampOverride); + const inputIndices = await getInputIndex(services, version, index); const [privileges, timestampFieldCaps] = await Promise.all([ - pipe( - { services, version, index }, - ({ services: svc, version: ver, index: idx }) => - pipe( - tryCatch(() => getInputIndex(svc, ver, idx), toError), - chain((indices) => tryCatch(() => checkPrivileges(svc, indices), toError)) - ), - toPromise - ), + checkPrivileges(services, inputIndices), services.scopedClusterClient.fieldCaps({ index, fields: hasTimestampOverride ? ['@timestamp', timestampOverride as string] : ['@timestamp'], - allow_no_indices: false, include_unmapped: true, }), ]); @@ -222,6 +214,7 @@ export const signalRulesAlertType = ({ wroteStatus, hasTimestampOverride ? (timestampOverride as string) : '@timestamp', timestampFieldCaps, + inputIndices, ruleStatusService, logger, buildRuleMessage diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts index ec55ad7f588e8..2574abd73b6cf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts @@ -819,6 +819,7 @@ describe('utils', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const timestampFieldCapsResponse: Partial, Context>> = { body: { + indices: ['myfakeindex-1', 'myfakeindex-2', 'myfakeindex-3', 'myfakeindex-4'], fields: { [timestampField]: { date: { @@ -843,6 +844,7 @@ describe('utils', () => { timestampField, // eslint-disable-next-line @typescript-eslint/no-explicit-any timestampFieldCapsResponse as ApiResponse>, + ['myfa*'], ruleStatusServiceMock, mockLogger, buildRuleMessage @@ -857,6 +859,7 @@ describe('utils', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const timestampFieldCapsResponse: Partial, Context>> = { body: { + indices: ['myfakeindex-1', 'myfakeindex-2', 'myfakeindex-3', 'myfakeindex-4'], fields: { [timestampField]: { date: { @@ -881,6 +884,7 @@ describe('utils', () => { timestampField, // eslint-disable-next-line @typescript-eslint/no-explicit-any timestampFieldCapsResponse as ApiResponse>, + ['myfa*'], ruleStatusServiceMock, mockLogger, buildRuleMessage diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index 0ad502b67fbe6..274e4feffcc3e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -107,11 +107,19 @@ export const hasTimestampFields = async ( // node_modules/@elastic/elasticsearch/api/kibana.d.ts // eslint-disable-next-line @typescript-eslint/no-explicit-any timestampFieldCapsResponse: ApiResponse, Context>, + inputIndices: string[], ruleStatusService: RuleStatusService, logger: Logger, buildRuleMessage: BuildRuleMessage ): Promise => { - if ( + if (!wroteStatus && isEmpty(timestampFieldCapsResponse.body.indices)) { + const errorString = `The following index patterns did not match any indices: ${JSON.stringify( + inputIndices + )}`; + logger.error(buildRuleMessage(errorString)); + await ruleStatusService.error(errorString); + return true; + } else if ( !wroteStatus && (isEmpty(timestampFieldCapsResponse.body.fields) || timestampFieldCapsResponse.body.fields[timestampField] == null || 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 a8a5f2abd072b..f97309bafde37 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,6 +25,7 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); describe('create_rules', () => { describe('validation errors', () => { @@ -46,11 +47,13 @@ export default ({ getService }: FtrProviderContext) => { describe('creating rules', () => { beforeEach(async () => { await createSignalsIndex(supertest); + await esArchiver.load('auditbeat/hosts'); }); afterEach(async () => { await deleteSignalsIndex(supertest); await deleteAllAlerts(supertest); + await esArchiver.unload('auditbeat/hosts'); }); 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 73be4154db1eb..30e2e22c0547c 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,6 +23,7 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); describe('create_rules_bulk', () => { describe('validation errors', () => { @@ -49,11 +50,13 @@ export default ({ getService }: FtrProviderContext): void => { describe('creating rules in bulk', () => { beforeEach(async () => { await createSignalsIndex(supertest); + await esArchiver.load('auditbeat/hosts'); }); afterEach(async () => { await deleteSignalsIndex(supertest); await deleteAllAlerts(supertest); + await esArchiver.unload('auditbeat/hosts'); }); it('should create a single rule with a rule_id', 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 785b74d334276..7457aa37be5df 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 @@ -21,17 +21,20 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); const es = getService('es'); describe('find_statuses', () => { beforeEach(async () => { await createSignalsIndex(supertest); + await esArchiver.load('auditbeat/hosts'); }); afterEach(async () => { await deleteSignalsIndex(supertest); await deleteAllAlerts(supertest); await deleteAllRulesStatuses(es); + await esArchiver.unload('auditbeat/hosts'); }); it('should return an empty find statuses body correctly if no statuses are loaded', 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 a2c3fc6c6c288..ffb418be5dc7c 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,16 +24,19 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); describe('add_actions', () => { describe('adding actions', () => { beforeEach(async () => { + await esArchiver.load('auditbeat/hosts'); await createSignalsIndex(supertest); }); afterEach(async () => { await deleteSignalsIndex(supertest); await deleteAllAlerts(supertest); + await esArchiver.unload('auditbeat/hosts'); }); it('should be able to create a new webhook action and attach it to a rule', async () => { 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 b90bea66be11f..6cfd3b9e9e1e3 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 @@ -45,12 +45,14 @@ export default ({ getService }: FtrProviderContext) => { describe('creating rules with exceptions', () => { beforeEach(async () => { await createSignalsIndex(supertest); + await esArchiver.load('auditbeat/hosts'); }); afterEach(async () => { await deleteSignalsIndex(supertest); await deleteAllAlerts(supertest); await deleteAllExceptions(es); + await esArchiver.unload('auditbeat/hosts'); }); it('should create a single rule with a rule_id and add an exception list to the rule', async () => { 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 ba4a524f3b9b2..e416dcf57b32b 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 @@ -55,11 +55,13 @@ export default ({ getService }: FtrProviderContext) => { describe('creating rules', () => { beforeEach(async () => { await createSignalsIndex(supertest); + await esArchiver.load('auditbeat/hosts'); }); afterEach(async () => { await deleteSignalsIndex(supertest); await deleteAllAlerts(supertest); + await esArchiver.unload('auditbeat/hosts'); }); it('should create a single rule with a rule_id', async () => { @@ -111,6 +113,47 @@ export default ({ getService }: FtrProviderContext) => { expect(statusBody[body.id].current_status.status).to.eql('succeeded'); }); + it('should create a single rule with a rule_id and an index pattern that does not match anything available and fail the rule', async () => { + const simpleRule = getRuleForSignalTesting(['does-not-exist-*']); + const { body } = await supertest + .post(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(simpleRule) + .expect(200); + + await waitForRuleSuccessOrStatus(supertest, body.id, 'failed'); + + const { body: statusBody } = await supertest + .post(DETECTION_ENGINE_RULES_STATUS_URL) + .set('kbn-xsrf', 'true') + .send({ ids: [body.id] }) + .expect(200); + + expect(statusBody[body.id].current_status.status).to.eql('failed'); + expect(statusBody[body.id].current_status.last_failure_message).to.eql( + 'The following index patterns did not match any indices: ["does-not-exist-*"]' + ); + }); + + it('should create a single rule with a rule_id and an index pattern that does not match anything and an index pattern that does and the rule should be successful', async () => { + const simpleRule = getRuleForSignalTesting(['does-not-exist-*', 'auditbeat-*']); + const { body } = await supertest + .post(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(simpleRule) + .expect(200); + + await waitForRuleSuccessOrStatus(supertest, body.id, 'succeeded'); + + const { body: statusBody } = await supertest + .post(DETECTION_ENGINE_RULES_STATUS_URL) + .set('kbn-xsrf', 'true') + .send({ ids: [body.id] }) + .expect(200); + + expect(statusBody[body.id].current_status.status).to.eql('succeeded'); + }); + it('should create a single rule without an input index', async () => { const rule: CreateRulesSchema = { name: 'Simple Rule Query', 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 2577c6b163604..99854442b9c7a 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 @@ -28,6 +28,7 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); describe('create_rules_bulk', () => { describe('validation errors', () => { @@ -54,11 +55,13 @@ export default ({ getService }: FtrProviderContext): void => { describe('creating rules in bulk', () => { beforeEach(async () => { await createSignalsIndex(supertest); + await esArchiver.load('auditbeat/hosts'); }); afterEach(async () => { await deleteSignalsIndex(supertest); await deleteAllAlerts(supertest); + await esArchiver.unload('auditbeat/hosts'); }); it('should create a single rule with a rule_id', 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 dfec35e4a64f3..d31b076ab12ea 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 @@ -22,16 +22,19 @@ import { export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const es = getService('es'); + const esArchiver = getService('esArchiver'); describe('find_statuses', () => { beforeEach(async () => { await createSignalsIndex(supertest); + await esArchiver.load('auditbeat/hosts'); }); afterEach(async () => { await deleteSignalsIndex(supertest); await deleteAllAlerts(supertest); await deleteAllRulesStatuses(es); + await esArchiver.unload('auditbeat/hosts'); }); it('should return an empty find statuses body correctly if no statuses are loaded', async () => {