[Detection Engine][FTR] Audit DE exceptions tests (#179706)

## Summary

Continues work on https://github.com/elastic/kibana/issues/169185 and
https://github.com/elastic/kibana/issues/151877 . There wasn't a ton of
folder restructure, just switching the tests to run on basic
license/essentials tier and some test renaming for clarity sake.
This commit is contained in:
Yara Tercero 2024-04-02 14:53:08 -07:00 committed by GitHub
parent 6b29552382
commit 9215efb6aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
44 changed files with 1300 additions and 1123 deletions

View file

@ -475,18 +475,18 @@ enabled:
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/date_numeric_types/trial_license_complete_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/date_numeric_types/trial_license_complete_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/ips/trial_license_complete_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/ips/trial_license_complete_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/keyword/trial_license_complete_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/keyword/trial_license_complete_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/long/trial_license_complete_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/long/trial_license_complete_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/text/trial_license_complete_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/text/trial_license_complete_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/date_numeric_types/basic_license_essentials_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/date_numeric_types/basic_license_essentials_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/ips/basic_license_essentials_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/ips/basic_license_essentials_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/keyword/basic_license_essentials_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/keyword/basic_license_essentials_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/long/basic_license_essentials_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/long/basic_license_essentials_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/text/basic_license_essentials_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/text/basic_license_essentials_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/basic_license_essentials_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/basic_license_essentials_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/configs/ess.config.ts

View file

@ -90,7 +90,7 @@ FORMATTED_KB_URL="${KB_URL/https:\/\//}"
# This is used in order to wait for the environment to be ready.
sleep 150
TEST_CLOUD=1 TEST_ES_URL="https://elastic:$PASSWORD@$FORMATTED_ES_URL:443" TEST_KIBANA_URL="https://elastic:$PASSWORD@$FORMATTED_KB_URL:443" yarn run $1
TEST_CLOUD=1 TEST_ES_URL="https://$USERNAME:$PASSWORD@$FORMATTED_ES_URL:443" TEST_KIBANA_URL="https://$USERNAME:$PASSWORD@$FORMATTED_KB_URL:443" yarn run $1
cmd_status=$?
echo "Exit code with status: $cmd_status"

View file

@ -104,6 +104,7 @@
"logs-*",
"packetbeat-*",
"winlogbeat-*",
"logstash-*",
".asset-criticality.asset-criticality-*"
],
"privileges": ["read", "write"]
@ -117,7 +118,7 @@
"privileges": ["read", "write"]
},
{
"names": ["metrics-endpoint.metadata_current_*", ".fleet-agents*", ".fleet-actions*"],
"names": ["metrics-endpoint.metadata_current_*", ".fleet-agents*", ".fleet-actions*", "risk-score.risk-score-*"],
"privileges": ["read"]
}
],
@ -143,9 +144,14 @@
"file_operations_all"
],
"securitySolutionCases": ["all"],
"securitySolutionAssistant": ["all"],
"actions": ["read"],
"builtInAlerts": ["all"],
"osquery": ["all"]
"osquery": ["all"],
"discover": ["all"],
"dashboard": ["all"],
"maps": ["all"],
"visualize": ["all"]
},
"spaces": ["*"],
"base": []

View file

@ -74,41 +74,41 @@
"telemetry:server:ess": "npm run initialize-server:dr telemetry ess",
"telemetry:runner:ess": "npm run run-tests:dr telemetry ess essEnv",
"exception_workflows:server:serverless": "npm run initialize-server:de exceptions/workflows serverless",
"exception_workflows:runner:serverless": "npm run run-tests:de exceptions/workflows serverless serverlessEnv",
"exception_workflows:qa:serverless": "npm run run-tests:de exceptions/workflows serverless qaEnv",
"exception_workflows:server:ess": "npm run initialize-server:de exceptions/workflows ess",
"exception_workflows:runner:ess": "npm run run-tests:de exceptions/workflows ess essEnv",
"exception_workflows:essentials:server:serverless": "npm run initialize-server:de:basic_essentials exceptions/workflows serverless",
"exception_workflows:essentials:runner:serverless": "npm run run-tests:de:basic_essentials exceptions/workflows serverless serverlessEnv",
"exception_workflows:essentials:qa:serverless": "npm run run-tests:de:basic_essentials exceptions/workflows serverless qaEnv",
"exception_workflows:basic:server:ess": "npm run initialize-server:de:basic_essentials exceptions/workflows ess",
"exception_workflows:basic:runner:ess": "npm run run-tests:de:basic_essentials exceptions/workflows ess essEnv",
"exception_operators_date_numeric_types:server:serverless": "npm run initialize-server:de exceptions/operators_data_types/date_numeric_types serverless",
"exception_operators_date_numeric_types:runner:serverless": "npm run run-tests:de exceptions/operators_data_types/date_numeric_types serverless serverlessEnv",
"exception_operators_date_numeric_types:qa:serverless": "npm run run-tests:de exceptions/operators_data_types/date_numeric_types serverless qaEnv",
"exception_operators_date_numeric_types:server:ess": "npm run initialize-server:de exceptions/operators_data_types/date_numeric_types ess",
"exception_operators_date_numeric_types:runner:ess": "npm run run-tests:de exceptions/operators_data_types/date_numeric_types ess essEnv",
"exception_operators_date_numeric_types:essentials:server:serverless": "npm run initialize-server:de:basic_essentials exceptions/operators_data_types/date_numeric_types serverless",
"exception_operators_date_numeric_types:essentials:runner:serverless": "npm run run-tests:de:basic_essentials exceptions/operators_data_types/date_numeric_types serverless serverlessEnv",
"exception_operators_date_numeric_types:essentials:qa:serverless": "npm run run-tests:de:basic_essentials exceptions/operators_data_types/date_numeric_types serverless qaEnv",
"exception_operators_date_numeric_types:basic:server:ess": "npm run initialize-server:de:basic_essentials exceptions/operators_data_types/date_numeric_types ess",
"exception_operators_date_numeric_types:basic:runner:ess": "npm run run-tests:de:basic_essentials exceptions/operators_data_types/date_numeric_types ess essEnv",
"exception_operators_keyword:server:serverless": "npm run initialize-server:de exceptions/operators_data_types/keyword serverless",
"exception_operators_keyword:runner:serverless": "npm run run-tests:de exceptions/operators_data_types/keyword serverless serverlessEnv",
"exception_operators_keyword:qa:serverless": "npm run run-tests:de exceptions/operators_data_types/keyword serverless qaEnv",
"exception_operators_keyword:server:ess": "npm run initialize-server:de exceptions/operators_data_types/keyword ess",
"exception_operators_keyword:runner:ess": "npm run run-tests:de exceptions/operators_data_types/keyword ess essEnv",
"exception_operators_keyword:essentials:server:serverless": "npm run initialize-server:de:basic_essentials exceptions/operators_data_types/keyword serverless",
"exception_operators_keyword:essentials:runner:serverless": "npm run run-tests:de:basic_essentials exceptions/operators_data_types/keyword serverless serverlessEnv",
"exception_operators_keyword:essentials:qa:serverless": "npm run run-tests:de:basic_essentials exceptions/operators_data_types/keyword serverless qaEnv",
"exception_operators_keyword:basic:server:ess": "npm run initialize-server:de:basic_essentials exceptions/operators_data_types/keyword ess",
"exception_operators_keyword:basic:runner:ess": "npm run run-tests:de:basic_essentials exceptions/operators_data_types/keyword ess essEnv",
"exception_operators_ips:server:serverless": "npm run initialize-server:de exceptions/operators_data_types/ips serverless",
"exception_operators_ips:runner:serverless": "npm run run-tests:de exceptions/operators_data_types/ips serverless serverlessEnv",
"exception_operators_ips:qa:serverless": "npm run run-tests:de exceptions/operators_data_types/ips serverless qaEnv",
"exception_operators_ips:server:ess": "npm run initialize-server:de exceptions/operators_data_types/ips ess",
"exception_operators_ips:runner:ess": "npm run run-tests:de exceptions/operators_data_types/ips ess essEnv",
"exception_operators_ips:essentials:server:serverless": "npm run initialize-server:de:basic_essentials exceptions/operators_data_types/ips serverless",
"exception_operators_ips:essentials:runner:serverless": "npm run run-tests:de:basic_essentials exceptions/operators_data_types/ips serverless serverlessEnv",
"exception_operators_ips:essentials:qa:serverless": "npm run run-tests:de:basic_essentials exceptions/operators_data_types/ips serverless qaEnv",
"exception_operators_ips:basic:server:ess": "npm run initialize-server:de:basic_essentials exceptions/operators_data_types/ips ess",
"exception_operators_ips:basic:runner:ess": "npm run run-tests:de:basic_essentials exceptions/operators_data_types/ips ess essEnv",
"exception_operators_long:server:serverless": "npm run initialize-server:de exceptions/operators_data_types/long serverless",
"exception_operators_long:runner:serverless": "npm run run-tests:de exceptions/operators_data_types/long serverless serverlessEnv",
"exception_operators_long:qa:serverless": "npm run run-tests:de exceptions/operators_data_types/long serverless qaEnv",
"exception_operators_long:server:ess": "npm run initialize-server:de exceptions/operators_data_types/long ess",
"exception_operators_long:runner:ess": "npm run run-tests:de exceptions/operators_data_types/long ess essEnv",
"exception_operators_long:essentials:server:serverless": "npm run initialize-server:de:basic_essentials exceptions/operators_data_types/long serverless",
"exception_operators_long:essentials:runner:serverless": "npm run run-tests:de:basic_essentials exceptions/operators_data_types/long serverless serverlessEnv",
"exception_operators_long:essentials:qa:serverless": "npm run run-tests:de:basic_essentials exceptions/operators_data_types/long serverless qaEnv",
"exception_operators_long:basic:server:ess": "npm run initialize-server:de:basic_essentials exceptions/operators_data_types/long ess",
"exception_operators_long:basic:runner:ess": "npm run run-tests:de:basic_essentials exceptions/operators_data_types/long ess essEnv",
"exception_operators_text:server:serverless": "npm run initialize-server:de exceptions/operators_data_types/text serverless",
"exception_operators_text:runner:serverless": "npm run run-tests:de exceptions/operators_data_types/text serverless serverlessEnv",
"exception_operators_text:qa:serverless": "npm run run-tests:de exceptions/operators_data_types/text serverless qaEnv",
"exception_operators_text:server:ess": "npm run initialize-server:de exceptions/operators_data_types/text ess",
"exception_operators_text:runner:ess": "npm run run-tests:de exceptions/operators_data_types/text ess essEnv",
"exception_operators_text:essentials:server:serverless": "npm run initialize-server:de:basic_essentials exceptions/operators_data_types/text serverless",
"exception_operators_text:essentials:runner:serverless": "npm run run-tests:de:basic_essentials exceptions/operators_data_types/text serverless serverlessEnv",
"exception_operators_text:essentials:qa:serverless": "npm run run-tests:de:basic_essentials exceptions/operators_data_types/text serverless qaEnv",
"exception_operators_text:basic:server:ess": "npm run initialize-server:de:basic_essentials exceptions/operators_data_types/text ess",
"exception_operators_text:basic:runner:ess": "npm run run-tests:de:basic_essentials exceptions/operators_data_types/text ess essEnv",
"actions:server:serverless": "npm run initialize-server:de actions serverless",
"actions:runner:serverless": "npm run run-tests:de actions serverless serverlessEnv",

View file

@ -72,7 +72,6 @@ export default ({ getService }: FtrProviderContext) => {
await createAlertsIndex(supertest, log);
});
after(async () => {
// await esArchiver.unload('x-pack/test/functional/es_archives/endpoint/resolver/signals');
await deleteAllAlerts(supertest, log, es);
});

View file

@ -9,7 +9,7 @@ import { FtrConfigProviderContext } from '@kbn/test';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const functionalConfig = await readConfigFile(
require.resolve('../../../../../../../../config/ess/config.base.trial')
require.resolve('../../../../../../../../config/ess/config.base.basic')
);
return {
@ -17,7 +17,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
testFiles: [require.resolve('..')],
junit: {
reportName:
'Detection Engine - Exception Operators Date & Numeric Types Integration Tests - ESS Env - Trial License',
'Detection Engine - Exception Operators Date & Numeric Types Integration Tests - ESS Env - Basic License',
},
};
}

View file

@ -5,12 +5,12 @@
* 2.0.
*/
import { createTestConfig } from '../../../../../../../../config/serverless/config.base';
import { createTestConfig } from '../../../../../../../../config/serverless/config.base.essentials';
export default createTestConfig({
testFiles: [require.resolve('..')],
junit: {
reportName:
'Detection Engine - Exception Operators Date & Numeric Types Integration Tests - Serverless Env - Complete Tier',
'Detection Engine - Exception Operators Date & Numeric Types Integration Tests - Serverless Env - Essentials Tier',
},
});

View file

@ -9,7 +9,7 @@ import { FtrConfigProviderContext } from '@kbn/test';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const functionalConfig = await readConfigFile(
require.resolve('../../../../../../../../config/ess/config.base.trial')
require.resolve('../../../../../../../../config/ess/config.base.basic')
);
return {
@ -17,7 +17,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
testFiles: [require.resolve('..')],
junit: {
reportName:
'Detection Engine - Exception Operators IP Types Integration Tests - ESS Env - Trial License',
'Detection Engine - Exception Operators IP Types Integration Tests - ESS Env - Basic License',
},
};
}

View file

@ -5,12 +5,12 @@
* 2.0.
*/
import { createTestConfig } from '../../../../../../../../config/serverless/config.base';
import { createTestConfig } from '../../../../../../../../config/serverless/config.base.essentials';
export default createTestConfig({
testFiles: [require.resolve('..')],
junit: {
reportName:
'Detection Engine - Exception Operators IP Types Integration Tests - Serverless Env - Complete Tier',
'Detection Engine - Exception Operators IP Types Integration Tests - Serverless Env - Essentials Tier',
},
});

View file

@ -9,7 +9,7 @@ import { FtrConfigProviderContext } from '@kbn/test';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const functionalConfig = await readConfigFile(
require.resolve('../../../../../../../../config/ess/config.base.trial')
require.resolve('../../../../../../../../config/ess/config.base.basic')
);
return {
@ -17,7 +17,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
testFiles: [require.resolve('..')],
junit: {
reportName:
'Detection Engine - Exception Operators Keyword Types Integration Tests - ESS Env - Trial License',
'Detection Engine - Exception Operators Keyword Types Integration Tests - ESS Env - Basic License',
},
};
}

View file

@ -5,12 +5,12 @@
* 2.0.
*/
import { createTestConfig } from '../../../../../../../../config/serverless/config.base';
import { createTestConfig } from '../../../../../../../../config/serverless/config.base.essentials';
export default createTestConfig({
testFiles: [require.resolve('..')],
junit: {
reportName:
'Detection Engine - Exception Operators Keyword Types Integration Tests - Serverless Env - Complete Tier',
'Detection Engine - Exception Operators Keyword Types Integration Tests - Serverless Env - Essentials Tier',
},
});

View file

@ -9,7 +9,7 @@ import { FtrConfigProviderContext } from '@kbn/test';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const functionalConfig = await readConfigFile(
require.resolve('../../../../../../../../config/ess/config.base.trial')
require.resolve('../../../../../../../../config/ess/config.base.basic')
);
return {
@ -17,7 +17,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
testFiles: [require.resolve('..')],
junit: {
reportName:
'Detection Engine - Exception Operators Long Types Integration Tests - ESS Env - Trial License',
'Detection Engine - Exception Operators Long Types Integration Tests - ESS Env - Basic License',
},
};
}

View file

@ -5,12 +5,12 @@
* 2.0.
*/
import { createTestConfig } from '../../../../../../../../config/serverless/config.base';
import { createTestConfig } from '../../../../../../../../config/serverless/config.base.essentials';
export default createTestConfig({
testFiles: [require.resolve('..')],
junit: {
reportName:
'Detection Engine - Exception Operators Long Types Integration Tests - Serverless Env - Complete Tier',
'Detection Engine - Exception Operators Long Types Integration Tests - Serverless Env - Essentials Tier',
},
});

View file

@ -9,7 +9,7 @@ import { FtrConfigProviderContext } from '@kbn/test';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const functionalConfig = await readConfigFile(
require.resolve('../../../../../../../../config/ess/config.base.trial')
require.resolve('../../../../../../../../config/ess/config.base.basic')
);
return {
@ -17,7 +17,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
testFiles: [require.resolve('..')],
junit: {
reportName:
'Detection Engine - Exception Operators Text Types Integration Tests - ESS Env - Trial License',
'Detection Engine - Exception Operators Text Types Integration Tests - ESS Env - Basic License',
},
};
}

View file

@ -5,12 +5,12 @@
* 2.0.
*/
import { createTestConfig } from '../../../../../../../../config/serverless/config.base';
import { createTestConfig } from '../../../../../../../../config/serverless/config.base.essentials';
export default createTestConfig({
testFiles: [require.resolve('..')],
junit: {
reportName:
'Detection Engine - Exception Operators Text Types Integration Tests - Serverless Env - Complete Tier',
'Detection Engine - Exception Operators Text Types Integration Tests - Serverless Env - Essentials Tier',
},
});

View file

@ -9,7 +9,7 @@ import { FtrConfigProviderContext } from '@kbn/test';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const functionalConfig = await readConfigFile(
require.resolve('../../../../../../../config/ess/config.base.trial')
require.resolve('../../../../../../../config/ess/config.base.basic')
);
return {
@ -17,7 +17,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
testFiles: [require.resolve('..')],
junit: {
reportName:
'Detection Engine - Exception Workflows Integration Tests - ESS Env - Trial License',
'Detection Engine - Exception Workflows Integration Tests - ESS Env - Basic License',
},
};
}

View file

@ -5,12 +5,12 @@
* 2.0.
*/
import { createTestConfig } from '../../../../../../../config/serverless/config.base';
import { createTestConfig } from '../../../../../../../config/serverless/config.base.essentials';
export default createTestConfig({
testFiles: [require.resolve('..')],
junit: {
reportName:
'Detection Engine - Exception Workflows Integration Tests - Serverless Env - Complete Tier',
'Detection Engine - Exception Workflows Integration Tests - Serverless Env - Essentials Tier',
},
});

View file

@ -6,8 +6,6 @@
*/
import expect from '@kbn/expect';
import { Rule } from '@kbn/alerting-plugin/common';
import { BaseRuleParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema';
import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants';
import {
CreateExceptionListSchema,
@ -16,15 +14,7 @@ import {
ExceptionListTypeEnum,
} from '@kbn/securitysolution-io-ts-list-types';
import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock';
import {
fetchRule,
getSimpleRule,
createExceptionList,
getRuleSOById,
createRuleThroughAlertingEndpoint,
getRuleSavedObjectWithLegacyInvestigationFields,
checkInvestigationFieldSoValue,
} from '../../../../utils';
import { fetchRule, getSimpleRule, createExceptionList } from '../../../../utils';
import {
createRule,
createAlertsIndex,
@ -58,7 +48,7 @@ export default ({ getService }: FtrProviderContext) => {
const config = getService('config');
const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username');
describe('@serverless @ess create_rule_exception_route', () => {
describe('@serverless @ess create "rule_default" exceptions', () => {
before(async () => {
await createAlertsIndex(supertest, log);
});
@ -254,54 +244,5 @@ export default ({ getService }: FtrProviderContext) => {
status_code: 500,
});
});
// TODO: When available this tag should be @skipInServerless
// This use case is not relevant to serverless.
describe('@brokenInServerless legacy investigation_fields', () => {
let ruleWithLegacyInvestigationField: Rule<BaseRuleParams>;
beforeEach(async () => {
await deleteAllRules(supertest, log);
ruleWithLegacyInvestigationField = await createRuleThroughAlertingEndpoint(
supertest,
getRuleSavedObjectWithLegacyInvestigationFields()
);
});
afterEach(async () => {
await deleteAllRules(supertest, log);
});
it('creates and associates a `rule_default` exception list to a rule with a legacy investigation_field', async () => {
await supertest
.post(`${DETECTION_ENGINE_RULES_URL}/${ruleWithLegacyInvestigationField.id}/exceptions`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '2023-10-31')
.send({
items: [getRuleExceptionItemMock()],
})
.expect(200);
/**
* Confirm type on SO so that it's clear in the tests whether it's expected that
* the SO itself is migrated to the inteded object type, or if the transformation is
* happening just on the response. In this case, change will
* NOT include a migration on SO.
*/
const {
hits: {
hits: [{ _source: ruleSO }],
},
} = await getRuleSOById(es, ruleWithLegacyInvestigationField.id);
const isInvestigationFieldMigratedInSo = await checkInvestigationFieldSoValue(ruleSO, {
field_names: ['client.address', 'agent.name'],
});
expect(
ruleSO?.alert.params.exceptionsList.some((list) => list.type === 'rule_default')
).to.eql(true);
expect(isInvestigationFieldMigratedInSo).to.eql(false);
});
});
});
};

View file

@ -0,0 +1,104 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { Rule } from '@kbn/alerting-plugin/common';
import { BaseRuleParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema';
import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants';
import { CreateRuleExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import {
getRuleSOById,
createRuleThroughAlertingEndpoint,
getRuleSavedObjectWithLegacyInvestigationFields,
checkInvestigationFieldSoValue,
} from '../../../../utils';
import {
createAlertsIndex,
deleteAllRules,
deleteAllAlerts,
} from '../../../../../../../common/utils/security_solution';
import { deleteAllExceptions } from '../../../../../lists_and_exception_lists/utils';
import { FtrProviderContext } from '../../../../../../ftr_provider_context';
const getRuleExceptionItemMock = (): CreateRuleExceptionListItemSchema => ({
description: 'Exception item for rule default exception list',
entries: [
{
field: 'some.not.nested.field',
operator: 'included',
type: 'match',
value: 'some value',
},
],
name: 'Sample exception item',
type: 'simple',
});
export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertest');
const log = getService('log');
const es = getService('es');
describe('@ess create rule exception routes, ESS specific logic', () => {
before(async () => {
await createAlertsIndex(supertest, log);
});
after(async () => {
await deleteAllExceptions(supertest, log);
await deleteAllAlerts(supertest, log, es);
await deleteAllRules(supertest, log);
});
describe('legacy investigation_fields', () => {
let ruleWithLegacyInvestigationField: Rule<BaseRuleParams>;
beforeEach(async () => {
await deleteAllRules(supertest, log);
ruleWithLegacyInvestigationField = await createRuleThroughAlertingEndpoint(
supertest,
getRuleSavedObjectWithLegacyInvestigationFields()
);
});
afterEach(async () => {
await deleteAllRules(supertest, log);
});
it('creates and associates a `rule_default` exception list to a rule with a legacy investigation_field', async () => {
await supertest
.post(`${DETECTION_ENGINE_RULES_URL}/${ruleWithLegacyInvestigationField.id}/exceptions`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '2023-10-31')
.send({
items: [getRuleExceptionItemMock()],
})
.expect(200);
/**
* Confirm type on SO so that it's clear in the tests whether it's expected that
* the SO itself is migrated to the inteded object type, or if the transformation is
* happening just on the response. In this case, change will
* NOT include a migration on SO.
*/
const {
hits: {
hits: [{ _source: ruleSO }],
},
} = await getRuleSOById(es, ruleWithLegacyInvestigationField.id);
const isInvestigationFieldMigratedInSo = await checkInvestigationFieldSoValue(ruleSO, {
field_names: ['client.address', 'agent.name'],
});
expect(
ruleSO?.alert.params.exceptionsList.some((list) => list.type === 'rule_default')
).to.eql(true);
expect(isInvestigationFieldMigratedInSo).to.eql(false);
});
});
});
};

View file

@ -30,7 +30,7 @@ export default ({ getService }: FtrProviderContext) => {
const log = getService('log');
const supertestWithoutAuth = getService('supertestWithoutAuth');
describe('@serverless @ess @brokenInServerless role_based_add_edit_comments', () => {
describe('@ess exception item comments', () => {
const socManager = ROLES.soc_manager;
const detectionAdmin = ROLES.detections_admin;

View file

@ -0,0 +1,222 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
/* eslint-disable @typescript-eslint/naming-convention */
import expect from 'expect';
import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants';
import {
getCreateExceptionListDetectionSchemaMock,
getCreateExceptionListMinimalSchemaMock,
} from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock';
import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock';
import { ROLES } from '@kbn/security-solution-plugin/common/test';
import { getUpdateMinimalExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_item_schema.mock';
import { UpdateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { deleteAllExceptions } from '../../../../../lists_and_exception_lists/utils';
import { FtrProviderContext } from '../../../../../../ftr_provider_context';
export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertest');
const log = getService('log');
const supertestWithoutAuth = getService('supertestWithoutAuth');
// @skipInQA purposefully - only running tests in MKI whose failure should block release
describe('@serverless @skipInQA exception item comments - serverless specific behavior', () => {
describe('Rule Exceptions', () => {
afterEach(async () => {
await deleteAllExceptions(supertest, log);
});
it('Add comment on a new exception, add another comment has unicode from a different user', async () => {
await supertest
.post(EXCEPTION_LIST_URL)
.set('kbn-xsrf', 'true')
.send(getCreateExceptionListDetectionSchemaMock())
.expect(200);
const { os_types, ...ruleException } = getCreateExceptionListItemMinimalSchemaMock();
// Add comment by another user
await supertestWithoutAuth
.post(EXCEPTION_LIST_ITEM_URL)
.auth(ROLES.t3_analyst, 'changeme')
.set('kbn-xsrf', 'true')
.send({
...ruleException,
comments: [{ comment: 'Comment by user@t3_analyst' }],
})
.expect(200);
const { body: items } = await supertest
.get(
`${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${
getCreateExceptionListMinimalSchemaMock().list_id
}`
)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
// Validate the first user comment
expect(items.total).toEqual(1);
const [item] = items.data;
const t3AnalystComments = item.comments;
expect(t3AnalystComments.length).toEqual(1);
expect(t3AnalystComments[0]).toEqual(
expect.objectContaining({
created_by: 't3_analyst',
comment: 'Comment by user@t3_analyst',
})
);
const expectedId = item.id;
// Update exception comment by different user
const { item_id: _, ...updateItemWithoutItemId } =
getUpdateMinimalExceptionListItemSchemaMock();
const updatePayload: UpdateExceptionListItemSchema = {
...updateItemWithoutItemId,
comments: [
...(updateItemWithoutItemId.comments || []),
{ comment: 'Comment by elastic_serverless' },
],
id: expectedId,
};
await supertest
.put(EXCEPTION_LIST_ITEM_URL)
.set('kbn-xsrf', 'true')
.send(updatePayload)
.expect(200);
const { body: itemsAfterUpdate } = await supertest
.get(
`${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${
getCreateExceptionListMinimalSchemaMock().list_id
}`
)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
const [itemAfterUpdate] = itemsAfterUpdate.data;
const comments = itemAfterUpdate.comments;
expect(comments.length).toEqual(2);
expect(comments).toEqual(
expect.arrayContaining([
expect.objectContaining({
created_by: 't3_analyst',
comment: 'Comment by user@t3_analyst',
}),
expect.objectContaining({
created_by: 'elastic_serverless',
comment: 'Comment by elastic_serverless',
}),
])
);
});
});
describe('Endpoint Exceptions', () => {
afterEach(async () => {
await deleteAllExceptions(supertest, log);
});
it('Add comment on a new exception, add another comment has unicode from a different user', async () => {
await supertest
.post(EXCEPTION_LIST_URL)
.set('kbn-xsrf', 'true')
.send(getCreateExceptionListMinimalSchemaMock())
.expect(200);
// Add comment by the t3 analyst
await supertestWithoutAuth
.post(EXCEPTION_LIST_ITEM_URL)
.auth(ROLES.t3_analyst, 'changeme')
.set('kbn-xsrf', 'true')
.send({
...getCreateExceptionListItemMinimalSchemaMock(),
comments: [{ comment: 'Comment by user@t3_analyst' }],
})
.expect(200);
const { body: items } = await supertest
.get(
`${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${
getCreateExceptionListMinimalSchemaMock().list_id
}`
)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
// Validate the first user comment
expect(items.total).toEqual(1);
const [item] = items.data;
const t3AnalystComments = item.comments;
expect(t3AnalystComments.length).toEqual(1);
expect(t3AnalystComments[0]).toEqual(
expect.objectContaining({
created_by: 't3_analyst',
comment: 'Comment by user@t3_analyst',
})
);
const expectedId = item.id;
// Update exception comment by different user
const { item_id: _, ...updateItemWithoutItemId } =
getUpdateMinimalExceptionListItemSchemaMock();
const updatePayload: UpdateExceptionListItemSchema = {
...updateItemWithoutItemId,
comments: [
...(updateItemWithoutItemId.comments || []),
{ comment: 'Comment by elastic_serverless' },
],
id: expectedId,
};
await supertest
.put(EXCEPTION_LIST_ITEM_URL)
.set('kbn-xsrf', 'true')
.send(updatePayload)
.expect(200);
const { body: itemsAfterUpdate } = await supertest
.get(
`${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${
getCreateExceptionListMinimalSchemaMock().list_id
}`
)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
const [itemAfterUpdate] = itemsAfterUpdate.data;
const comments = itemAfterUpdate.comments;
expect(comments.length).toEqual(2);
expect(comments).toEqual(
expect.arrayContaining([
expect.objectContaining({
created_by: 't3_analyst',
comment: 'Comment by user@t3_analyst',
}),
expect.objectContaining({
created_by: 'elastic_serverless',
comment: 'Comment by elastic_serverless',
}),
])
);
});
});
});
};

View file

@ -37,7 +37,8 @@ export default ({ getService }: FtrProviderContext) => {
const log = getService('log');
const es = getService('es');
describe('@serverless @ess Synchronizations', () => {
// @skipInQA purposefully - only running tests in MKI whose failure should block release
describe('@serverless @ess @skipInQA exceptions data integrity', () => {
afterEach(async () => {
await deleteAllAlerts(supertest, log, es);
await deleteAllRules(supertest, log);

View file

@ -43,11 +43,11 @@ export default ({ getService }: FtrProviderContext) => {
after(async () => {
await deleteAllAlerts(supertest, log, es);
await deleteAllRules(supertest, log);
});
afterEach(async () => {
await deleteAllExceptions(supertest, log);
await deleteAllRules(supertest, log);
});
it('returns empty array per list_id if no references are found', async () => {
@ -67,6 +67,7 @@ export default ({ getService }: FtrProviderContext) => {
.get(DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.set('X-Elastic-Internal-Origin', 'Kibana')
.query({
ids: `${exceptionList.id}`,
list_ids: `${exceptionList.list_id}`,
@ -121,6 +122,7 @@ export default ({ getService }: FtrProviderContext) => {
.get(DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.set('X-Elastic-Internal-Origin', 'Kibana')
.query({
ids: `1234`,
list_ids: `i_dont_exist`,
@ -168,6 +170,7 @@ export default ({ getService }: FtrProviderContext) => {
.get(DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.set('X-Elastic-Internal-Origin', 'Kibana')
.query({
ids: `${exceptionList.id},${exceptionList2.id}`,
list_ids: `${exceptionList.list_id},${exceptionList2.list_id}`,
@ -217,6 +220,7 @@ export default ({ getService }: FtrProviderContext) => {
.get(DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.set('X-Elastic-Internal-Origin', 'Kibana')
.query({
namespace_types: 'single,agnostic',
})

View file

@ -8,11 +8,14 @@ import { FtrProviderContext } from '../../../../../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('Detection Engine - Exception workflows APIs', function () {
loadTestFile(require.resolve('./create_rule_exceptions'));
loadTestFile(require.resolve('./create_rule_exceptions'));
loadTestFile(require.resolve('./role_based_rule_exceptions_workflows'));
loadTestFile(require.resolve('./exception_comments_ess'));
loadTestFile(require.resolve('./exception_comments_serverless'));
loadTestFile(require.resolve('./create_endpoint_exceptions'));
loadTestFile(require.resolve('./role_based_add_edit_comments'));
loadTestFile(require.resolve('./rule_exception_synchronizations'));
loadTestFile(require.resolve('./create_rule_exceptions_ess'));
loadTestFile(require.resolve('./create_rule_exceptions'));
loadTestFile(require.resolve('./exceptions_data_integrity'));
loadTestFile(require.resolve('./find_rule_exception_references'));
loadTestFile(require.resolve('./rule_exceptions_execution'));
loadTestFile(require.resolve('./prebuilt_rules'));
});
}

View file

@ -0,0 +1,402 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
/* eslint-disable @typescript-eslint/naming-convention */
import expect from 'expect';
import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock';
import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants';
import { ELASTIC_SECURITY_RULE_ID } from '@kbn/security-solution-plugin/common';
import {
fetchRule,
createExceptionList,
removeServerGeneratedProperties,
downgradeImmutableRule,
installMockPrebuiltRules,
findImmutableRuleById,
getPrebuiltRulesAndTimelinesStatus,
SAMPLE_PREBUILT_RULES,
} from '../../../../utils';
import {
createAlertsIndex,
deleteAllRules,
deleteAllAlerts,
} from '../../../../../../../common/utils/security_solution';
import { deleteAllExceptions } from '../../../../../lists_and_exception_lists/utils';
import { FtrProviderContext } from '../../../../../../ftr_provider_context';
export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertest');
const log = getService('log');
const es = getService('es');
// @skipInQA purposefully - only running tests in MKI whose failure should block release
describe('@serverless @ess @skipInQA exceptions workflows for prebuilt rules', () => {
describe('creating rules with exceptions', () => {
beforeEach(async () => {
await createAlertsIndex(supertest, log);
});
afterEach(async () => {
await deleteAllAlerts(supertest, log, es);
await deleteAllRules(supertest, log);
await deleteAllExceptions(supertest, log);
});
it('should allow removing an exception list from an immutable rule through patch', async () => {
await installMockPrebuiltRules(supertest, es);
// This rule has an existing exceptions_list that we are going to use
const immutableRule = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
expect(immutableRule.exceptions_list.length).toBeGreaterThan(0); // make sure we have at least one exceptions_list
// remove the exceptions list as a user is allowed to remove it from an immutable rule
await supertest
.patch(DETECTION_ENGINE_RULES_URL)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '2023-10-31')
.send({ rule_id: ELASTIC_SECURITY_RULE_ID, exceptions_list: [] })
.expect(200);
const immutableRuleSecondTime = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
expect(immutableRuleSecondTime.exceptions_list.length).toEqual(0);
});
it('should allow adding a second exception list to an immutable rule through patch', async () => {
await installMockPrebuiltRules(supertest, es);
const { id, list_id, namespace_type, type } = await createExceptionList(
supertest,
log,
getCreateExceptionListMinimalSchemaMock()
);
// This rule has an existing exceptions_list that we are going to use
const immutableRule = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
expect(immutableRule.exceptions_list.length).toBeGreaterThan(0); // make sure we have at least one
// add a second exceptions list as a user is allowed to add a second list to an immutable rule
await supertest
.patch(DETECTION_ENGINE_RULES_URL)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '2023-10-31')
.send({
rule_id: ELASTIC_SECURITY_RULE_ID,
exceptions_list: [
...immutableRule.exceptions_list,
{
id,
list_id,
namespace_type,
type,
},
],
})
.expect(200);
const immutableRuleSecondTime = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
expect(immutableRuleSecondTime.exceptions_list.length).toEqual(2);
});
it('should override any updates to pre-packaged rules if the user removes the exception list through the API but the new version of a rule has an exception list again', async () => {
await installMockPrebuiltRules(supertest, es);
// This rule has an existing exceptions_list that we are going to use
const immutableRule = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
expect(immutableRule.exceptions_list.length).toBeGreaterThan(0); // make sure we have at least one
await supertest
.patch(DETECTION_ENGINE_RULES_URL)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '2023-10-31')
.send({ rule_id: ELASTIC_SECURITY_RULE_ID, exceptions_list: [] })
.expect(200);
await downgradeImmutableRule(es, log, ELASTIC_SECURITY_RULE_ID);
await installMockPrebuiltRules(supertest, es);
const immutableRuleSecondTime = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
// We should have a length of 1 and it should be the same as our original before we tried to remove it using patch
expect(immutableRuleSecondTime.exceptions_list.length).toEqual(1);
expect(immutableRuleSecondTime.exceptions_list).toEqual(immutableRule.exceptions_list);
});
it('should merge back an exceptions_list if it was removed from the immutable rule through PATCH', async () => {
await installMockPrebuiltRules(supertest, es);
const { id, list_id, namespace_type, type } = await createExceptionList(
supertest,
log,
getCreateExceptionListMinimalSchemaMock()
);
// This rule has an existing exceptions_list that we are going to ensure does not stomp on our existing rule
const immutableRule = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
expect(immutableRule.exceptions_list.length).toBeGreaterThan(0); // make sure we have at least one
// remove the exception list and only have a single list that is not an endpoint_list
await supertest
.patch(DETECTION_ENGINE_RULES_URL)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '2023-10-31')
.send({
rule_id: ELASTIC_SECURITY_RULE_ID,
exceptions_list: [
{
id,
list_id,
namespace_type,
type,
},
],
})
.expect(200);
await downgradeImmutableRule(es, log, ELASTIC_SECURITY_RULE_ID);
await installMockPrebuiltRules(supertest, es);
const immutableRuleSecondTime = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
expect(immutableRuleSecondTime.exceptions_list).toEqual([
...immutableRule.exceptions_list,
{
id,
list_id,
namespace_type,
type,
},
]);
});
it('should NOT add an extra exceptions_list that already exists on a rule during an upgrade', async () => {
await installMockPrebuiltRules(supertest, es);
// This rule has an existing exceptions_list that we are going to ensure does not stomp on our existing rule
const immutableRule = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
expect(immutableRule.exceptions_list.length).toBeGreaterThan(0); // make sure we have at least one
await downgradeImmutableRule(es, log, ELASTIC_SECURITY_RULE_ID);
await installMockPrebuiltRules(supertest, es);
const immutableRuleSecondTime = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
// The installed rule should have both the original immutable exceptions list back and the
// new list the user added.
expect(immutableRuleSecondTime.exceptions_list).toEqual([...immutableRule.exceptions_list]);
});
it('should NOT allow updates to pre-packaged rules to overwrite existing exception based rules when the user adds an additional exception list', async () => {
await installMockPrebuiltRules(supertest, es);
const { id, list_id, namespace_type, type } = await createExceptionList(
supertest,
log,
getCreateExceptionListMinimalSchemaMock()
);
// This rule has an existing exceptions_list that we are going to ensure does not stomp on our existing rule
const immutableRule = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
// add a second exceptions list as a user is allowed to add a second list to an immutable rule
await supertest
.patch(DETECTION_ENGINE_RULES_URL)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '2023-10-31')
.send({
rule_id: ELASTIC_SECURITY_RULE_ID,
exceptions_list: [
...immutableRule.exceptions_list,
{
id,
list_id,
namespace_type,
type,
},
],
})
.expect(200);
await downgradeImmutableRule(es, log, ELASTIC_SECURITY_RULE_ID);
await installMockPrebuiltRules(supertest, es);
const immutableRuleSecondTime = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
// It should be the same as what the user added originally
expect(immutableRuleSecondTime.exceptions_list).toEqual([
...immutableRule.exceptions_list,
{
id,
list_id,
namespace_type,
type,
},
]);
});
it('should not remove any exceptions added to a pre-packaged/immutable rule during an update if that rule has no existing exception lists', async () => {
await installMockPrebuiltRules(supertest, es);
// Create a new exception list
const { id, list_id, namespace_type, type } = await createExceptionList(
supertest,
log,
getCreateExceptionListMinimalSchemaMock()
);
// Find a rule without exceptions_list
const ruleWithoutExceptionList = SAMPLE_PREBUILT_RULES.find(
(rule) => !rule['security-rule'].exceptions_list
);
const ruleId = ruleWithoutExceptionList?.['security-rule'].rule_id;
if (!ruleId) {
throw new Error('Cannot find a rule without exceptions_list in the sample data');
}
const immutableRule = await fetchRule(supertest, { ruleId });
expect(immutableRule.exceptions_list.length).toEqual(0); // make sure we have no exceptions_list
// add a second exceptions list as a user is allowed to add a second list to an immutable rule
await supertest
.patch(DETECTION_ENGINE_RULES_URL)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '2023-10-31')
.send({
rule_id: ruleId,
exceptions_list: [
{
id,
list_id,
namespace_type,
type,
},
],
})
.expect(200);
await downgradeImmutableRule(es, log, ruleId);
await installMockPrebuiltRules(supertest, es);
const immutableRuleSecondTime = await fetchRule(supertest, { ruleId });
expect(immutableRuleSecondTime.exceptions_list).toEqual([
{
id,
list_id,
namespace_type,
type,
},
]);
});
it('should not change the immutable tags when adding a second exception list to an immutable rule through patch', async () => {
await installMockPrebuiltRules(supertest, es);
const { id, list_id, namespace_type, type } = await createExceptionList(
supertest,
log,
getCreateExceptionListMinimalSchemaMock()
);
// This rule has an existing exceptions_list that we are going to use
const immutableRule = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
expect(immutableRule.exceptions_list.length).toBeGreaterThan(0); // make sure we have at least one
// add a second exceptions list as a user is allowed to add a second list to an immutable rule
await supertest
.patch(DETECTION_ENGINE_RULES_URL)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '2023-10-31')
.send({
rule_id: ELASTIC_SECURITY_RULE_ID,
exceptions_list: [
...immutableRule.exceptions_list,
{
id,
list_id,
namespace_type,
type,
},
],
})
.expect(200);
const body = await findImmutableRuleById(supertest, log, ELASTIC_SECURITY_RULE_ID);
expect(body.data.length).toEqual(1); // should have only one length to the data set, otherwise we have duplicates or the tags were removed and that is incredibly bad.
const bodyToCompare = removeServerGeneratedProperties(body.data[0]);
expect(bodyToCompare.rule_id).toEqual(immutableRule.rule_id); // Rule id should not change with a a patch
expect(bodyToCompare.immutable).toEqual(immutableRule.immutable); // Immutable should always stay the same which is true and never flip to false.
expect(bodyToCompare.version).toEqual(immutableRule.version); // The version should never update on a patch
});
it('should not change count of prepacked rules when adding a second exception list to an immutable rule through patch. If this fails, suspect the immutable tags are not staying on the rule correctly.', async () => {
await installMockPrebuiltRules(supertest, es);
const { id, list_id, namespace_type, type } = await createExceptionList(
supertest,
log,
getCreateExceptionListMinimalSchemaMock()
);
// This rule has an existing exceptions_list that we are going to use
const immutableRule = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
expect(immutableRule.exceptions_list.length).toBeGreaterThan(0); // make sure we have at least one
// add a second exceptions list as a user is allowed to add a second list to an immutable rule
await supertest
.patch(DETECTION_ENGINE_RULES_URL)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '2023-10-31')
.send({
rule_id: ELASTIC_SECURITY_RULE_ID,
exceptions_list: [
...immutableRule.exceptions_list,
{
id,
list_id,
namespace_type,
type,
},
],
})
.expect(200);
const status = await getPrebuiltRulesAndTimelinesStatus(es, supertest);
expect(status.rules_not_installed).toEqual(0);
});
});
});
};

View file

@ -0,0 +1,479 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
/* eslint-disable @typescript-eslint/naming-convention */
import expect from 'expect';
import type { CreateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { LIST_URL } from '@kbn/securitysolution-list-constants';
import type {
RuleCreateProps,
EqlRuleCreateProps,
QueryRuleCreateProps,
ThreatMatchRuleCreateProps,
ThresholdRuleCreateProps,
} from '@kbn/security-solution-plugin/common/api/detection_engine';
import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock';
import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock';
import { EsArchivePathBuilder } from '../../../../../../es_archive_path_builder';
import {
getSimpleRule,
createExceptionList,
createExceptionListItem,
getThresholdRuleForAlertTesting,
getOpenAlerts,
createRuleWithExceptionEntries,
getEqlRuleForAlertTesting,
} from '../../../../utils';
import {
createAlertsIndex,
createRule,
deleteAllRules,
waitForRuleSuccess,
waitForAlertsToBePresent,
getAlertsByIds,
deleteAllAlerts,
} from '../../../../../../../common/utils/security_solution';
import {
createListsIndex,
deleteAllExceptions,
deleteListsIndex,
importFile,
} from '../../../../../lists_and_exception_lists/utils';
import { FtrProviderContext } from '../../../../../../ftr_provider_context';
export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
const log = getService('log');
const es = getService('es');
// TODO: add a new service for loading archiver files similar to "getService('es')"
const config = getService('config');
const isServerless = config.get('serverless');
const dataPathBuilder = new EsArchivePathBuilder(isServerless);
const path = dataPathBuilder.getPath('auditbeat/hosts');
describe('@serverless @ess rule exceptions execution', () => {
before(async () => {
await esArchiver.load(path);
});
after(async () => {
await esArchiver.unload(path);
});
describe('creating rules with exceptions', () => {
beforeEach(async () => {
await createAlertsIndex(supertest, log);
});
afterEach(async () => {
await deleteAllAlerts(supertest, log, es);
await deleteAllRules(supertest, log);
await deleteAllExceptions(supertest, log);
});
it('should be able to execute against an exception list that does not include valid entries and get back 10 alerts', async () => {
const { id, list_id, namespace_type, type } = await createExceptionList(
supertest,
log,
getCreateExceptionListMinimalSchemaMock()
);
const exceptionListItem: CreateExceptionListItemSchema = {
...getCreateExceptionListItemMinimalSchemaMock(),
entries: [
{
field: 'some.none.existent.field', // non-existent field where we should not exclude anything
operator: 'included',
type: 'match',
value: 'some value',
},
],
};
await createExceptionListItem(supertest, log, exceptionListItem);
const ruleWithException: RuleCreateProps = {
name: 'Simple Rule Query',
description: 'Simple Rule Query',
enabled: true,
risk_score: 1,
rule_id: 'rule-1',
severity: 'high',
index: ['auditbeat-*'],
type: 'query',
from: '1900-01-01T00:00:00.000Z',
query: 'host.name: "suricata-sensor-amsterdam"',
exceptions_list: [
{
id,
list_id,
namespace_type,
type,
},
],
};
const { id: createdId } = await createRule(supertest, log, ruleWithException);
await waitForRuleSuccess({ supertest, log, id: createdId });
await waitForAlertsToBePresent(supertest, log, 10, [createdId]);
const alertsOpen = await getAlertsByIds(supertest, log, [createdId]);
expect(alertsOpen.hits.hits.length).toEqual(10);
});
it('should be able to execute against an exception list that does include valid entries and get back 0 alerts', async () => {
const rule: QueryRuleCreateProps = {
name: 'Simple Rule Query',
description: 'Simple Rule Query',
enabled: true,
risk_score: 1,
rule_id: 'rule-1',
severity: 'high',
index: ['auditbeat-*'],
type: 'query',
from: '1900-01-01T00:00:00.000Z',
query: 'host.name: "suricata-sensor-amsterdam"',
};
const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [
[
{
field: 'host.name', // This matches the query above which will exclude everything
operator: 'included',
type: 'match',
value: 'suricata-sensor-amsterdam',
},
],
]);
const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule);
expect(alertsOpen.hits.hits.length).toEqual(0);
});
it('should be able to execute against an exception list that does include valid case sensitive entries and get back 0 alerts', async () => {
const rule: QueryRuleCreateProps = {
name: 'Simple Rule Query',
description: 'Simple Rule Query',
enabled: true,
risk_score: 1,
rule_id: 'rule-1',
severity: 'high',
index: ['auditbeat-*'],
type: 'query',
from: '1900-01-01T00:00:00.000Z',
query: 'host.name: "suricata-sensor-amsterdam"',
};
const rule2: QueryRuleCreateProps = {
name: 'Simple Rule Query',
description: 'Simple Rule Query',
enabled: true,
risk_score: 1,
rule_id: 'rule-2',
severity: 'high',
index: ['auditbeat-*'],
type: 'query',
from: '1900-01-01T00:00:00.000Z',
query: 'host.name: "suricata-sensor-amsterdam"',
};
const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [
[
{
field: 'host.os.name',
operator: 'included',
type: 'match_any',
value: ['ubuntu'],
},
],
]);
const createdRule2 = await createRuleWithExceptionEntries(supertest, log, rule2, [
[
{
field: 'host.os.name', // This matches the query above which will exclude everything
operator: 'included',
type: 'match_any',
value: ['ubuntu', 'Ubuntu'],
},
],
]);
const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule);
const alertsOpen2 = await getOpenAlerts(supertest, log, es, createdRule2);
// Expect alerts here because all values are "Ubuntu"
// and exception is one of ["ubuntu"]
expect(alertsOpen.hits.hits.length).toEqual(10);
// Expect no alerts here because all values are "Ubuntu"
// and exception is one of ["ubuntu", "Ubuntu"]
expect(alertsOpen2.hits.hits.length).toEqual(0);
});
it('generates no alerts when an exception is added for an EQL rule', async () => {
const rule: EqlRuleCreateProps = {
...getEqlRuleForAlertTesting(['auditbeat-*']),
query: 'configuration where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"',
};
const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [
[
{
field: 'host.id',
operator: 'included',
type: 'match',
value: '8cc95778cce5407c809480e8e32ad76b',
},
],
]);
const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule);
expect(alertsOpen.hits.hits.length).toEqual(0);
});
it('generates no alerts when an exception is added for a threshold rule', async () => {
const rule: ThresholdRuleCreateProps = {
...getThresholdRuleForAlertTesting(['auditbeat-*']),
threshold: {
field: 'host.id',
value: 700,
},
};
const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [
[
{
field: 'host.id',
operator: 'included',
type: 'match',
value: '8cc95778cce5407c809480e8e32ad76b',
},
],
]);
const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule);
expect(alertsOpen.hits.hits.length).toEqual(0);
});
it('generates no alerts when an exception is added for a threat match rule', async () => {
const rule: ThreatMatchRuleCreateProps = {
description: 'Detecting root and admin users',
name: 'Query with a rule id',
severity: 'high',
index: ['auditbeat-*'],
type: 'threat_match',
risk_score: 55,
language: 'kuery',
rule_id: 'rule-1',
from: '1900-01-01T00:00:00.000Z',
query: '*:*',
threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip
threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity
threat_mapping: [
// We match host.name against host.name
{
entries: [
{
field: 'host.name',
value: 'host.name',
type: 'mapping',
},
],
},
],
threat_filters: [],
};
const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [
[
{
field: 'source.ip',
operator: 'included',
type: 'match',
value: '188.166.120.93',
},
],
]);
const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule);
expect(alertsOpen.hits.hits.length).toEqual(0);
});
describe('rules with value list exceptions', () => {
beforeEach(async () => {
await createListsIndex(supertest, log);
});
afterEach(async () => {
await deleteListsIndex(supertest, log);
});
it('generates no alerts when a value list exception is added for a query rule', async () => {
const valueListId = 'value-list-id.txt';
await importFile(supertest, log, 'keyword', ['suricata-sensor-amsterdam'], valueListId);
const rule: QueryRuleCreateProps = {
name: 'Simple Rule Query',
description: 'Simple Rule Query',
enabled: true,
risk_score: 1,
rule_id: 'rule-1',
severity: 'high',
index: ['auditbeat-*'],
type: 'query',
from: '1900-01-01T00:00:00.000Z',
query: 'host.name: "suricata-sensor-amsterdam"',
};
const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [
[
{
field: 'host.name',
operator: 'included',
type: 'list',
list: {
id: valueListId,
type: 'keyword',
},
},
],
]);
const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule);
expect(alertsOpen.hits.hits.length).toEqual(0);
});
it('generates no alerts when a value list exception is added for a threat match rule', async () => {
const valueListId = 'value-list-id.txt';
await importFile(supertest, log, 'keyword', ['zeek-sensor-amsterdam'], valueListId);
const rule: ThreatMatchRuleCreateProps = {
description: 'Detecting root and admin users',
name: 'Query with a rule id',
severity: 'high',
index: ['auditbeat-*'],
type: 'threat_match',
risk_score: 55,
language: 'kuery',
rule_id: 'rule-1',
from: '1900-01-01T00:00:00.000Z',
query: '*:*',
threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip
threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity
threat_mapping: [
// We match host.name against host.name
{
entries: [
{
field: 'host.name',
value: 'host.name',
type: 'mapping',
},
],
},
],
threat_filters: [],
};
const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [
[
{
field: 'host.name',
operator: 'included',
type: 'list',
list: {
id: valueListId,
type: 'keyword',
},
},
],
]);
const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule);
expect(alertsOpen.hits.hits.length).toEqual(0);
});
it('generates no alerts when a value list exception is added for a threshold rule', async () => {
const valueListId = 'value-list-id.txt';
await importFile(supertest, log, 'keyword', ['zeek-sensor-amsterdam'], valueListId);
const rule: ThresholdRuleCreateProps = {
description: 'Detecting root and admin users',
name: 'Query with a rule id',
severity: 'high',
index: ['auditbeat-*'],
type: 'threshold',
risk_score: 55,
language: 'kuery',
rule_id: 'rule-1',
from: '1900-01-01T00:00:00.000Z',
query: 'host.name: "zeek-sensor-amsterdam"',
threshold: {
field: 'host.name',
value: 1,
},
};
const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [
[
{
field: 'host.name',
operator: 'included',
type: 'list',
list: {
id: valueListId,
type: 'keyword',
},
},
],
]);
const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule);
expect(alertsOpen.hits.hits.length).toEqual(0);
});
it('generates no alerts when a value list exception is added for an EQL rule', async () => {
const valueListId = 'value-list-id.txt';
await importFile(supertest, log, 'keyword', ['zeek-sensor-amsterdam'], valueListId);
const rule: EqlRuleCreateProps = {
...getEqlRuleForAlertTesting(['auditbeat-*']),
query: 'configuration where host.name=="zeek-sensor-amsterdam"',
};
const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [
[
{
field: 'host.name',
operator: 'included',
type: 'list',
list: {
id: valueListId,
type: 'keyword',
},
},
],
]);
const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule);
expect(alertsOpen.hits.hits.length).toEqual(0);
});
it('should Not allow deleting value list when there are references and ignoreReferences is false', async () => {
const valueListId = 'value-list-id.txt';
await importFile(supertest, log, 'keyword', ['suricata-sensor-amsterdam'], valueListId);
const rule: QueryRuleCreateProps = {
...getSimpleRule(),
query: 'host.name: "suricata-sensor-amsterdam"',
};
await createRuleWithExceptionEntries(supertest, log, rule, [
[
{
field: 'host.name',
operator: 'included',
type: 'list',
list: {
id: valueListId,
type: 'keyword',
},
},
],
]);
const deleteReferences = false;
const ignoreReferences = false;
// Delete the value list
await supertest
.delete(
`${LIST_URL}?deleteReferences=${deleteReferences}&id=${valueListId}&ignoreReferences=${ignoreReferences}`
)
.set('kbn-xsrf', 'true')
.send()
.expect(409);
});
});
});
});
};

View file

@ -1,984 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
/* eslint-disable @typescript-eslint/naming-convention */
import expect from 'expect';
import type { CreateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import {
EXCEPTION_LIST_ITEM_URL,
EXCEPTION_LIST_URL,
LIST_URL,
} from '@kbn/securitysolution-list-constants';
import type {
RuleCreateProps,
EqlRuleCreateProps,
QueryRuleCreateProps,
ThreatMatchRuleCreateProps,
ThresholdRuleCreateProps,
} from '@kbn/security-solution-plugin/common/api/detection_engine';
import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock';
import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock';
import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants';
import { ROLES } from '@kbn/security-solution-plugin/common/test';
import { ELASTIC_SECURITY_RULE_ID } from '@kbn/security-solution-plugin/common';
import { EsArchivePathBuilder } from '../../../../../../es_archive_path_builder';
import {
fetchRule,
getSimpleRule,
createExceptionList,
createExceptionListItem,
getThresholdRuleForAlertTesting,
getSimpleRuleOutput,
removeServerGeneratedProperties,
downgradeImmutableRule,
installMockPrebuiltRules,
findImmutableRuleById,
getPrebuiltRulesAndTimelinesStatus,
getOpenAlerts,
createRuleWithExceptionEntries,
getEqlRuleForAlertTesting,
SAMPLE_PREBUILT_RULES,
updateUsername,
} from '../../../../utils';
import {
createAlertsIndex,
createRule,
deleteAllRules,
waitForRuleSuccess,
waitForAlertsToBePresent,
getAlertsByIds,
deleteAllAlerts,
} from '../../../../../../../common/utils/security_solution';
import {
createListsIndex,
deleteAllExceptions,
deleteListsIndex,
importFile,
} from '../../../../../lists_and_exception_lists/utils';
import {
createUserAndRole,
deleteUserAndRole,
} from '../../../../../../../common/services/security_solution';
import { FtrProviderContext } from '../../../../../../ftr_provider_context';
export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertest');
const supertestWithoutAuth = getService('supertestWithoutAuth');
const esArchiver = getService('esArchiver');
const log = getService('log');
const es = getService('es');
// TODO: add a new service for loading archiver files similar to "getService('es')"
const config = getService('config');
const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username');
const isServerless = config.get('serverless');
const dataPathBuilder = new EsArchivePathBuilder(isServerless);
const path = dataPathBuilder.getPath('auditbeat/hosts');
describe('@serverless @ess role_based_rule_exceptions_workflows', () => {
before(async () => {
await esArchiver.load(path);
});
after(async () => {
await esArchiver.unload(path);
});
describe('creating rules with exceptions', () => {
beforeEach(async () => {
await createAlertsIndex(supertest, log);
});
afterEach(async () => {
await deleteAllAlerts(supertest, log, es);
await deleteAllRules(supertest, log);
await deleteAllExceptions(supertest, log);
});
describe('elastic admin', () => {
it('should create a single rule with a rule_id and add an exception list to the rule', async () => {
const {
body: { id, list_id, namespace_type, type },
} = await supertest
.post(EXCEPTION_LIST_URL)
.set('kbn-xsrf', 'true')
.send(getCreateExceptionListMinimalSchemaMock())
.expect(200);
const ruleWithException: RuleCreateProps = {
...getSimpleRule(),
exceptions_list: [
{
id,
list_id,
namespace_type,
type,
},
],
};
const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME);
const rule = await createRule(supertest, log, ruleWithException);
const expected = {
...expectedRule,
exceptions_list: [
{
id,
list_id,
namespace_type,
type,
},
],
};
const bodyToCompare = removeServerGeneratedProperties(rule);
expect(bodyToCompare).toEqual(expected);
});
it('should create a single rule with an exception list and validate it ran successfully', async () => {
const {
body: { id, list_id, namespace_type, type },
} = await supertest
.post(EXCEPTION_LIST_URL)
.set('kbn-xsrf', 'true')
.send(getCreateExceptionListMinimalSchemaMock())
.expect(200);
const ruleWithException: RuleCreateProps = {
...getSimpleRule(),
enabled: true,
exceptions_list: [
{
id,
list_id,
namespace_type,
type,
},
],
};
const rule = await createRule(supertest, log, ruleWithException);
await waitForRuleSuccess({ supertest, log, id: rule.id });
const bodyToCompare = removeServerGeneratedProperties(rule);
const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME);
const expected = {
...expectedRule,
enabled: true,
exceptions_list: [
{
id,
list_id,
namespace_type,
type,
},
],
};
expect(bodyToCompare).toEqual(expected);
});
it('@skipInQA should allow removing an exception list from an immutable rule through patch', async () => {
await installMockPrebuiltRules(supertest, es);
// This rule has an existing exceptions_list that we are going to use
const immutableRule = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
expect(immutableRule.exceptions_list.length).toBeGreaterThan(0); // make sure we have at least one exceptions_list
// remove the exceptions list as a user is allowed to remove it from an immutable rule
await supertest
.patch(DETECTION_ENGINE_RULES_URL)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '2023-10-31')
.send({ rule_id: ELASTIC_SECURITY_RULE_ID, exceptions_list: [] })
.expect(200);
const immutableRuleSecondTime = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
expect(immutableRuleSecondTime.exceptions_list.length).toEqual(0);
});
it('@skipInQA should allow adding a second exception list to an immutable rule through patch', async () => {
await installMockPrebuiltRules(supertest, es);
const { id, list_id, namespace_type, type } = await createExceptionList(
supertest,
log,
getCreateExceptionListMinimalSchemaMock()
);
// This rule has an existing exceptions_list that we are going to use
const immutableRule = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
expect(immutableRule.exceptions_list.length).toBeGreaterThan(0); // make sure we have at least one
// add a second exceptions list as a user is allowed to add a second list to an immutable rule
await supertest
.patch(DETECTION_ENGINE_RULES_URL)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '2023-10-31')
.send({
rule_id: ELASTIC_SECURITY_RULE_ID,
exceptions_list: [
...immutableRule.exceptions_list,
{
id,
list_id,
namespace_type,
type,
},
],
})
.expect(200);
const immutableRuleSecondTime = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
expect(immutableRuleSecondTime.exceptions_list.length).toEqual(2);
});
it('@skipInQA should override any updates to pre-packaged rules if the user removes the exception list through the API but the new version of a rule has an exception list again', async () => {
await installMockPrebuiltRules(supertest, es);
// This rule has an existing exceptions_list that we are going to use
const immutableRule = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
expect(immutableRule.exceptions_list.length).toBeGreaterThan(0); // make sure we have at least one
await supertest
.patch(DETECTION_ENGINE_RULES_URL)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '2023-10-31')
.send({ rule_id: ELASTIC_SECURITY_RULE_ID, exceptions_list: [] })
.expect(200);
await downgradeImmutableRule(es, log, ELASTIC_SECURITY_RULE_ID);
await installMockPrebuiltRules(supertest, es);
const immutableRuleSecondTime = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
// We should have a length of 1 and it should be the same as our original before we tried to remove it using patch
expect(immutableRuleSecondTime.exceptions_list.length).toEqual(1);
expect(immutableRuleSecondTime.exceptions_list).toEqual(immutableRule.exceptions_list);
});
it('@skipInQA should merge back an exceptions_list if it was removed from the immutable rule through PATCH', async () => {
await installMockPrebuiltRules(supertest, es);
const { id, list_id, namespace_type, type } = await createExceptionList(
supertest,
log,
getCreateExceptionListMinimalSchemaMock()
);
// This rule has an existing exceptions_list that we are going to ensure does not stomp on our existing rule
const immutableRule = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
expect(immutableRule.exceptions_list.length).toBeGreaterThan(0); // make sure we have at least one
// remove the exception list and only have a single list that is not an endpoint_list
await supertest
.patch(DETECTION_ENGINE_RULES_URL)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '2023-10-31')
.send({
rule_id: ELASTIC_SECURITY_RULE_ID,
exceptions_list: [
{
id,
list_id,
namespace_type,
type,
},
],
})
.expect(200);
await downgradeImmutableRule(es, log, ELASTIC_SECURITY_RULE_ID);
await installMockPrebuiltRules(supertest, es);
const immutableRuleSecondTime = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
expect(immutableRuleSecondTime.exceptions_list).toEqual([
...immutableRule.exceptions_list,
{
id,
list_id,
namespace_type,
type,
},
]);
});
it('@skipInQA should NOT add an extra exceptions_list that already exists on a rule during an upgrade', async () => {
await installMockPrebuiltRules(supertest, es);
// This rule has an existing exceptions_list that we are going to ensure does not stomp on our existing rule
const immutableRule = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
expect(immutableRule.exceptions_list.length).toBeGreaterThan(0); // make sure we have at least one
await downgradeImmutableRule(es, log, ELASTIC_SECURITY_RULE_ID);
await installMockPrebuiltRules(supertest, es);
const immutableRuleSecondTime = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
// The installed rule should have both the original immutable exceptions list back and the
// new list the user added.
expect(immutableRuleSecondTime.exceptions_list).toEqual([
...immutableRule.exceptions_list,
]);
});
it('@skipInQA should NOT allow updates to pre-packaged rules to overwrite existing exception based rules when the user adds an additional exception list', async () => {
await installMockPrebuiltRules(supertest, es);
const { id, list_id, namespace_type, type } = await createExceptionList(
supertest,
log,
getCreateExceptionListMinimalSchemaMock()
);
// This rule has an existing exceptions_list that we are going to ensure does not stomp on our existing rule
const immutableRule = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
// add a second exceptions list as a user is allowed to add a second list to an immutable rule
await supertest
.patch(DETECTION_ENGINE_RULES_URL)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '2023-10-31')
.send({
rule_id: ELASTIC_SECURITY_RULE_ID,
exceptions_list: [
...immutableRule.exceptions_list,
{
id,
list_id,
namespace_type,
type,
},
],
})
.expect(200);
await downgradeImmutableRule(es, log, ELASTIC_SECURITY_RULE_ID);
await installMockPrebuiltRules(supertest, es);
const immutableRuleSecondTime = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
// It should be the same as what the user added originally
expect(immutableRuleSecondTime.exceptions_list).toEqual([
...immutableRule.exceptions_list,
{
id,
list_id,
namespace_type,
type,
},
]);
});
it('@skipInQA should not remove any exceptions added to a pre-packaged/immutable rule during an update if that rule has no existing exception lists', async () => {
await installMockPrebuiltRules(supertest, es);
// Create a new exception list
const { id, list_id, namespace_type, type } = await createExceptionList(
supertest,
log,
getCreateExceptionListMinimalSchemaMock()
);
// Find a rule without exceptions_list
const ruleWithoutExceptionList = SAMPLE_PREBUILT_RULES.find(
(rule) => !rule['security-rule'].exceptions_list
);
const ruleId = ruleWithoutExceptionList?.['security-rule'].rule_id;
if (!ruleId) {
throw new Error('Cannot find a rule without exceptions_list in the sample data');
}
const immutableRule = await fetchRule(supertest, { ruleId });
expect(immutableRule.exceptions_list.length).toEqual(0); // make sure we have no exceptions_list
// add a second exceptions list as a user is allowed to add a second list to an immutable rule
await supertest
.patch(DETECTION_ENGINE_RULES_URL)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '2023-10-31')
.send({
rule_id: ruleId,
exceptions_list: [
{
id,
list_id,
namespace_type,
type,
},
],
})
.expect(200);
await downgradeImmutableRule(es, log, ruleId);
await installMockPrebuiltRules(supertest, es);
const immutableRuleSecondTime = await fetchRule(supertest, { ruleId });
expect(immutableRuleSecondTime.exceptions_list).toEqual([
{
id,
list_id,
namespace_type,
type,
},
]);
});
it('@skipInQA should not change the immutable tags when adding a second exception list to an immutable rule through patch', async () => {
await installMockPrebuiltRules(supertest, es);
const { id, list_id, namespace_type, type } = await createExceptionList(
supertest,
log,
getCreateExceptionListMinimalSchemaMock()
);
// This rule has an existing exceptions_list that we are going to use
const immutableRule = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
expect(immutableRule.exceptions_list.length).toBeGreaterThan(0); // make sure we have at least one
// add a second exceptions list as a user is allowed to add a second list to an immutable rule
await supertest
.patch(DETECTION_ENGINE_RULES_URL)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '2023-10-31')
.send({
rule_id: ELASTIC_SECURITY_RULE_ID,
exceptions_list: [
...immutableRule.exceptions_list,
{
id,
list_id,
namespace_type,
type,
},
],
})
.expect(200);
const body = await findImmutableRuleById(supertest, log, ELASTIC_SECURITY_RULE_ID);
expect(body.data.length).toEqual(1); // should have only one length to the data set, otherwise we have duplicates or the tags were removed and that is incredibly bad.
const bodyToCompare = removeServerGeneratedProperties(body.data[0]);
expect(bodyToCompare.rule_id).toEqual(immutableRule.rule_id); // Rule id should not change with a a patch
expect(bodyToCompare.immutable).toEqual(immutableRule.immutable); // Immutable should always stay the same which is true and never flip to false.
expect(bodyToCompare.version).toEqual(immutableRule.version); // The version should never update on a patch
});
it('@skipInQA should not change count of prepacked rules when adding a second exception list to an immutable rule through patch. If this fails, suspect the immutable tags are not staying on the rule correctly.', async () => {
await installMockPrebuiltRules(supertest, es);
const { id, list_id, namespace_type, type } = await createExceptionList(
supertest,
log,
getCreateExceptionListMinimalSchemaMock()
);
// This rule has an existing exceptions_list that we are going to use
const immutableRule = await fetchRule(supertest, {
ruleId: ELASTIC_SECURITY_RULE_ID,
});
expect(immutableRule.exceptions_list.length).toBeGreaterThan(0); // make sure we have at least one
// add a second exceptions list as a user is allowed to add a second list to an immutable rule
await supertest
.patch(DETECTION_ENGINE_RULES_URL)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '2023-10-31')
.send({
rule_id: ELASTIC_SECURITY_RULE_ID,
exceptions_list: [
...immutableRule.exceptions_list,
{
id,
list_id,
namespace_type,
type,
},
],
})
.expect(200);
const status = await getPrebuiltRulesAndTimelinesStatus(es, supertest);
expect(status.rules_not_installed).toEqual(0);
});
});
describe('@brokenInServerless t1_analyst', () => {
const role = ROLES.t1_analyst;
beforeEach(async () => {
await createUserAndRole(getService, role);
});
afterEach(async () => {
await deleteUserAndRole(getService, role);
});
it('should NOT be able to create an exception list', async () => {
await supertestWithoutAuth
.post(EXCEPTION_LIST_ITEM_URL)
.auth(role, 'changeme')
.set('kbn-xsrf', 'true')
.send(getCreateExceptionListItemMinimalSchemaMock())
.expect(403);
});
it('should NOT be able to create an exception list item', async () => {
await supertestWithoutAuth
.post(EXCEPTION_LIST_ITEM_URL)
.auth(role, 'changeme')
.set('kbn-xsrf', 'true')
.send(getCreateExceptionListItemMinimalSchemaMock())
.expect(403);
});
});
// FLAKY: https://github.com/elastic/kibana/issues/169664
describe.skip('tests with auditbeat data', () => {
before(async () => {
await esArchiver.load(path);
});
after(async () => {
await esArchiver.unload(path);
});
beforeEach(async () => {
await createAlertsIndex(supertest, log);
});
afterEach(async () => {
await deleteAllAlerts(supertest, log, es);
await deleteAllRules(supertest, log);
await deleteAllExceptions(supertest, log);
});
it('should be able to execute against an exception list that does not include valid entries and get back 10 alerts', async () => {
const { id, list_id, namespace_type, type } = await createExceptionList(
supertest,
log,
getCreateExceptionListMinimalSchemaMock()
);
const exceptionListItem: CreateExceptionListItemSchema = {
...getCreateExceptionListItemMinimalSchemaMock(),
entries: [
{
field: 'some.none.existent.field', // non-existent field where we should not exclude anything
operator: 'included',
type: 'match',
value: 'some value',
},
],
};
await createExceptionListItem(supertest, log, exceptionListItem);
const ruleWithException: RuleCreateProps = {
name: 'Simple Rule Query',
description: 'Simple Rule Query',
enabled: true,
risk_score: 1,
rule_id: 'rule-1',
severity: 'high',
index: ['auditbeat-*'],
type: 'query',
from: '1900-01-01T00:00:00.000Z',
query: 'host.name: "suricata-sensor-amsterdam"',
exceptions_list: [
{
id,
list_id,
namespace_type,
type,
},
],
};
const { id: createdId } = await createRule(supertest, log, ruleWithException);
await waitForRuleSuccess({ supertest, log, id: createdId });
await waitForAlertsToBePresent(supertest, log, 10, [createdId]);
const alertsOpen = await getAlertsByIds(supertest, log, [createdId]);
expect(alertsOpen.hits.hits.length).toEqual(10);
});
it('should be able to execute against an exception list that does include valid entries and get back 0 alerts', async () => {
const rule: QueryRuleCreateProps = {
name: 'Simple Rule Query',
description: 'Simple Rule Query',
enabled: true,
risk_score: 1,
rule_id: 'rule-1',
severity: 'high',
index: ['auditbeat-*'],
type: 'query',
from: '1900-01-01T00:00:00.000Z',
query: 'host.name: "suricata-sensor-amsterdam"',
};
const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [
[
{
field: 'host.name', // This matches the query above which will exclude everything
operator: 'included',
type: 'match',
value: 'suricata-sensor-amsterdam',
},
],
]);
const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule);
expect(alertsOpen.hits.hits.length).toEqual(0);
});
it('should be able to execute against an exception list that does include valid case sensitive entries and get back 0 alerts', async () => {
const rule: QueryRuleCreateProps = {
name: 'Simple Rule Query',
description: 'Simple Rule Query',
enabled: true,
risk_score: 1,
rule_id: 'rule-1',
severity: 'high',
index: ['auditbeat-*'],
type: 'query',
from: '1900-01-01T00:00:00.000Z',
query: 'host.name: "suricata-sensor-amsterdam"',
};
const rule2: QueryRuleCreateProps = {
name: 'Simple Rule Query',
description: 'Simple Rule Query',
enabled: true,
risk_score: 1,
rule_id: 'rule-2',
severity: 'high',
index: ['auditbeat-*'],
type: 'query',
from: '1900-01-01T00:00:00.000Z',
query: 'host.name: "suricata-sensor-amsterdam"',
};
const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [
[
{
field: 'host.os.name',
operator: 'included',
type: 'match_any',
value: ['ubuntu'],
},
],
]);
const createdRule2 = await createRuleWithExceptionEntries(supertest, log, rule2, [
[
{
field: 'host.os.name', // This matches the query above which will exclude everything
operator: 'included',
type: 'match_any',
value: ['ubuntu', 'Ubuntu'],
},
],
]);
const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule);
const alertsOpen2 = await getOpenAlerts(supertest, log, es, createdRule2);
// Expect alerts here because all values are "Ubuntu"
// and exception is one of ["ubuntu"]
expect(alertsOpen.hits.hits.length).toEqual(10);
// Expect no alerts here because all values are "Ubuntu"
// and exception is one of ["ubuntu", "Ubuntu"]
expect(alertsOpen2.hits.hits.length).toEqual(0);
});
it('generates no alerts when an exception is added for an EQL rule', async () => {
const rule: EqlRuleCreateProps = {
...getEqlRuleForAlertTesting(['auditbeat-*']),
query: 'configuration where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"',
};
const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [
[
{
field: 'host.id',
operator: 'included',
type: 'match',
value: '8cc95778cce5407c809480e8e32ad76b',
},
],
]);
const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule);
expect(alertsOpen.hits.hits.length).toEqual(0);
});
it('generates no alerts when an exception is added for a threshold rule', async () => {
const rule: ThresholdRuleCreateProps = {
...getThresholdRuleForAlertTesting(['auditbeat-*']),
threshold: {
field: 'host.id',
value: 700,
},
};
const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [
[
{
field: 'host.id',
operator: 'included',
type: 'match',
value: '8cc95778cce5407c809480e8e32ad76b',
},
],
]);
const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule);
expect(alertsOpen.hits.hits.length).toEqual(0);
});
it('generates no alerts when an exception is added for a threat match rule', async () => {
const rule: ThreatMatchRuleCreateProps = {
description: 'Detecting root and admin users',
name: 'Query with a rule id',
severity: 'high',
index: ['auditbeat-*'],
type: 'threat_match',
risk_score: 55,
language: 'kuery',
rule_id: 'rule-1',
from: '1900-01-01T00:00:00.000Z',
query: '*:*',
threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip
threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity
threat_mapping: [
// We match host.name against host.name
{
entries: [
{
field: 'host.name',
value: 'host.name',
type: 'mapping',
},
],
},
],
threat_filters: [],
};
const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [
[
{
field: 'source.ip',
operator: 'included',
type: 'match',
value: '188.166.120.93',
},
],
]);
const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule);
expect(alertsOpen.hits.hits.length).toEqual(0);
});
describe('rules with value list exceptions', () => {
beforeEach(async () => {
await createListsIndex(supertest, log);
});
afterEach(async () => {
await deleteListsIndex(supertest, log);
});
it('generates no alerts when a value list exception is added for a query rule', async () => {
const valueListId = 'value-list-id.txt';
await importFile(supertest, log, 'keyword', ['suricata-sensor-amsterdam'], valueListId);
const rule: QueryRuleCreateProps = {
name: 'Simple Rule Query',
description: 'Simple Rule Query',
enabled: true,
risk_score: 1,
rule_id: 'rule-1',
severity: 'high',
index: ['auditbeat-*'],
type: 'query',
from: '1900-01-01T00:00:00.000Z',
query: 'host.name: "suricata-sensor-amsterdam"',
};
const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [
[
{
field: 'host.name',
operator: 'included',
type: 'list',
list: {
id: valueListId,
type: 'keyword',
},
},
],
]);
const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule);
expect(alertsOpen.hits.hits.length).toEqual(0);
});
it('generates no alerts when a value list exception is added for a threat match rule', async () => {
const valueListId = 'value-list-id.txt';
await importFile(supertest, log, 'keyword', ['zeek-sensor-amsterdam'], valueListId);
const rule: ThreatMatchRuleCreateProps = {
description: 'Detecting root and admin users',
name: 'Query with a rule id',
severity: 'high',
index: ['auditbeat-*'],
type: 'threat_match',
risk_score: 55,
language: 'kuery',
rule_id: 'rule-1',
from: '1900-01-01T00:00:00.000Z',
query: '*:*',
threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip
threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity
threat_mapping: [
// We match host.name against host.name
{
entries: [
{
field: 'host.name',
value: 'host.name',
type: 'mapping',
},
],
},
],
threat_filters: [],
};
const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [
[
{
field: 'host.name',
operator: 'included',
type: 'list',
list: {
id: valueListId,
type: 'keyword',
},
},
],
]);
const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule);
expect(alertsOpen.hits.hits.length).toEqual(0);
});
it('generates no alerts when a value list exception is added for a threshold rule', async () => {
const valueListId = 'value-list-id.txt';
await importFile(supertest, log, 'keyword', ['zeek-sensor-amsterdam'], valueListId);
const rule: ThresholdRuleCreateProps = {
description: 'Detecting root and admin users',
name: 'Query with a rule id',
severity: 'high',
index: ['auditbeat-*'],
type: 'threshold',
risk_score: 55,
language: 'kuery',
rule_id: 'rule-1',
from: '1900-01-01T00:00:00.000Z',
query: 'host.name: "zeek-sensor-amsterdam"',
threshold: {
field: 'host.name',
value: 1,
},
};
const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [
[
{
field: 'host.name',
operator: 'included',
type: 'list',
list: {
id: valueListId,
type: 'keyword',
},
},
],
]);
const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule);
expect(alertsOpen.hits.hits.length).toEqual(0);
});
it('generates no alerts when a value list exception is added for an EQL rule', async () => {
const valueListId = 'value-list-id.txt';
await importFile(supertest, log, 'keyword', ['zeek-sensor-amsterdam'], valueListId);
const rule: EqlRuleCreateProps = {
...getEqlRuleForAlertTesting(['auditbeat-*']),
query: 'configuration where host.name=="zeek-sensor-amsterdam"',
};
const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [
[
{
field: 'host.name',
operator: 'included',
type: 'list',
list: {
id: valueListId,
type: 'keyword',
},
},
],
]);
const alertsOpen = await getOpenAlerts(supertest, log, es, createdRule);
expect(alertsOpen.hits.hits.length).toEqual(0);
});
it('should Not allow deleting value list when there are references and ignoreReferences is false', async () => {
const valueListId = 'value-list-id.txt';
await importFile(supertest, log, 'keyword', ['suricata-sensor-amsterdam'], valueListId);
const rule: QueryRuleCreateProps = {
...getSimpleRule(),
query: 'host.name: "suricata-sensor-amsterdam"',
};
await createRuleWithExceptionEntries(supertest, log, rule, [
[
{
field: 'host.name',
operator: 'included',
type: 'list',
list: {
id: valueListId,
type: 'keyword',
},
},
],
]);
const deleteReferences = false;
const ignoreReferences = false;
// Delete the value list
await supertest
.delete(
`${LIST_URL}?deleteReferences=${deleteReferences}&id=${valueListId}&ignoreReferences=${ignoreReferences}`
)
.set('kbn-xsrf', 'true')
.send()
.expect(409);
});
});
});
});
});
};