[CTI] adds securitySoluion:defaultThreatIndex uiSetting (#108389)

This commit is contained in:
Ece Özalp 2021-08-13 16:32:32 -04:00 committed by GitHub
parent 8ea29b6c5a
commit f952643e54
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 124 additions and 42 deletions

View file

@ -19,6 +19,10 @@ export const stackManagementSchema: MakeSchemaFrom<UsageStats> = {
type: 'keyword',
_meta: { description: 'Default value of the setting was changed.' },
},
'securitySolution:defaultThreatIndex': {
type: 'keyword',
_meta: { description: 'Default value of the setting was changed.' },
},
'securitySolution:newsFeedUrl': {
type: 'keyword',
_meta: { description: 'Default value of the setting was changed.' },

View file

@ -12,6 +12,7 @@ export interface UsageStats {
*/
'timelion:quandl.key': string;
'securitySolution:defaultIndex': string;
'securitySolution:defaultThreatIndex': string;
'securitySolution:newsFeedUrl': string;
'xpackReporting:customPdfLogo': string;
'notifications:banner': string;

View file

@ -7168,6 +7168,12 @@
"description": "Default value of the setting was changed."
}
},
"securitySolution:defaultThreatIndex": {
"type": "keyword",
"_meta": {
"description": "Default value of the setting was changed."
}
},
"securitySolution:newsFeedUrl": {
"type": "keyword",
"_meta": {
@ -9330,4 +9336,4 @@
}
}
}
}
}

View file

@ -62,6 +62,8 @@ export const DEFAULT_SPACE_ID = 'default';
// to enrich signals, and are copied to threat.indicator.
export const DEFAULT_INDICATOR_SOURCE_PATH = 'threatintel.indicator';
export const INDICATOR_DESTINATION_PATH = 'threat.indicator';
export const DEFAULT_THREAT_INDEX_KEY = 'securitySolution:defaultThreatIndex';
export const DEFAULT_THREAT_INDEX_VALUE = ['filebeat-*'];
export enum SecurityPageName {
administration = 'administration',

View file

@ -69,5 +69,3 @@ export const CTI_DATASET_KEY_MAP: { [key: string]: string } = {
MISP: 'threatintel.misp',
'Recorded Future': 'threatintel.recordedfuture',
};
export const DEFAULT_CTI_SOURCE_INDEX = ['filebeat-*'];

View file

@ -6,7 +6,11 @@
*/
import { formatMitreAttackDescription } from '../../helpers/rules';
import { getIndexPatterns, getNewThreatIndicatorRule } from '../../objects/rule';
import {
getIndexPatterns,
getNewThreatIndicatorRule,
getThreatIndexPatterns,
} from '../../objects/rule';
import {
ALERT_RULE_NAME,
@ -136,26 +140,21 @@ describe('indicator match', () => {
});
it('Does NOT show invalidation text on initial page load if indicator index pattern is filled out', () => {
getIndicatorIndicatorIndex().type(
`${getNewThreatIndicatorRule().indicatorIndexPattern}{enter}`
);
getDefineContinueButton().click();
getIndexPatternInvalidationText().should('not.exist');
});
it('Shows invalidation text when you try to continue without filling it out', () => {
getIndexPatternClearButton().click();
getIndicatorIndicatorIndex().type(
`${getNewThreatIndicatorRule().indicatorIndexPattern}{enter}`
);
getIndicatorIndicatorIndex().type(`{backspace}{enter}`);
getDefineContinueButton().click();
getIndexPatternInvalidationText().should('exist');
});
});
describe('Indicator index patterns', () => {
it('Contains empty index pattern', () => {
getIndicatorIndicatorIndex().should('have.text', '');
it('Contains a predefined index pattern', () => {
getIndicatorIndicatorIndex().should('have.text', getThreatIndexPatterns().join(''));
});
it('Does NOT show invalidation text on initial page load', () => {
@ -163,6 +162,7 @@ describe('indicator match', () => {
});
it('Shows invalidation text if you try to continue without filling it out', () => {
getIndicatorIndicatorIndex().type(`{backspace}{enter}`);
getDefineContinueButton().click();
getIndexPatternInvalidationText().should('exist');
});

View file

@ -107,6 +107,8 @@ export const getIndexPatterns = (): string[] => [
'winlogbeat-*',
];
export const getThreatIndexPatterns = (): string[] => ['filebeat-*'];
const getMitre1 = (): Mitre => ({
tactic: `${getMockThreatData().tactic.name} (${getMockThreatData().tactic.id})`,
techniques: [

View file

@ -273,7 +273,7 @@ export const fillDefineThresholdRule = (rule: ThresholdRule) => {
cy.get(IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK).click();
cy.get(TIMELINE(rule.timeline.id!)).click();
cy.get(COMBO_BOX_CLEAR_BTN).click();
cy.get(COMBO_BOX_CLEAR_BTN).first().click();
rule.index.forEach((index) => {
cy.get(COMBO_BOX_INPUT).first().type(`${index}{enter}`);
@ -447,7 +447,7 @@ export const getIndicatorIndicatorIndex = () =>
cy.get(THREAT_MATCH_INDICATOR_INDICATOR_INDEX).eq(0);
/** Returns the index pattern's clear button */
export const getIndexPatternClearButton = () => cy.get(COMBO_BOX_CLEAR_BTN);
export const getIndexPatternClearButton = () => cy.get(COMBO_BOX_CLEAR_BTN).first();
/** Returns the custom query input */
export const getCustomQueryInput = () => cy.get(THREAT_MATCH_CUSTOM_QUERY_INPUT).eq(0);

View file

@ -11,7 +11,6 @@ import { isEmpty } from 'lodash';
import { EventFields } from '../../../../../common/search_strategy/security_solution/cti';
import {
DEFAULT_CTI_SOURCE_INDEX,
DEFAULT_EVENT_ENRICHMENT_FROM,
DEFAULT_EVENT_ENRICHMENT_TO,
} from '../../../../../common/cti/constants';
@ -20,13 +19,16 @@ import { useKibana } from '../../../lib/kibana';
import { inputsActions } from '../../../store/actions';
import * as i18n from './translations';
import { useEventEnrichmentComplete } from '.';
import { DEFAULT_THREAT_INDEX_KEY } from '../../../../../common/constants';
export const QUERY_ID = 'investigation_time_enrichment';
const noop = () => {};
export const useInvestigationTimeEnrichment = (eventFields: EventFields) => {
const { addError } = useAppToasts();
const kibana = useKibana();
const { data, uiSettings } = useKibana().services;
const defaultThreatIndices = uiSettings.get<string[]>(DEFAULT_THREAT_INDEX_KEY);
const dispatch = useDispatch();
const [{ from, to }, setRange] = useState({
from: DEFAULT_EVENT_ENRICHMENT_FROM,
@ -66,14 +68,14 @@ export const useInvestigationTimeEnrichment = (eventFields: EventFields) => {
useEffect(() => {
if (!isEmpty(eventFields)) {
start({
data: kibana.services.data,
data,
timerange: { from, to, interval: '' },
defaultIndex: DEFAULT_CTI_SOURCE_INDEX,
defaultIndex: defaultThreatIndices,
eventFields,
filterQuery: '',
});
}
}, [from, start, kibana.services.data, to, eventFields]);
}, [from, start, data, to, eventFields, defaultThreatIndices]);
return {
loading,

View file

@ -11,7 +11,7 @@ import styled from 'styled-components';
import { isEqual } from 'lodash';
import { IndexPattern } from 'src/plugins/data/public';
import { DEFAULT_INDEX_KEY } from '../../../../../common/constants';
import { DEFAULT_INDEX_KEY, DEFAULT_THREAT_INDEX_KEY } from '../../../../../common/constants';
import { DEFAULT_TIMELINE_TITLE } from '../../../../timelines/components/timeline/translations';
import { isMlRule } from '../../../../../common/machine_learning/helpers';
import { hasMlAdminPermissions } from '../../../../../common/machine_learning/has_ml_admin_permissions';
@ -102,7 +102,7 @@ const threatQueryBarDefaultValue: DefineStepRule['queryBar'] = {
query: { ...stepDefineDefaultValue.queryBar.query, query: '*:*' },
};
const MyLabelButton = styled(EuiButtonEmpty)`
export const MyLabelButton = styled(EuiButtonEmpty)`
height: 18px;
font-size: 12px;
@ -135,10 +135,13 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
const mlCapabilities = useMlCapabilities();
const [openTimelineSearch, setOpenTimelineSearch] = useState(false);
const [indexModified, setIndexModified] = useState(false);
const [threatIndexModified, setThreatIndexModified] = useState(false);
const [indicesConfig] = useUiSetting$<string[]>(DEFAULT_INDEX_KEY);
const [threatIndicesConfig] = useUiSetting$<string[]>(DEFAULT_THREAT_INDEX_KEY);
const initialState = defaultValues ?? {
...stepDefineDefaultValue,
index: indicesConfig,
threatIndex: threatIndicesConfig,
};
const { form } = useForm<DefineStepRule>({
defaultValue: initialState,
@ -206,6 +209,10 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
setIndexModified(!isEqual(index, indicesConfig));
}, [index, indicesConfig]);
useEffect(() => {
setThreatIndexModified(!isEqual(threatIndex, threatIndicesConfig));
}, [threatIndex, threatIndicesConfig]);
/**
* When a rule type is changed to or from a threat match this will modify the
* default query string to either:
@ -269,6 +276,11 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
indexField.setValue(indicesConfig);
}, [getFields, indicesConfig]);
const handleResetThreatIndices = useCallback(() => {
const threatIndexField = getFields().threatIndex;
threatIndexField.setValue(threatIndicesConfig);
}, [getFields, threatIndicesConfig]);
const handleOpenTimelineSearch = useCallback(() => {
setOpenTimelineSearch(true);
}, []);
@ -293,14 +305,23 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
const ThreatMatchInputChildren = useCallback(
({ threatMapping }) => (
<ThreatMatchInput
threatBrowserFields={threatBrowserFields}
handleResetThreatIndices={handleResetThreatIndices}
indexPatterns={indexPatterns as IndexPattern}
threatBrowserFields={threatBrowserFields}
threatIndexModified={threatIndexModified}
threatIndexPatterns={threatIndexPatterns as IndexPattern}
threatMapping={threatMapping}
threatIndexPatternsLoading={threatIndexPatternsLoading}
threatMapping={threatMapping}
/>
),
[threatBrowserFields, threatIndexPatternsLoading, threatIndexPatterns, indexPatterns]
[
handleResetThreatIndices,
indexPatterns,
threatBrowserFields,
threatIndexModified,
threatIndexPatterns,
threatIndexPatternsLoading,
]
);
return isReadOnlyView ? (
<StepContentWrapper data-test-subj="definitionRule" addPadding={addPadding}>

View file

@ -22,6 +22,8 @@ import { DefineStepRule } from '../../../pages/detection_engine/rules/types';
import { schema } from '../step_define_rule/schema';
import { QueryBarDefineRule } from '../query_bar';
import { IndexPattern } from '../../../../../../../../src/plugins/data/public';
import * as i18n from '../step_define_rule/translations';
import { MyLabelButton } from '../step_define_rule';
const CommonUseField = getUseField({ component: Field });
@ -31,9 +33,13 @@ interface ThreatMatchInputProps {
threatIndexPatterns: IndexPattern;
indexPatterns: IndexPattern;
threatIndexPatternsLoading: boolean;
threatIndexModified: boolean;
handleResetThreatIndices: () => void;
}
const ThreatMatchInputComponent: React.FC<ThreatMatchInputProps> = ({
threatIndexModified,
handleResetThreatIndices,
threatMapping,
indexPatterns,
threatIndexPatterns,
@ -57,7 +63,11 @@ const ThreatMatchInputComponent: React.FC<ThreatMatchInputProps> = ({
path="threatIndex"
config={{
...schema.threatIndex,
labelAppend: null,
labelAppend: threatIndexModified ? (
<MyLabelButton onClick={handleResetThreatIndices} iconType="refresh">
{i18n.RESET_DEFAULT_INDEX}
</MyLabelButton>
) : null,
}}
componentProps={{
idAria: 'detectionEngineStepDefineRuleThreatMatchIndices',

View file

@ -22,6 +22,7 @@ import {
import { mockTheme, mockProps, mockCtiEventCountsResponse, mockCtiLinksResponse } from './mock';
import { useCtiEventCounts } from '../../containers/overview_cti_links/use_cti_event_counts';
import { useCtiDashboardLinks } from '../../containers/overview_cti_links';
import { useRequestEventCounts } from '../../containers/overview_cti_links/use_request_event_counts';
jest.mock('../../../common/lib/kibana');
@ -29,6 +30,10 @@ jest.mock('../../containers/overview_cti_links/use_cti_event_counts');
const useCTIEventCountsMock = useCtiEventCounts as jest.Mock;
useCTIEventCountsMock.mockReturnValue(mockCtiEventCountsResponse);
jest.mock('../../containers/overview_cti_links/use_request_event_counts');
const useRequestEventCountsMock = useRequestEventCounts as jest.Mock;
useRequestEventCountsMock.mockReturnValue([true, {}]);
jest.mock('../../containers/overview_cti_links');
const useCtiDashboardLinksMock = useCtiDashboardLinks as jest.Mock;
useCtiDashboardLinksMock.mockReturnValue(mockCtiLinksResponse);

View file

@ -19,10 +19,20 @@ import {
mockGlobalState,
SUB_PLUGINS_REDUCER,
} from '../../../common/mock';
import { mockTheme, mockProps } from './mock';
import { mockTheme, mockProps, mockCtiEventCountsResponse } from './mock';
import { useRequestEventCounts } from '../../containers/overview_cti_links/use_request_event_counts';
import { useCtiEventCounts } from '../../containers/overview_cti_links/use_cti_event_counts';
jest.mock('../../../common/lib/kibana');
jest.mock('../../containers/overview_cti_links/use_request_event_counts');
const useRequestEventCountsMock = useRequestEventCounts as jest.Mock;
useRequestEventCountsMock.mockReturnValue([true, {}]);
jest.mock('../../containers/overview_cti_links/use_cti_event_counts');
const useCTIEventCountsMock = useCtiEventCounts as jest.Mock;
useCTIEventCountsMock.mockReturnValue(mockCtiEventCountsResponse);
describe('ThreatIntelLinkPanel', () => {
const state: State = mockGlobalState;

View file

@ -9,12 +9,14 @@ import { i18n } from '@kbn/i18n';
import { convertToBuildEsQuery } from '../../../common/lib/keury';
import { esQuery } from '../../../../../../../src/plugins/data/public';
import { MatrixHistogramType } from '../../../../common/search_strategy';
import { EVENT_DATASET, DEFAULT_CTI_SOURCE_INDEX } from '../../../../common/cti/constants';
import { EVENT_DATASET } from '../../../../common/cti/constants';
import { useMatrixHistogram } from '../../../common/containers/matrix_histogram';
import { useKibana } from '../../../common/lib/kibana';
import { DEFAULT_THREAT_INDEX_KEY } from '../../../../common/constants';
export const useRequestEventCounts = (to: string, from: string) => {
const { uiSettings } = useKibana().services;
const defaultThreatIndices = uiSettings.get<string[]>(DEFAULT_THREAT_INDEX_KEY);
const [filterQuery] = convertToBuildEsQuery({
config: esQuery.getEsQueryConfig(uiSettings),
@ -28,7 +30,7 @@ export const useRequestEventCounts = (to: string, from: string) => {
esTypes: ['keyword'],
},
],
title: 'filebeat-*',
title: defaultThreatIndices.toString(),
},
queries: [{ query: 'event.type:indicator', language: 'kuery' }],
filters: [],
@ -42,12 +44,12 @@ export const useRequestEventCounts = (to: string, from: string) => {
}),
filterQuery,
histogramType: MatrixHistogramType.events,
indexNames: DEFAULT_CTI_SOURCE_INDEX,
indexNames: defaultThreatIndices,
stackByField: EVENT_DATASET,
startDate: from,
size: 0,
};
}, [to, from, filterQuery]);
}, [to, from, filterQuery, defaultThreatIndices]);
const results = useMatrixHistogram(matrixHistogramRequest);

View file

@ -11,27 +11,29 @@ import { schema } from '@kbn/config-schema';
import { CoreSetup } from '../../../../src/core/server';
import {
APP_ID,
DEFAULT_ANOMALY_SCORE,
DEFAULT_APP_REFRESH_INTERVAL,
DEFAULT_APP_TIME_RANGE,
DEFAULT_FROM,
DEFAULT_INDEX_KEY,
DEFAULT_INDEX_PATTERN,
DEFAULT_INDEX_PATTERN_EXPERIMENTAL,
DEFAULT_ANOMALY_SCORE,
DEFAULT_APP_TIME_RANGE,
DEFAULT_APP_REFRESH_INTERVAL,
DEFAULT_INTERVAL_PAUSE,
DEFAULT_INTERVAL_VALUE,
DEFAULT_FROM,
DEFAULT_TO,
ENABLE_NEWS_FEED_SETTING,
NEWS_FEED_URL_SETTING,
NEWS_FEED_URL_SETTING_DEFAULT,
IP_REPUTATION_LINKS_SETTING,
IP_REPUTATION_LINKS_SETTING_DEFAULT,
DEFAULT_RULES_TABLE_REFRESH_SETTING,
DEFAULT_RULE_REFRESH_IDLE_VALUE,
DEFAULT_RULE_REFRESH_INTERVAL_ON,
DEFAULT_RULE_REFRESH_INTERVAL_VALUE,
DEFAULT_RULE_REFRESH_IDLE_VALUE,
DEFAULT_RULES_TABLE_REFRESH_SETTING,
DEFAULT_THREAT_INDEX_KEY,
DEFAULT_THREAT_INDEX_VALUE,
DEFAULT_TO,
DEFAULT_TRANSFORMS,
DEFAULT_TRANSFORMS_SETTING,
ENABLE_NEWS_FEED_SETTING,
IP_REPUTATION_LINKS_SETTING,
IP_REPUTATION_LINKS_SETTING_DEFAULT,
NEWS_FEED_URL_SETTING,
NEWS_FEED_URL_SETTING_DEFAULT,
} from '../common/constants';
import { transformConfigSchema } from '../common/transforms/types';
import { ExperimentalFeatures } from '../common/experimental_features';
@ -100,6 +102,23 @@ export const initUiSettings = (
requiresPageReload: true,
schema: schema.arrayOf(schema.string()),
},
[DEFAULT_THREAT_INDEX_KEY]: {
name: i18n.translate('xpack.securitySolution.uiSettings.defaultThreatIndexLabel', {
defaultMessage: 'Threat indices',
}),
sensitive: true,
value: DEFAULT_THREAT_INDEX_VALUE,
description: i18n.translate(
'xpack.securitySolution.uiSettings.defaultThreatIndexDescription',
{
defaultMessage:
'<p>Comma-delimited list of Threat Intelligence indices from which the Security app collects indicators.</p>',
}
),
category: [APP_ID],
requiresPageReload: true,
schema: schema.arrayOf(schema.string()),
},
[DEFAULT_ANOMALY_SCORE]: {
name: i18n.translate('xpack.securitySolution.uiSettings.defaultAnomalyScoreLabel', {
defaultMessage: 'Anomaly threshold',