mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[AI4DSOC] Fix issue with filtering by integrations (#216574)
## Summary This PR fixes an issue with the logic implemented in [this previous PR](https://github.com/elastic/kibana/pull/215586): _In the AI for SOC effort, each integration is bundled with a single rule. This means that deselecting a source from the Source filter button is equivalent to adding a filter to the search bar to exclude all alerts with the kibana.alert.rule.name property having the value of that integration._ The problem with the previous logic above is the value in the `kibana.alert.rule.name` field can be overridden (see `Rule name override [here](https://www.elastic.co/guide/en/security/current/rules-ui-create.html)). Therefore filtering alerts by this value does not guarantee that all the alerts generated by the rule will be correctly filtered out. The new logic uses the `rule.id` instead of the `rule.name`, which we then use to filter using the `signal.rule.id` field instead of `kibana.alert.rule.name` ### Example: There are following 2 integrations installed: ```typescript [ { id: 'splunk', name: 'splunk', status: installationStatuses.Installed, title: 'Splunk', version: '', }, { id: 'google_secops', name: 'google_secops', status: installationStatuses.Installed, title: 'Google SecOps', version: '', }, ] ``` This means that - in theory - there are the following 2 rules installed and running: ```typescript [ { related_integrations: [{ package: 'splunk' }], id: 'splunk_rule_id', }, { related_integrations: [{ package: 'google_secops' }], id: 'google_secops_rule_id', }, ] ``` In this case, the `Sources` button would show 2 entries, as follow: ```typescript [ { checked: 'on', key: 'splunk_rule_id', label: 'Splunk', }, { checked: 'on', key: 'google_secops_rule_id', label: 'Splunk', }, ] ``` This PR also fixes a small miss in [the prior PR](https://github.com/elastic/kibana/pull/215585) that implemented the KPI section, where I had forgotten to pass the KQL filters to the charts. #### Before https://github.com/user-attachments/assets/77e583c6-718f-46d9-96b4-42ee9976161b #### After https://github.com/user-attachments/assets/50e8e541-5798-4906-b7cc-4f9756dbdefc ## How to test This needs to be ran in Serverless: - `yarn es serverless --projectType security` - `yarn serverless-security --no-base-path` You also need to enable the AI for SOC tier, by adding the following to your `serverless.security.dev.yaml` file: ``` xpack.securitySolutionServerless.productTypes: [ { product_line: 'ai_soc', product_tier: 'search_ai_lake' }, ] ``` Use one of these Serverless users: - `platform_engineer` - `endpoint_operations_analyst` - `endpoint_policy_manager` - `admin` - `system_indices_superuser` ### Notes - generate data: `yarn test:generate:serverless-dev` - create 4 catch all rules, each with a name of a AI for SOC integration (`google_secops`, `microsoft_sentinel`,, `sentinel_one` and `crowdstrike`) - change [this line](https://github.com/elastic/kibana/blob/main/x-pack/solutions/security/plugins/security_solution/public/detections/hooks/alert_summary/use_fetch_integrations.ts#L73) to `installedPackages: availablePackages` to force having some packages installed - change [this line](https://github.com/elastic/kibana/blob/main/x-pack/solutions/security/plugins/security_solution/public/detections/hooks/alert_summary/use_integrations.ts#L63) to `r.name === p.name` to make sure there will be matches between integrations and rules ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios https://github.com/elastic/security-team/issues/11956
This commit is contained in:
parent
3c2b4daf97
commit
2ed4266ea5
5 changed files with 31 additions and 17 deletions
|
@ -35,10 +35,14 @@ export const KPIsSection = memo(({ dataView }: KPIsSectionProps) => {
|
|||
const getGlobalQuerySelector = useMemo(() => inputsSelectors.globalQuerySelector(), []);
|
||||
const query = useDeepEqualSelector(getGlobalQuerySelector);
|
||||
|
||||
const getGlobalFiltersSelector = useMemo(() => inputsSelectors.globalFiltersQuerySelector(), []);
|
||||
const filters = useDeepEqualSelector(getGlobalFiltersSelector);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup data-test-subj={KPIS_SECTION}>
|
||||
<EuiFlexItem>
|
||||
<SeverityLevelPanel
|
||||
filters={filters}
|
||||
signalIndexName={signalIndexName}
|
||||
query={query}
|
||||
showCellActions={false}
|
||||
|
@ -46,13 +50,18 @@ export const KPIsSection = memo(({ dataView }: KPIsSectionProps) => {
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<AlertsByRulePanel
|
||||
filters={filters}
|
||||
signalIndexName={signalIndexName}
|
||||
query={query}
|
||||
showCellActions={false}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<AlertsProgressBarByHostNamePanel signalIndexName={signalIndexName} query={query} />
|
||||
<AlertsProgressBarByHostNamePanel
|
||||
filters={filters}
|
||||
signalIndexName={signalIndexName}
|
||||
query={query}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
|
|
@ -9,6 +9,7 @@ import React from 'react';
|
|||
import { act, render } from '@testing-library/react';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import {
|
||||
FILTER_KEY,
|
||||
INTEGRATION_BUTTON_TEST_ID,
|
||||
IntegrationFilterButton,
|
||||
INTEGRATIONS_LIST_TEST_ID,
|
||||
|
@ -74,12 +75,12 @@ describe('<IntegrationFilterButton />', () => {
|
|||
alias: null,
|
||||
disabled: false,
|
||||
index: undefined,
|
||||
key: 'kibana.alert.rule.name',
|
||||
key: FILTER_KEY,
|
||||
negate: true,
|
||||
params: { query: 'firstKey' },
|
||||
type: 'phrase',
|
||||
},
|
||||
query: { match_phrase: { 'kibana.alert.rule.name': 'firstKey' } },
|
||||
query: { match_phrase: { [FILTER_KEY]: 'firstKey' } },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -92,12 +93,12 @@ describe('<IntegrationFilterButton />', () => {
|
|||
alias: null,
|
||||
disabled: false,
|
||||
index: undefined,
|
||||
key: 'kibana.alert.rule.name',
|
||||
key: FILTER_KEY,
|
||||
negate: true,
|
||||
params: { query: 'secondKey' },
|
||||
type: 'phrase',
|
||||
},
|
||||
query: { match_phrase: { 'kibana.alert.rule.name': 'secondKey' } },
|
||||
query: { match_phrase: { [FILTER_KEY]: 'secondKey' } },
|
||||
},
|
||||
]);
|
||||
const setFilters = jest.fn();
|
||||
|
|
|
@ -32,7 +32,7 @@ const INTEGRATIONS_BUTTON = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const FILTER_KEY = 'kibana.alert.rule.name';
|
||||
export const FILTER_KEY = 'signal.rule.id';
|
||||
|
||||
export interface IntegrationFilterButtonProps {
|
||||
/**
|
||||
|
@ -75,13 +75,13 @@ export const IntegrationFilterButton = memo(({ integrations }: IntegrationFilter
|
|||
) => {
|
||||
setItems(options);
|
||||
|
||||
const ruleName = changedOption.key;
|
||||
if (ruleName) {
|
||||
const ruleId = changedOption.key;
|
||||
if (ruleId) {
|
||||
const existingFilters = filterManager.getFilters();
|
||||
const newFilters: Filter[] = updateFiltersArray(
|
||||
existingFilters,
|
||||
FILTER_KEY,
|
||||
ruleName,
|
||||
ruleId,
|
||||
changedOption.checked === 'on'
|
||||
);
|
||||
filterManager.setFilters(newFilters);
|
||||
|
|
|
@ -39,7 +39,7 @@ describe('useIntegrations', () => {
|
|||
rules: [
|
||||
{
|
||||
related_integrations: [{ package: 'splunk' }],
|
||||
name: 'SplunkRuleName',
|
||||
id: 'SplunkRuleId',
|
||||
},
|
||||
],
|
||||
total: 0,
|
||||
|
@ -64,7 +64,7 @@ describe('useIntegrations', () => {
|
|||
{
|
||||
checked: 'on',
|
||||
'data-test-subj': 'alert-summary-integration-option-Splunk',
|
||||
key: 'SplunkRuleName',
|
||||
key: 'SplunkRuleId',
|
||||
label: 'Splunk',
|
||||
},
|
||||
],
|
||||
|
@ -85,9 +85,9 @@ describe('useIntegrations', () => {
|
|||
disabled: false,
|
||||
type: 'phrase',
|
||||
key: FILTER_KEY,
|
||||
params: { query: 'Splunk' },
|
||||
params: { query: 'SplunkRuleId' },
|
||||
},
|
||||
query: { match_phrase: { [FILTER_KEY]: 'Splunk' } },
|
||||
query: { match_phrase: { [FILTER_KEY]: 'SplunkRuleId' } },
|
||||
},
|
||||
]),
|
||||
},
|
||||
|
@ -101,7 +101,7 @@ describe('useIntegrations', () => {
|
|||
rules: [
|
||||
{
|
||||
related_integrations: [{ package: 'splunk' }],
|
||||
name: 'SplunkRuleName',
|
||||
id: 'SplunkRuleId',
|
||||
},
|
||||
],
|
||||
total: 0,
|
||||
|
@ -125,7 +125,7 @@ describe('useIntegrations', () => {
|
|||
integrations: [
|
||||
{
|
||||
'data-test-subj': 'alert-summary-integration-option-Splunk',
|
||||
key: 'SplunkRuleName',
|
||||
key: 'SplunkRuleId',
|
||||
label: 'Splunk',
|
||||
},
|
||||
],
|
||||
|
|
|
@ -65,13 +65,17 @@ export const useIntegrations = ({ packages }: UseIntegrationsParams): UseIntegra
|
|||
|
||||
if (matchingRule) {
|
||||
// Retrieves the filter from the key/value pair
|
||||
const currentFilter = filterExistsInFiltersArray(currentFilters, FILTER_KEY, p.title);
|
||||
const currentFilter = filterExistsInFiltersArray(
|
||||
currentFilters,
|
||||
FILTER_KEY,
|
||||
matchingRule.id
|
||||
);
|
||||
|
||||
// A EuiSelectableOption is checked only if there is no matching filter for that rule
|
||||
const integration = {
|
||||
'data-test-subj': `${INTEGRATION_OPTION_TEST_ID}${p.title}`,
|
||||
...(!currentFilter && { checked: 'on' as EuiSelectableOptionCheckedType }),
|
||||
key: matchingRule?.name,
|
||||
key: matchingRule?.id, // we save the rule id that we will match again the signal.rule.id field on the alerts
|
||||
label: p.title,
|
||||
};
|
||||
result.push(integration);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue