mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
## Risk score from new Risk Engine showing in UI (#163237)
## Risk score from new Risk Engine showing in UI What happened in this pr: 1. We create the latest transform and index on the `init` call when we install resources for Risk Engine. The original plan was to just get some API layer around our datastream with historical data. But it's not possible in one all to achieve pagination/sorting/filtering of risk scores, so we decided to create transforms. Latest transform: `risk_score_latest_transform_${spaceId}` Latest Index: `risk-score.risk-score-latest-${spaceId}` 2. To get the risk score to UI we use the existing search strategy from the old risk score module, and just pass the new index to the search 3. UI are the same except for the single host/user risk score page, when we change the explanation parts and instead of the old UI, we will show alerts table with grouping etc. <img width="1365" alt="Screenshot 2023-08-09 at 16 19 20" src="0a850b2e
-d3d5-4b06-948d-c129dbf754f0"> 4. Temporarily pass experimentalFeutres to rule wrapper and bulk create as we need to know, which index to use for alert enrichment on ingest time. It will be removed after we decide to release a new Risk Engine 5. Limiting to have only 2 risk scores per kibana <img width="972" alt="Screenshot 2023-08-10 at 16 00 42" src="9cc3c545
-2ace-42d9-a2f3-ff771c7e5abd"> Because of limited timeframe before FF, majority of UI tests will be added after FF ## How to test `xpack.securitySolution.enableExperimental: ['riskScoringRoutesEnabled'] ` - Go to Settings -> Entity Risk Score - Enable risk score module - Generate some alerts with host.name or user.name - Call from Kibana console calculation API ``` POST kbn:/api/risk_scores/calculation { "data_view_id": ".alerts-security.alerts-default", "identifier_type": "user", "range": { "start": "now-30d", "end": "now" } } POST kbn:/api/risk_scores/calculation { "data_view_id": ".alerts-security.alerts-default", "identifier_type": "host", "range": { "start": "now-30d", "end": "now" } } ``` - Go to Security / Explore / Hosts / Hosts Risk and see risk scores - - If host page not available because it's required integrations, easy fix to create filebeat index ``` PUT filebeat-8.10 { "mappings": { "properties": { "@timestamp": { "type":"date" }, "host": { "type": "object", "properties": { "name": { "type": "keyword" } } } } } } ``` - Click on any and go to the single host/user risk page and go to Host/User risk tab - Observe the alerts table for top risk core contributors --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Ryland Herrick <ryalnd@gmail.com>
This commit is contained in:
parent
897e5cbf83
commit
cd65fbbacb
77 changed files with 1455 additions and 329 deletions
|
@ -30,6 +30,7 @@ export enum TableId {
|
|||
rulePreview = 'rule-preview',
|
||||
kubernetesPageSessions = 'kubernetes-page-sessions',
|
||||
alertsOnCasePage = 'alerts-case-page',
|
||||
alertsRiskInputs = 'alerts-risk-inputs',
|
||||
}
|
||||
|
||||
export enum TableEntityType {
|
||||
|
@ -50,6 +51,7 @@ export const tableEntity: Record<TableId, TableEntityType> = {
|
|||
[TableId.rulePreview]: TableEntityType.event,
|
||||
[TableId.hostsPageSessions]: TableEntityType.session,
|
||||
[TableId.kubernetesPageSessions]: TableEntityType.session,
|
||||
[TableId.alertsRiskInputs]: TableEntityType.alert,
|
||||
} as const;
|
||||
|
||||
const TableIdLiteralRt = runtimeTypes.union([
|
||||
|
@ -63,6 +65,7 @@ const TableIdLiteralRt = runtimeTypes.union([
|
|||
runtimeTypes.literal(TableId.rulePreview),
|
||||
runtimeTypes.literal(TableId.kubernetesPageSessions),
|
||||
runtimeTypes.literal(TableId.alertsOnCasePage),
|
||||
runtimeTypes.literal(TableId.alertsRiskInputs),
|
||||
]);
|
||||
|
||||
export type TableIdLiteral = runtimeTypes.TypeOf<typeof TableIdLiteralRt>;
|
||||
|
|
|
@ -497,6 +497,7 @@ export const ALERTS_TABLE_REGISTRY_CONFIG_IDS = {
|
|||
ALERTS_PAGE: `${APP_ID}-alerts-page`,
|
||||
RULE_DETAILS: `${APP_ID}-rule-details`,
|
||||
CASE: `${APP_ID}-case`,
|
||||
RISK_INPUTS: `${APP_ID}-risk-inputs`,
|
||||
} as const;
|
||||
|
||||
export const DEFAULT_ALERT_TAGS_KEY = 'securitySolution:alertTags' as const;
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const MAX_SPACES_COUNT = 2;
|
|
@ -8,3 +8,6 @@
|
|||
export * from './after_keys';
|
||||
export * from './risk_weights';
|
||||
export * from './identifier_types';
|
||||
export * from './types';
|
||||
export * from './indices';
|
||||
export * from './constants';
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const riskScoreBaseIndexName = 'risk-score';
|
||||
|
||||
export const getRiskScoreLatestIndex = (spaceId = 'default') =>
|
||||
`${riskScoreBaseIndexName}.risk-score-latest-${spaceId}`;
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { RiskCategories } from './risk_weights/types';
|
||||
|
||||
export enum RiskScoreEntity {
|
||||
host = 'host',
|
||||
user = 'user',
|
||||
|
@ -23,3 +25,37 @@ export interface InitRiskEngineResult {
|
|||
riskEngineEnabled: boolean;
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
export interface SimpleRiskInput {
|
||||
id: string;
|
||||
index: string;
|
||||
category: RiskCategories;
|
||||
description: string;
|
||||
risk_score: string | number | undefined;
|
||||
timestamp: string | undefined;
|
||||
}
|
||||
|
||||
export interface EcsRiskScore {
|
||||
'@timestamp': string;
|
||||
host?: {
|
||||
risk: Omit<RiskScore, '@timestamp'>;
|
||||
};
|
||||
user?: {
|
||||
risk: Omit<RiskScore, '@timestamp'>;
|
||||
};
|
||||
}
|
||||
|
||||
export type RiskInputs = SimpleRiskInput[];
|
||||
|
||||
export interface RiskScore {
|
||||
'@timestamp': string;
|
||||
id_field: string;
|
||||
id_value: string;
|
||||
calculated_level: string;
|
||||
calculated_score: number;
|
||||
calculated_score_norm: number;
|
||||
category_1_score: number;
|
||||
category_1_count: number;
|
||||
notes: string[];
|
||||
inputs: RiskInputs;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ export interface HostsStrategyResponse extends IEsSearchResponse {
|
|||
|
||||
export interface HostsRequestOptions extends RequestOptionsPaginated<HostsFields> {
|
||||
defaultIndex: string[];
|
||||
isNewRiskScoreModuleAvailable: boolean;
|
||||
}
|
||||
|
||||
export interface HostsSortField {
|
||||
|
|
|
@ -39,4 +39,5 @@ export interface UsersRelatedHostsRequestOptions extends Partial<RequestBasicOpt
|
|||
skip?: boolean;
|
||||
from: string;
|
||||
inspect?: Maybe<Inspect>;
|
||||
isNewRiskScoreModuleAvailable: boolean;
|
||||
}
|
||||
|
|
|
@ -39,4 +39,5 @@ export interface HostsRelatedUsersRequestOptions extends Partial<RequestBasicOpt
|
|||
skip?: boolean;
|
||||
from: string;
|
||||
inspect?: Maybe<Inspect>;
|
||||
isNewRiskScoreModuleAvailable: boolean;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import type { ESQuery } from '../../../../typed_json';
|
|||
|
||||
import type { Inspect, Maybe, SortField, TimerangeInput } from '../../../common';
|
||||
import type { RiskScoreEntity } from '../common';
|
||||
import type { RiskInputs } from '../../../../risk_engine';
|
||||
|
||||
export interface RiskScoreRequestOptions extends IEsSearchRequest {
|
||||
defaultIndex: string[];
|
||||
|
@ -43,6 +44,7 @@ export interface RiskStats {
|
|||
calculated_score_norm: number;
|
||||
multipliers: string[];
|
||||
calculated_level: RiskSeverity;
|
||||
inputs?: RiskInputs;
|
||||
}
|
||||
|
||||
export interface HostRiskScore {
|
||||
|
|
|
@ -9,10 +9,10 @@ import { getHostRiskIndex, getUserRiskIndex } from '.';
|
|||
|
||||
describe('hosts risk search_strategy getHostRiskIndex', () => {
|
||||
it('should properly return host index if space is specified', () => {
|
||||
expect(getHostRiskIndex('testName')).toEqual('ml_host_risk_score_latest_testName');
|
||||
expect(getHostRiskIndex('testName', true, false)).toEqual('ml_host_risk_score_latest_testName');
|
||||
});
|
||||
|
||||
it('should properly return user index if space is specified', () => {
|
||||
expect(getUserRiskIndex('testName')).toEqual('ml_user_risk_score_latest_testName');
|
||||
expect(getUserRiskIndex('testName', true, false)).toEqual('ml_user_risk_score_latest_testName');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,18 +7,30 @@
|
|||
|
||||
import type { ESQuery } from '../../../../typed_json';
|
||||
import { RISKY_HOSTS_INDEX_PREFIX, RISKY_USERS_INDEX_PREFIX } from '../../../../constants';
|
||||
import { RiskScoreEntity } from '../../../../risk_engine/types';
|
||||
import { RiskScoreEntity, getRiskScoreLatestIndex } from '../../../../risk_engine';
|
||||
|
||||
/**
|
||||
* Make sure this aligns with the index in step 6, 9 in
|
||||
* prebuilt_dev_tool_content/console_templates/enable_host_risk_score.console
|
||||
*/
|
||||
export const getHostRiskIndex = (spaceId: string, onlyLatest: boolean = true): string => {
|
||||
return `${RISKY_HOSTS_INDEX_PREFIX}${onlyLatest ? 'latest_' : ''}${spaceId}`;
|
||||
export const getHostRiskIndex = (
|
||||
spaceId: string,
|
||||
onlyLatest: boolean = true,
|
||||
isNewRiskScoreModuleAvailable: boolean
|
||||
): string => {
|
||||
return isNewRiskScoreModuleAvailable
|
||||
? getRiskScoreLatestIndex(spaceId)
|
||||
: `${RISKY_HOSTS_INDEX_PREFIX}${onlyLatest ? 'latest_' : ''}${spaceId}`;
|
||||
};
|
||||
|
||||
export const getUserRiskIndex = (spaceId: string, onlyLatest: boolean = true): string => {
|
||||
return `${RISKY_USERS_INDEX_PREFIX}${onlyLatest ? 'latest_' : ''}${spaceId}`;
|
||||
export const getUserRiskIndex = (
|
||||
spaceId: string,
|
||||
onlyLatest: boolean = true,
|
||||
isNewRiskScoreModuleAvailable: boolean
|
||||
): string => {
|
||||
return isNewRiskScoreModuleAvailable
|
||||
? getRiskScoreLatestIndex(spaceId)
|
||||
: `${RISKY_USERS_INDEX_PREFIX}${onlyLatest ? 'latest_' : ''}${spaceId}`;
|
||||
};
|
||||
|
||||
export const buildHostNamesFilter = (hostNames: string[]) => {
|
||||
|
|
|
@ -28,4 +28,5 @@ export interface UsersStrategyResponse extends IEsSearchResponse {
|
|||
|
||||
export interface UsersRequestOptions extends RequestOptionsPaginated<SortableUsersFields> {
|
||||
defaultIndex: string[];
|
||||
isNewRiskScoreModuleAvailable: boolean;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import { RelatedEntitiesQueries } from '../../../../../common/search_strategy/se
|
|||
import type { RelatedHost } from '../../../../../common/search_strategy/security_solution/related_entities/related_hosts';
|
||||
import { useSearchStrategy } from '../../use_search_strategy';
|
||||
import { FAIL_RELATED_HOSTS } from './translations';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features';
|
||||
|
||||
export interface UseUserRelatedHostsResult {
|
||||
inspect: InspectResponse;
|
||||
|
@ -49,6 +50,7 @@ export const useUserRelatedHosts = ({
|
|||
errorMessage: FAIL_RELATED_HOSTS,
|
||||
abort: skip,
|
||||
});
|
||||
const isNewRiskScoreModuleAvailable = useIsExperimentalFeatureEnabled('riskScoringRoutesEnabled');
|
||||
|
||||
const userRelatedHostsResponse = useMemo(
|
||||
() => ({
|
||||
|
@ -67,8 +69,9 @@ export const useUserRelatedHosts = ({
|
|||
factoryQueryType: RelatedEntitiesQueries.relatedHosts,
|
||||
userName,
|
||||
from,
|
||||
isNewRiskScoreModuleAvailable,
|
||||
}),
|
||||
[indexNames, from, userName]
|
||||
[indexNames, from, userName, isNewRiskScoreModuleAvailable]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -12,6 +12,7 @@ import { RelatedEntitiesQueries } from '../../../../../common/search_strategy/se
|
|||
import type { RelatedUser } from '../../../../../common/search_strategy/security_solution/related_entities/related_users';
|
||||
import { useSearchStrategy } from '../../use_search_strategy';
|
||||
import { FAIL_RELATED_USERS } from './translations';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features';
|
||||
|
||||
export interface UseHostRelatedUsersResult {
|
||||
inspect: InspectResponse;
|
||||
|
@ -34,6 +35,7 @@ export const useHostRelatedUsers = ({
|
|||
from,
|
||||
skip = false,
|
||||
}: UseHostRelatedUsersParam): UseHostRelatedUsersResult => {
|
||||
const isNewRiskScoreModuleAvailable = useIsExperimentalFeatureEnabled('riskScoringRoutesEnabled');
|
||||
const {
|
||||
loading,
|
||||
result: response,
|
||||
|
@ -67,8 +69,9 @@ export const useHostRelatedUsers = ({
|
|||
factoryQueryType: RelatedEntitiesQueries.relatedUsers,
|
||||
hostName,
|
||||
from,
|
||||
isNewRiskScoreModuleAvailable,
|
||||
}),
|
||||
[indexNames, from, hostName]
|
||||
[indexNames, from, hostName, isNewRiskScoreModuleAvailable]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -93,6 +93,7 @@ const registerAlertsTableConfiguration = (
|
|||
id: ALERTS_TABLE_REGISTRY_CONFIG_IDS.CASE,
|
||||
cases: { featureId: CASES_FEATURE_ID, owner: [APP_ID], syncAlerts: true },
|
||||
columns: alertColumns,
|
||||
|
||||
getRenderCellValue: renderCellValueHookCasePage,
|
||||
useInternalFlyout,
|
||||
useBulkActions: getBulkActionHook(TableId.alertsOnCasePage),
|
||||
|
@ -100,6 +101,20 @@ const registerAlertsTableConfiguration = (
|
|||
sort,
|
||||
showInspectButton: true,
|
||||
});
|
||||
|
||||
registerIfNotAlready(registry, {
|
||||
id: ALERTS_TABLE_REGISTRY_CONFIG_IDS.RISK_INPUTS,
|
||||
cases: { featureId: CASES_FEATURE_ID, owner: [APP_ID], syncAlerts: true },
|
||||
columns: alertColumns,
|
||||
getRenderCellValue: renderCellValueHookAlertPage,
|
||||
useActionsColumn: getUseActionColumnHook(TableId.alertsRiskInputs),
|
||||
useInternalFlyout,
|
||||
useBulkActions: getBulkActionHook(TableId.alertsRiskInputs),
|
||||
useCellActions: getUseCellActionsHook(TableId.alertsRiskInputs),
|
||||
usePersistentControls: getPersistentControlsHook(TableId.alertsRiskInputs),
|
||||
sort,
|
||||
showInspectButton: true,
|
||||
});
|
||||
};
|
||||
|
||||
const registerIfNotAlready = (
|
||||
|
|
|
@ -124,7 +124,7 @@ export interface Alert {
|
|||
|
||||
// generates default grouping option for alerts table
|
||||
export const getDefaultGroupingOptions = (tableId: TableId): GroupOption[] => {
|
||||
if (tableId === TableId.alertsOnAlertsPage) {
|
||||
if (tableId === TableId.alertsOnAlertsPage || tableId === TableId.alertsRiskInputs) {
|
||||
return [
|
||||
{
|
||||
label: i18n.ruleName,
|
||||
|
|
|
@ -38,7 +38,7 @@ import { useRiskEngineStatus } from '../api/hooks/use_risk_engine_status';
|
|||
import { useInitRiskEngineMutation } from '../api/hooks/use_init_risk_engine_mutation';
|
||||
import { useEnableRiskEngineMutation } from '../api/hooks/use_enable_risk_engine_mutation';
|
||||
import { useDisableRiskEngineMutation } from '../api/hooks/use_disable_risk_engine_mutation';
|
||||
import { RiskEngineStatus } from '../../../common/risk_engine/types';
|
||||
import { RiskEngineStatus, MAX_SPACES_COUNT } from '../../../common/risk_engine';
|
||||
|
||||
const docsLinks = [
|
||||
{
|
||||
|
@ -187,6 +187,22 @@ export const RiskScoreEnableSection = () => {
|
|||
initRiskEngineErrors = [errorBody];
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
currentRiskEngineStatus !== RiskEngineStatus.ENABLED &&
|
||||
riskEngineStatus?.is_max_amount_of_risk_engines_reached
|
||||
) {
|
||||
return (
|
||||
<EuiCallOut
|
||||
title={i18n.getMaxSpaceTitle(MAX_SPACES_COUNT)}
|
||||
color="warning"
|
||||
iconType="error"
|
||||
data-test-subj="risk-score-warning-panel"
|
||||
>
|
||||
<p>{i18n.MAX_SPACE_PANEL_MESSAGE}</p>
|
||||
</EuiCallOut>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<>
|
||||
|
@ -217,7 +233,9 @@ export const RiskScoreEnableSection = () => {
|
|||
{isUpdateAvailable && (
|
||||
<EuiFlexGroup gutterSize="s" alignItems={'center'}>
|
||||
<EuiFlexItem>
|
||||
{initRiskEngineMutation.isLoading && <EuiLoadingSpinner size="m" />}
|
||||
{initRiskEngineMutation.isLoading && !isModalVisible && (
|
||||
<EuiLoadingSpinner size="m" />
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiButtonEmpty
|
||||
disabled={initRiskEngineMutation.isLoading}
|
||||
|
|
|
@ -20,10 +20,9 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import type { BoolQuery, TimeRange, Query } from '@kbn/es-query';
|
||||
import { buildEsQuery } from '@kbn/es-query';
|
||||
import { RiskScoreEntity } from '../../../common/risk_engine/types';
|
||||
import { RiskScoreEntity, type RiskScore } from '../../../common/risk_engine';
|
||||
import { RiskScorePreviewTable } from './risk_score_preview_table';
|
||||
import * as i18n from '../translations';
|
||||
import type { RiskScore } from '../../../server/lib/risk_engine/types';
|
||||
import { useRiskScorePreview } from '../api/hooks/use_preview_risk_scores';
|
||||
import { useKibana } from '../../common/lib/kibana';
|
||||
import { SourcererScopeName } from '../../common/store/sourcerer/model';
|
||||
|
|
|
@ -12,8 +12,7 @@ import type { RiskSeverity } from '../../../common/search_strategy';
|
|||
import { RiskScore } from '../../explore/components/risk_score/severity/common';
|
||||
|
||||
import { HostDetailsLink, UserDetailsLink } from '../../common/components/links';
|
||||
import type { RiskScore as IRiskScore } from '../../../server/lib/risk_engine/types';
|
||||
import { RiskScoreEntity } from '../../../common/risk_engine/types';
|
||||
import { RiskScoreEntity, type RiskScore as IRiskScore } from '../../../common/risk_engine';
|
||||
|
||||
type RiskScoreColumn = EuiBasicTableColumn<IRiskScore> & {
|
||||
field: keyof IRiskScore;
|
||||
|
|
|
@ -244,3 +244,17 @@ export const UPDATE_PANEL_GO_TO_DISMISS = i18n.translate(
|
|||
defaultMessage: 'Dismiss',
|
||||
}
|
||||
);
|
||||
|
||||
export const getMaxSpaceTitle = (maxSpaces: number) =>
|
||||
i18n.translate('xpack.securitySolution.riskScore.maxSpacePanel.title', {
|
||||
defaultMessage:
|
||||
'Entity Risk Scoring in the current version can run in {maxSpaces} Kibana spaces.',
|
||||
values: { maxSpaces },
|
||||
});
|
||||
|
||||
export const MAX_SPACE_PANEL_MESSAGE = i18n.translate(
|
||||
'xpack.securitySolution.riskScore.maxSpacePanel.message',
|
||||
{
|
||||
defaultMessage: 'Please disable a currently running engine before enabling it here.',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { RISKY_HOSTS_DASHBOARD_TITLE, RISKY_USERS_DASHBOARD_TITLE } from '../constants';
|
||||
import { EnableRiskScore } from '../enable_risk_score';
|
||||
import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
|
||||
|
@ -20,6 +21,7 @@ import * as i18n from './translations';
|
|||
import { useQueryInspector } from '../../../../common/components/page/manage_query';
|
||||
import { RiskScoreOverTime } from '../risk_score_over_time';
|
||||
import { TopRiskScoreContributors } from '../top_risk_score_contributors';
|
||||
import { TopRiskScoreContributorsAlerts } from '../top_risk_score_contributors_alerts';
|
||||
import { useQueryToggle } from '../../../../common/containers/query_toggle';
|
||||
import {
|
||||
HostRiskScoreQueryId,
|
||||
|
@ -34,7 +36,7 @@ import { useDashboardHref } from '../../../../common/hooks/use_dashboard_href';
|
|||
import { RiskScoresNoDataDetected } from '../risk_score_onboarding/risk_score_no_data_detected';
|
||||
import { useRiskEngineStatus } from '../../../../entity_analytics/api/hooks/use_risk_engine_status';
|
||||
import { RiskScoreUpdatePanel } from '../../../../entity_analytics/components/risk_score_update_panel';
|
||||
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
|
||||
const StyledEuiFlexGroup = styled(EuiFlexGroup)`
|
||||
margin-top: ${({ theme }) => theme.eui.euiSizeL};
|
||||
`;
|
||||
|
@ -57,6 +59,7 @@ const RiskDetailsTabBodyComponent: React.FC<
|
|||
: UserRiskScoreQueryId.USER_DETAILS_RISK_SCORE,
|
||||
[riskEntity]
|
||||
);
|
||||
const isNewRiskScoreModuleAvailable = useIsExperimentalFeatureEnabled('riskScoringRoutesEnabled');
|
||||
|
||||
const severitySelectionRedux = useDeepEqualSelector((state: State) =>
|
||||
riskEntity === RiskScoreEntity.host
|
||||
|
@ -158,31 +161,47 @@ const RiskDetailsTabBodyComponent: React.FC<
|
|||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup direction="row">
|
||||
<EuiFlexItem grow={2}>
|
||||
<RiskScoreOverTime
|
||||
from={startDate}
|
||||
loading={loading}
|
||||
queryId={queryId}
|
||||
riskEntity={riskEntity}
|
||||
riskScore={data}
|
||||
title={i18n.RISK_SCORE_OVER_TIME(riskEntity)}
|
||||
to={endDate}
|
||||
toggleQuery={toggleOverTimeQuery}
|
||||
toggleStatus={overTimeToggleStatus}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{isNewRiskScoreModuleAvailable ? (
|
||||
<StyledEuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
{data?.[0] && (
|
||||
<TopRiskScoreContributorsAlerts
|
||||
toggleStatus={contributorsToggleStatus}
|
||||
toggleQuery={toggleContributorsQuery}
|
||||
riskScore={data[0]}
|
||||
riskEntity={riskEntity}
|
||||
loading={loading}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</StyledEuiFlexGroup>
|
||||
) : (
|
||||
<EuiFlexGroup direction="row">
|
||||
<EuiFlexItem grow={2}>
|
||||
<RiskScoreOverTime
|
||||
from={startDate}
|
||||
loading={loading}
|
||||
queryId={queryId}
|
||||
riskEntity={riskEntity}
|
||||
riskScore={data}
|
||||
title={i18n.RISK_SCORE_OVER_TIME(riskEntity)}
|
||||
to={endDate}
|
||||
toggleQuery={toggleOverTimeQuery}
|
||||
toggleStatus={overTimeToggleStatus}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={1}>
|
||||
<TopRiskScoreContributors
|
||||
loading={loading}
|
||||
queryId={queryId}
|
||||
toggleStatus={contributorsToggleStatus}
|
||||
toggleQuery={toggleContributorsQuery}
|
||||
rules={rules}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexItem grow={1}>
|
||||
<TopRiskScoreContributors
|
||||
loading={loading}
|
||||
queryId={queryId}
|
||||
toggleStatus={contributorsToggleStatus}
|
||||
toggleQuery={toggleContributorsQuery}
|
||||
rules={rules}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
|
||||
<StyledEuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -197,6 +216,7 @@ const RiskDetailsTabBodyComponent: React.FC<
|
|||
{i18n.VIEW_DASHBOARD_BUTTON}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<RiskInformationButtonEmpty riskEntity={riskEntity} />
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -15,6 +15,7 @@ import { RiskScoreHeaderTitle } from './risk_score_header_title';
|
|||
import { RiskScoreRestartButton } from './risk_score_restart_button';
|
||||
import type { inputsModel } from '../../../../common/store';
|
||||
import * as overviewI18n from '../../../../overview/components/entity_analytics/common/translations';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
|
||||
|
||||
const RiskScoresNoDataDetectedComponent = ({
|
||||
entityType,
|
||||
|
@ -23,6 +24,8 @@ const RiskScoresNoDataDetectedComponent = ({
|
|||
entityType: RiskScoreEntity;
|
||||
refetch: inputsModel.Refetch;
|
||||
}) => {
|
||||
const isNewRiskScoreModuleAvailable = useIsExperimentalFeatureEnabled('riskScoringRoutesEnabled');
|
||||
|
||||
const translations = useMemo(
|
||||
() => ({
|
||||
title:
|
||||
|
@ -47,9 +50,13 @@ const RiskScoresNoDataDetectedComponent = ({
|
|||
title={<h2>{translations.title}</h2>}
|
||||
body={translations.body}
|
||||
actions={
|
||||
<EuiToolTip content={i18n.RESTART_TOOLTIP}>
|
||||
<RiskScoreRestartButton refetch={refetch} riskScoreEntity={entityType} />
|
||||
</EuiToolTip>
|
||||
<>
|
||||
{!isNewRiskScoreModuleAvailable && (
|
||||
<EuiToolTip content={i18n.RESTART_TOOLTIP}>
|
||||
<RiskScoreRestartButton refetch={refetch} riskScoreEntity={entityType} />
|
||||
</EuiToolTip>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</EuiPanel>
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* 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 React, { useCallback, useMemo } from 'react';
|
||||
import { TableId } from '@kbn/securitysolution-data-table';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
|
||||
import type { Filter } from '@kbn/es-query';
|
||||
|
||||
import { HeaderSection } from '../../../../common/components/header_section';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import type { RiskInputs } from '../../../../../common/risk_engine';
|
||||
import { RiskScoreEntity } from '../../../../../common/risk_engine';
|
||||
import type { HostRiskScore, UserRiskScore } from '../../../../../common/search_strategy';
|
||||
import { ALERTS_TABLE_REGISTRY_CONFIG_IDS } from '../../../../../common/constants';
|
||||
import { AlertsTableComponent } from '../../../../detections/components/alerts_table';
|
||||
import { GroupedAlertsTable } from '../../../../detections/components/alerts_table/alerts_grouping';
|
||||
import { useGlobalTime } from '../../../../common/containers/use_global_time';
|
||||
import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
|
||||
import { inputsSelectors } from '../../../../common/store/inputs';
|
||||
import { useUserData } from '../../../../detections/components/user_info';
|
||||
import { useSourcererDataView } from '../../../../common/containers/sourcerer';
|
||||
import { SourcererScopeName } from '../../../../common/store/sourcerer/model';
|
||||
|
||||
export interface TopRiskScoreContributorsAlertsProps {
|
||||
toggleStatus: boolean;
|
||||
toggleQuery?: (status: boolean) => void;
|
||||
riskScore: HostRiskScore | UserRiskScore;
|
||||
riskEntity: RiskScoreEntity;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export const TopRiskScoreContributorsAlerts: React.FC<TopRiskScoreContributorsAlertsProps> = ({
|
||||
toggleStatus,
|
||||
toggleQuery,
|
||||
riskScore,
|
||||
riskEntity,
|
||||
loading,
|
||||
}) => {
|
||||
const { to, from } = useGlobalTime();
|
||||
const [{ loading: userInfoLoading, signalIndexName, hasIndexWrite, hasIndexMaintenance }] =
|
||||
useUserData();
|
||||
const { runtimeMappings } = useSourcererDataView(SourcererScopeName.detections);
|
||||
const getGlobalFiltersQuerySelector = useMemo(
|
||||
() => inputsSelectors.globalFiltersQuerySelector(),
|
||||
[]
|
||||
);
|
||||
const getGlobalQuerySelector = useMemo(() => inputsSelectors.globalQuerySelector(), []);
|
||||
|
||||
const query = useDeepEqualSelector(getGlobalQuerySelector);
|
||||
const filters = useDeepEqualSelector(getGlobalFiltersQuerySelector);
|
||||
|
||||
const inputFilters = useMemo(() => {
|
||||
const riskScoreEntity =
|
||||
riskEntity === RiskScoreEntity.host
|
||||
? (riskScore as HostRiskScore).host
|
||||
: (riskScore as UserRiskScore).user;
|
||||
|
||||
const riskInputs = (riskScoreEntity?.risk?.inputs ?? []) as RiskInputs;
|
||||
return [
|
||||
{
|
||||
meta: {
|
||||
alias: null,
|
||||
negate: false,
|
||||
disabled: false,
|
||||
},
|
||||
query: {
|
||||
terms: {
|
||||
_id: riskInputs.map((item) => item.id),
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
}, [riskScore, riskEntity]);
|
||||
|
||||
const renderGroupedAlertTable = useCallback(
|
||||
(groupingFilters: Filter[]) => {
|
||||
return (
|
||||
<AlertsTableComponent
|
||||
configId={ALERTS_TABLE_REGISTRY_CONFIG_IDS.RISK_INPUTS}
|
||||
flyoutSize="m"
|
||||
inputFilters={[...inputFilters, ...filters, ...groupingFilters]}
|
||||
tableId={TableId.alertsRiskInputs}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[inputFilters, filters]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPanel hasBorder data-test-subj="topRiskScoreContributorsAlerts">
|
||||
<EuiFlexGroup gutterSize={'none'}>
|
||||
<EuiFlexItem grow={1}>
|
||||
<HeaderSection
|
||||
title={i18n.TOP_RISK_SCORE_CONTRIBUTORS}
|
||||
hideSubtitle
|
||||
toggleQuery={toggleQuery}
|
||||
toggleStatus={toggleStatus}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
{toggleStatus && (
|
||||
<EuiFlexGroup
|
||||
data-test-subj="topRiskScoreContributorsAlerts-table"
|
||||
gutterSize="none"
|
||||
direction="column"
|
||||
>
|
||||
<EuiFlexItem grow={1}>
|
||||
<GroupedAlertsTable
|
||||
defaultFilters={[...inputFilters, ...filters]}
|
||||
from={from}
|
||||
globalFilters={filters}
|
||||
globalQuery={query}
|
||||
hasIndexMaintenance={hasIndexMaintenance ?? false}
|
||||
hasIndexWrite={hasIndexWrite ?? false}
|
||||
loading={userInfoLoading || loading}
|
||||
renderChildComponent={renderGroupedAlertTable}
|
||||
runtimeMappings={runtimeMappings}
|
||||
signalIndexName={signalIndexName}
|
||||
tableId={TableId.alertsRiskInputs}
|
||||
to={to}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const TOP_RISK_SCORE_CONTRIBUTORS = i18n.translate(
|
||||
'xpack.securitySolution.hosts.topRiskScoreContributorsTable.title',
|
||||
{
|
||||
defaultMessage: 'Top risk score contributors',
|
||||
}
|
||||
);
|
|
@ -28,6 +28,7 @@ import { isIndexNotFoundError } from '../../../../common/utils/exceptions';
|
|||
import type { inputsModel } from '../../../../common/store';
|
||||
import { useSpaceId } from '../../../../common/hooks/use_space_id';
|
||||
import { useSearchStrategy } from '../../../../common/containers/use_search_strategy';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
|
||||
|
||||
export interface RiskScoreState<T extends RiskScoreEntity.host | RiskScoreEntity.user> {
|
||||
data:
|
||||
|
@ -83,10 +84,11 @@ export const useRiskScore = <T extends RiskScoreEntity.host | RiskScoreEntity.us
|
|||
includeAlertsCount = false,
|
||||
}: UseRiskScore<T>): RiskScoreState<T> => {
|
||||
const spaceId = useSpaceId();
|
||||
const isNewRiskScoreModuleAvailable = useIsExperimentalFeatureEnabled('riskScoringRoutesEnabled');
|
||||
const defaultIndex = spaceId
|
||||
? riskEntity === RiskScoreEntity.host
|
||||
? getHostRiskIndex(spaceId, onlyLatest)
|
||||
: getUserRiskIndex(spaceId, onlyLatest)
|
||||
? getHostRiskIndex(spaceId, onlyLatest, isNewRiskScoreModuleAvailable)
|
||||
: getUserRiskIndex(spaceId, onlyLatest, isNewRiskScoreModuleAvailable)
|
||||
: undefined;
|
||||
const factoryQueryType =
|
||||
riskEntity === RiskScoreEntity.host ? RiskQueries.hostsRiskScore : RiskQueries.usersRiskScore;
|
||||
|
|
|
@ -25,6 +25,7 @@ import { useSearchStrategy } from '../../../../common/containers/use_search_stra
|
|||
import type { InspectResponse } from '../../../../types';
|
||||
import type { inputsModel } from '../../../../common/store';
|
||||
import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
|
||||
|
||||
interface RiskScoreKpi {
|
||||
error: unknown;
|
||||
|
@ -52,10 +53,11 @@ export const useRiskScoreKpi = ({
|
|||
const { addError } = useAppToasts();
|
||||
const spaceId = useSpaceId();
|
||||
const featureEnabled = useMlCapabilities().isPlatinumOrTrialLicense;
|
||||
const isNewRiskScoreModuleAvailable = useIsExperimentalFeatureEnabled('riskScoringRoutesEnabled');
|
||||
const defaultIndex = spaceId
|
||||
? riskEntity === RiskScoreEntity.host
|
||||
? getHostRiskIndex(spaceId)
|
||||
: getUserRiskIndex(spaceId)
|
||||
? getHostRiskIndex(spaceId, true, isNewRiskScoreModuleAvailable)
|
||||
: getUserRiskIndex(spaceId, true, isNewRiskScoreModuleAvailable)
|
||||
: undefined;
|
||||
|
||||
const { loading, result, search, refetch, inspect, error } =
|
||||
|
|
|
@ -25,6 +25,7 @@ import type { ESTermQuery } from '../../../../../common/typed_json';
|
|||
import * as i18n from './translations';
|
||||
import type { InspectResponse } from '../../../../types';
|
||||
import { useSearchStrategy } from '../../../../common/containers/use_search_strategy';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
|
||||
|
||||
export const ID = 'hostsAllQuery';
|
||||
|
||||
|
@ -64,6 +65,8 @@ export const useAllHost = ({
|
|||
getHostsSelector(state, type)
|
||||
);
|
||||
|
||||
const isNewRiskScoreModuleAvailable = useIsExperimentalFeatureEnabled('riskScoringRoutesEnabled');
|
||||
|
||||
const [hostsRequest, setHostRequest] = useState<HostsRequestOptions | null>(null);
|
||||
|
||||
const wrappedLoadMore = useCallback(
|
||||
|
@ -145,13 +148,24 @@ export const useAllHost = ({
|
|||
direction,
|
||||
field: sortField,
|
||||
},
|
||||
isNewRiskScoreModuleAvailable,
|
||||
};
|
||||
if (!deepEqual(prevRequest, myRequest)) {
|
||||
return myRequest;
|
||||
}
|
||||
return prevRequest;
|
||||
});
|
||||
}, [activePage, direction, endDate, filterQuery, indexNames, limit, startDate, sortField]);
|
||||
}, [
|
||||
activePage,
|
||||
direction,
|
||||
endDate,
|
||||
filterQuery,
|
||||
indexNames,
|
||||
limit,
|
||||
startDate,
|
||||
sortField,
|
||||
isNewRiskScoreModuleAvailable,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!skip && hostsRequest) {
|
||||
|
|
|
@ -19,6 +19,7 @@ import { generateTablePaginationOptions } from '../../../components/paginated_ta
|
|||
import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
|
||||
import { usersSelectors } from '../../store';
|
||||
import { useQueryToggle } from '../../../../common/containers/query_toggle';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
|
||||
|
||||
const UsersTableManage = manageQuery(UsersTable);
|
||||
|
||||
|
@ -42,6 +43,7 @@ export const AllUsersQueryTabBody = ({
|
|||
|
||||
const getUsersSelector = useMemo(() => usersSelectors.allUsersSelector(), []);
|
||||
const { activePage, limit, sort } = useDeepEqualSelector((state) => getUsersSelector(state));
|
||||
const isNewRiskScoreModuleAvailable = useIsExperimentalFeatureEnabled('riskScoringRoutesEnabled');
|
||||
|
||||
const {
|
||||
loading,
|
||||
|
@ -76,9 +78,21 @@ export const AllUsersQueryTabBody = ({
|
|||
},
|
||||
pagination: generateTablePaginationOptions(activePage, limit),
|
||||
sort,
|
||||
isNewRiskScoreModuleAvailable,
|
||||
});
|
||||
}
|
||||
}, [search, startDate, endDate, filterQuery, indexNames, querySkip, activePage, limit, sort]);
|
||||
}, [
|
||||
search,
|
||||
startDate,
|
||||
endDate,
|
||||
filterQuery,
|
||||
indexNames,
|
||||
querySkip,
|
||||
activePage,
|
||||
limit,
|
||||
sort,
|
||||
isNewRiskScoreModuleAvailable,
|
||||
]);
|
||||
|
||||
return (
|
||||
<UsersTableManage
|
||||
|
|
|
@ -72,6 +72,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
|
|||
ruleExecutionLoggerFactory,
|
||||
version,
|
||||
isPreview,
|
||||
experimentalFeatures,
|
||||
}) =>
|
||||
(type) => {
|
||||
const { alertIgnoreFields: ignoreFields, alertMergeStrategy: mergeStrategy } = config;
|
||||
|
@ -340,7 +341,8 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
|
|||
const bulkCreate = bulkCreateFactory(
|
||||
alertWithPersistence,
|
||||
refresh,
|
||||
ruleExecutionLogger
|
||||
ruleExecutionLogger,
|
||||
experimentalFeatures
|
||||
);
|
||||
|
||||
const alertTimestampOverride = isPreview ? startedAt : undefined;
|
||||
|
|
|
@ -17,6 +17,7 @@ import type {
|
|||
BaseFieldsLatest,
|
||||
WrappedFieldsLatest,
|
||||
} from '../../../../../common/api/detection_engine/model/alerts';
|
||||
import type { ExperimentalFeatures } from '../../../../../common';
|
||||
|
||||
export interface GenericBulkCreateResponse<T extends BaseFieldsLatest> {
|
||||
success: boolean;
|
||||
|
@ -32,14 +33,16 @@ export const bulkCreateFactory =
|
|||
(
|
||||
alertWithPersistence: PersistenceAlertService,
|
||||
refreshForBulkCreate: RefreshTypes,
|
||||
ruleExecutionLogger: IRuleExecutionLogForExecutors
|
||||
ruleExecutionLogger: IRuleExecutionLogForExecutors,
|
||||
experimentalFeatures?: ExperimentalFeatures
|
||||
) =>
|
||||
async <T extends BaseFieldsLatest>(
|
||||
wrappedDocs: Array<WrappedFieldsLatest<T>>,
|
||||
maxAlerts?: number,
|
||||
enrichAlerts?: (
|
||||
alerts: Array<Pick<WrappedFieldsLatest<T>, '_id' | '_source'>>,
|
||||
params: { spaceId: string }
|
||||
params: { spaceId: string },
|
||||
experimentalFeatures?: ExperimentalFeatures
|
||||
) => Promise<Array<Pick<WrappedFieldsLatest<T>, '_id' | '_source'>>>
|
||||
): Promise<GenericBulkCreateResponse<T>> => {
|
||||
if (wrappedDocs.length === 0) {
|
||||
|
@ -63,7 +66,7 @@ export const bulkCreateFactory =
|
|||
enrichAlertsWrapper = async (alerts, params) => {
|
||||
enrichmentsTimeStart = performance.now();
|
||||
try {
|
||||
const enrichedAlerts = await enrichAlerts(alerts, params);
|
||||
const enrichedAlerts = await enrichAlerts(alerts, params, experimentalFeatures);
|
||||
return enrichedAlerts;
|
||||
} catch (error) {
|
||||
ruleExecutionLogger.error(`Enrichments failed ${error}`);
|
||||
|
|
|
@ -136,6 +136,7 @@ export interface CreateSecurityRuleTypeWrapperProps {
|
|||
ruleExecutionLoggerFactory: IRuleMonitoringService['createRuleExecutionLogClientForExecutors'];
|
||||
version: string;
|
||||
isPreview?: boolean;
|
||||
experimentalFeatures?: ExperimentalFeatures;
|
||||
}
|
||||
|
||||
export type CreateSecurityRuleTypeWrapper = (
|
||||
|
|
|
@ -16,10 +16,11 @@ import { getFieldValue } from '../utils/events';
|
|||
export const getIsHostRiskScoreAvailable: GetIsRiskScoreAvailable = async ({
|
||||
spaceId,
|
||||
services,
|
||||
isNewRiskScoreModuleAvailable,
|
||||
}) => {
|
||||
const isHostRiskScoreIndexExist = await services.scopedClusterClient.asCurrentUser.indices.exists(
|
||||
{
|
||||
index: getHostRiskIndex(spaceId),
|
||||
index: getHostRiskIndex(spaceId, true, isNewRiskScoreModuleAvailable),
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -31,10 +32,11 @@ export const createHostRiskEnrichments: CreateRiskEnrichment = async ({
|
|||
logger,
|
||||
events,
|
||||
spaceId,
|
||||
isNewRiskScoreModuleAvailable,
|
||||
}) => {
|
||||
return createSingleFieldMatchEnrichment({
|
||||
name: 'Host Risk',
|
||||
index: [getHostRiskIndex(spaceId)],
|
||||
index: [getHostRiskIndex(spaceId, true, isNewRiskScoreModuleAvailable)],
|
||||
services,
|
||||
logger,
|
||||
events,
|
||||
|
|
|
@ -15,10 +15,11 @@ import { getFieldValue } from '../utils/events';
|
|||
export const getIsUserRiskScoreAvailable: GetIsRiskScoreAvailable = async ({
|
||||
services,
|
||||
spaceId,
|
||||
isNewRiskScoreModuleAvailable,
|
||||
}) => {
|
||||
const isUserRiskScoreIndexExist = await services.scopedClusterClient.asCurrentUser.indices.exists(
|
||||
{
|
||||
index: getUserRiskIndex(spaceId),
|
||||
index: getUserRiskIndex(spaceId, true, isNewRiskScoreModuleAvailable),
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -30,10 +31,11 @@ export const createUserRiskEnrichments: CreateRiskEnrichment = async ({
|
|||
logger,
|
||||
events,
|
||||
spaceId,
|
||||
isNewRiskScoreModuleAvailable,
|
||||
}) => {
|
||||
return createSingleFieldMatchEnrichment({
|
||||
name: 'User Risk',
|
||||
index: [getUserRiskIndex(spaceId)],
|
||||
index: [getUserRiskIndex(spaceId, true, isNewRiskScoreModuleAvailable)],
|
||||
services,
|
||||
logger,
|
||||
events,
|
||||
|
|
|
@ -21,15 +21,22 @@ import type {
|
|||
} from './types';
|
||||
import { applyEnrichmentsToEvents } from './utils/transforms';
|
||||
|
||||
export const enrichEvents: EnrichEventsFunction = async ({ services, logger, events, spaceId }) => {
|
||||
export const enrichEvents: EnrichEventsFunction = async ({
|
||||
services,
|
||||
logger,
|
||||
events,
|
||||
spaceId,
|
||||
experimentalFeatures,
|
||||
}) => {
|
||||
try {
|
||||
const enrichments = [];
|
||||
|
||||
logger.debug('Alert enrichments started');
|
||||
const isNewRiskScoreModuleAvailable = experimentalFeatures?.riskScoringRoutesEnabled ?? false;
|
||||
|
||||
const [isHostRiskScoreIndexExist, isUserRiskScoreIndexExist] = await Promise.all([
|
||||
getIsHostRiskScoreAvailable({ spaceId, services }),
|
||||
getIsUserRiskScoreAvailable({ spaceId, services }),
|
||||
getIsHostRiskScoreAvailable({ spaceId, services, isNewRiskScoreModuleAvailable }),
|
||||
getIsUserRiskScoreAvailable({ spaceId, services, isNewRiskScoreModuleAvailable }),
|
||||
]);
|
||||
|
||||
if (isHostRiskScoreIndexExist) {
|
||||
|
@ -39,6 +46,7 @@ export const enrichEvents: EnrichEventsFunction = async ({ services, logger, eve
|
|||
logger,
|
||||
events,
|
||||
spaceId,
|
||||
isNewRiskScoreModuleAvailable,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -50,6 +58,7 @@ export const enrichEvents: EnrichEventsFunction = async ({ services, logger, eve
|
|||
logger,
|
||||
events,
|
||||
spaceId,
|
||||
isNewRiskScoreModuleAvailable,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -73,10 +82,11 @@ export const enrichEvents: EnrichEventsFunction = async ({ services, logger, eve
|
|||
|
||||
export const createEnrichEventsFunction: CreateEnrichEventsFunction =
|
||||
({ services, logger }) =>
|
||||
(events, { spaceId }: { spaceId: string }) =>
|
||||
(events, { spaceId }: { spaceId: string }, experimentalFeatures) =>
|
||||
enrichEvents({
|
||||
events,
|
||||
services,
|
||||
logger,
|
||||
spaceId,
|
||||
experimentalFeatures,
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import type { Filter } from '@kbn/es-query';
|
||||
|
||||
import type { ExperimentalFeatures } from '../../../../../../common';
|
||||
import type {
|
||||
BaseFieldsLatest,
|
||||
WrappedFieldsLatest,
|
||||
|
@ -75,11 +76,13 @@ export type SearchEnrichments = (params: {
|
|||
export type GetIsRiskScoreAvailable = (params: {
|
||||
spaceId: string;
|
||||
services: RuleServices;
|
||||
isNewRiskScoreModuleAvailable: boolean;
|
||||
}) => Promise<boolean>;
|
||||
|
||||
export type CreateRiskEnrichment = <T extends BaseFieldsLatest>(
|
||||
params: BasedEnrichParamters<T> & {
|
||||
spaceId: string;
|
||||
isNewRiskScoreModuleAvailable: boolean;
|
||||
}
|
||||
) => Promise<EventsMapByEnrichments>;
|
||||
|
||||
|
@ -96,6 +99,7 @@ export type CreateFieldsMatchEnrichment = <T extends BaseFieldsLatest>(
|
|||
export type EnrichEventsFunction = <T extends BaseFieldsLatest>(
|
||||
params: BasedEnrichParamters<T> & {
|
||||
spaceId: string;
|
||||
experimentalFeatures?: ExperimentalFeatures;
|
||||
}
|
||||
) => Promise<Array<EventsForEnrichment<T>>>;
|
||||
|
||||
|
@ -106,7 +110,8 @@ export type CreateEnrichEventsFunction = (params: {
|
|||
|
||||
export type EnrichEvents = <T extends BaseFieldsLatest>(
|
||||
alerts: Array<EventsForEnrichment<T>>,
|
||||
params: { spaceId: string }
|
||||
params: { spaceId: string },
|
||||
experimentalFeatures?: ExperimentalFeatures
|
||||
) => Promise<Array<EventsForEnrichment<T>>>;
|
||||
|
||||
interface Risk {
|
||||
|
|
|
@ -11,6 +11,7 @@ const createRiskEngineDataClientMock = () =>
|
|||
({
|
||||
getWriter: jest.fn(),
|
||||
initializeResources: jest.fn(),
|
||||
init: jest.fn(),
|
||||
} as unknown as jest.Mocked<RiskEngineDataClient>);
|
||||
|
||||
export const riskEngineDataClientMock = { create: createRiskEngineDataClientMock };
|
||||
|
|
|
@ -20,7 +20,9 @@ export const calculateAndPersistRiskScores = async (
|
|||
}
|
||||
): Promise<CalculateAndPersistScoresResponse> => {
|
||||
const { riskEngineDataClient, spaceId, ...rest } = params;
|
||||
const writer = await riskEngineDataClient.getWriter({ namespace: spaceId });
|
||||
const writer = await riskEngineDataClient.getWriter({
|
||||
namespace: spaceId,
|
||||
});
|
||||
const { after_keys: afterKeys, scores } = await calculateRiskScores(rest);
|
||||
|
||||
if (!scores.host?.length && !scores.user?.length) {
|
||||
|
|
|
@ -15,7 +15,12 @@ import {
|
|||
ALERT_RULE_NAME,
|
||||
EVENT_KIND,
|
||||
} from '@kbn/rule-registry-plugin/common/technical_rule_data_field_names';
|
||||
import type { AfterKeys, IdentifierType, RiskWeights } from '../../../common/risk_engine';
|
||||
import type {
|
||||
AfterKeys,
|
||||
IdentifierType,
|
||||
RiskWeights,
|
||||
RiskScore,
|
||||
} from '../../../common/risk_engine';
|
||||
import { RiskCategories } from '../../../common/risk_engine';
|
||||
import { withSecuritySpan } from '../../utils/with_security_span';
|
||||
import { getAfterKeyForIdentifierType, getFieldForIdentifierAgg } from './helpers';
|
||||
|
@ -30,7 +35,6 @@ import type {
|
|||
CalculateRiskScoreAggregations,
|
||||
CalculateScoresParams,
|
||||
CalculateScoresResponse,
|
||||
RiskScore,
|
||||
RiskScoreBucket,
|
||||
} from './types';
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
import type { FieldMap } from '@kbn/alerts-as-data-utils';
|
||||
import type { IdentifierType } from '../../../common/risk_engine';
|
||||
import { RiskScoreEntity } from '../../../common/risk_engine/types';
|
||||
import { RiskScoreEntity, riskScoreBaseIndexName } from '../../../common/risk_engine';
|
||||
import type { IIndexPatternString } from './utils/create_datastream';
|
||||
|
||||
export const ilmPolicy = {
|
||||
|
@ -56,6 +56,11 @@ const commonRiskFields: FieldMap = {
|
|||
array: false,
|
||||
required: false,
|
||||
},
|
||||
category_1_count: {
|
||||
type: 'long',
|
||||
array: false,
|
||||
required: false,
|
||||
},
|
||||
inputs: {
|
||||
type: 'object',
|
||||
array: true,
|
||||
|
@ -139,9 +144,30 @@ export const ilmPolicyName = '.risk-score-ilm-policy';
|
|||
export const mappingComponentName = '.risk-score-mappings';
|
||||
export const totalFieldsLimit = 1000;
|
||||
|
||||
const riskScoreBaseIndexName = 'risk-score';
|
||||
|
||||
export const getIndexPattern = (namespace: string): IIndexPatternString => ({
|
||||
export const getIndexPatternDataStream = (namespace: string): IIndexPatternString => ({
|
||||
template: `.${riskScoreBaseIndexName}.${riskScoreBaseIndexName}-${namespace}-index-template`,
|
||||
alias: `${riskScoreBaseIndexName}.${riskScoreBaseIndexName}-${namespace}`,
|
||||
});
|
||||
|
||||
export const getLatestTransformId = (namespace: string): string =>
|
||||
`risk_score_latest_transform_${namespace}`;
|
||||
|
||||
export const getTransformOptions = ({ dest, source }: { dest: string; source: string[] }) => ({
|
||||
dest: {
|
||||
index: dest,
|
||||
},
|
||||
frequency: '1h',
|
||||
latest: {
|
||||
sort: '@timestamp',
|
||||
unique_key: [`host.name`, `user.name`],
|
||||
},
|
||||
source: {
|
||||
index: source,
|
||||
},
|
||||
sync: {
|
||||
time: {
|
||||
delay: '2s',
|
||||
field: '@timestamp',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -15,10 +15,11 @@ import {
|
|||
elasticsearchServiceMock,
|
||||
savedObjectsClientMock,
|
||||
} from '@kbn/core/server/mocks';
|
||||
import type { AuthenticatedUser } from '@kbn/security-plugin/common/model';
|
||||
import { RiskEngineDataClient } from './risk_engine_data_client';
|
||||
import { createDataStream } from './utils/create_datastream';
|
||||
import * as savedObjectConfig from './utils/saved_object_configuration';
|
||||
import * as transforms from './utils/transforms';
|
||||
import { createIndex } from './utils/create_index';
|
||||
|
||||
const getSavedObjectConfiguration = (attributes = {}) => ({
|
||||
page: 1,
|
||||
|
@ -65,19 +66,33 @@ jest.mock('./utils/create_datastream', () => ({
|
|||
createDataStream: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../risk_score/transform/helpers/transforms', () => ({
|
||||
createAndStartTransform: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('./utils/create_index', () => ({
|
||||
createIndex: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.spyOn(transforms, 'createTransform').mockResolvedValue(Promise.resolve());
|
||||
jest.spyOn(transforms, 'startTransform').mockResolvedValue(Promise.resolve());
|
||||
|
||||
describe('RiskEngineDataClient', () => {
|
||||
let riskEngineDataClient: RiskEngineDataClient;
|
||||
let mockSavedObjectClient: ReturnType<typeof savedObjectsClientMock.create>;
|
||||
let logger: ReturnType<typeof loggingSystemMock.createLogger>;
|
||||
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
const mockSavedObjectClient = savedObjectsClientMock.create();
|
||||
const esClient = elasticsearchServiceMock.createScopedClusterClient().asCurrentUser;
|
||||
const totalFieldsLimit = 1000;
|
||||
|
||||
beforeEach(() => {
|
||||
logger = loggingSystemMock.createLogger();
|
||||
mockSavedObjectClient = savedObjectsClientMock.create();
|
||||
const options = {
|
||||
logger,
|
||||
kibanaVersion: '8.9.0',
|
||||
elasticsearchClientPromise: Promise.resolve(esClient),
|
||||
esClient,
|
||||
soClient: mockSavedObjectClient,
|
||||
namespace: 'default',
|
||||
};
|
||||
riskEngineDataClient = new RiskEngineDataClient(options);
|
||||
});
|
||||
|
@ -101,13 +116,6 @@ describe('RiskEngineDataClient', () => {
|
|||
expect(writer1).toEqual(writer2);
|
||||
expect(writer2).not.toEqual(writer3);
|
||||
});
|
||||
|
||||
it('should cache writer and not call initializeResources for a second tme', async () => {
|
||||
const initializeResourcesSpy = jest.spyOn(riskEngineDataClient, 'initializeResources');
|
||||
await riskEngineDataClient.getWriter({ namespace: 'default' });
|
||||
await riskEngineDataClient.getWriter({ namespace: 'default' });
|
||||
expect(initializeResourcesSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('initializeResources success', () => {
|
||||
|
@ -173,6 +181,9 @@ describe('RiskEngineDataClient', () => {
|
|||
"calculated_score_norm": Object {
|
||||
"type": "float",
|
||||
},
|
||||
"category_1_count": Object {
|
||||
"type": "long",
|
||||
},
|
||||
"category_1_score": Object {
|
||||
"type": "float",
|
||||
},
|
||||
|
@ -229,6 +240,9 @@ describe('RiskEngineDataClient', () => {
|
|||
"calculated_score_norm": Object {
|
||||
"type": "float",
|
||||
},
|
||||
"category_1_count": Object {
|
||||
"type": "long",
|
||||
},
|
||||
"category_1_score": Object {
|
||||
"type": "float",
|
||||
},
|
||||
|
@ -324,6 +338,170 @@ describe('RiskEngineDataClient', () => {
|
|||
alias: `risk-score.risk-score-default`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(createIndex).toHaveBeenCalledWith({
|
||||
logger,
|
||||
esClient,
|
||||
options: {
|
||||
index: `risk-score.risk-score-latest-default`,
|
||||
mappings: {
|
||||
dynamic: 'strict',
|
||||
properties: {
|
||||
'@timestamp': {
|
||||
type: 'date',
|
||||
},
|
||||
host: {
|
||||
properties: {
|
||||
name: {
|
||||
type: 'keyword',
|
||||
},
|
||||
risk: {
|
||||
properties: {
|
||||
calculated_level: {
|
||||
type: 'keyword',
|
||||
},
|
||||
calculated_score: {
|
||||
type: 'float',
|
||||
},
|
||||
calculated_score_norm: {
|
||||
type: 'float',
|
||||
},
|
||||
category_1_count: {
|
||||
type: 'long',
|
||||
},
|
||||
category_1_score: {
|
||||
type: 'float',
|
||||
},
|
||||
id_field: {
|
||||
type: 'keyword',
|
||||
},
|
||||
id_value: {
|
||||
type: 'keyword',
|
||||
},
|
||||
inputs: {
|
||||
properties: {
|
||||
category: {
|
||||
type: 'keyword',
|
||||
},
|
||||
description: {
|
||||
type: 'keyword',
|
||||
},
|
||||
id: {
|
||||
type: 'keyword',
|
||||
},
|
||||
index: {
|
||||
type: 'keyword',
|
||||
},
|
||||
risk_score: {
|
||||
type: 'float',
|
||||
},
|
||||
timestamp: {
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
notes: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
},
|
||||
user: {
|
||||
properties: {
|
||||
name: {
|
||||
type: 'keyword',
|
||||
},
|
||||
risk: {
|
||||
properties: {
|
||||
calculated_level: {
|
||||
type: 'keyword',
|
||||
},
|
||||
calculated_score: {
|
||||
type: 'float',
|
||||
},
|
||||
calculated_score_norm: {
|
||||
type: 'float',
|
||||
},
|
||||
category_1_count: {
|
||||
type: 'long',
|
||||
},
|
||||
category_1_score: {
|
||||
type: 'float',
|
||||
},
|
||||
id_field: {
|
||||
type: 'keyword',
|
||||
},
|
||||
id_value: {
|
||||
type: 'keyword',
|
||||
},
|
||||
inputs: {
|
||||
properties: {
|
||||
category: {
|
||||
type: 'keyword',
|
||||
},
|
||||
description: {
|
||||
type: 'keyword',
|
||||
},
|
||||
id: {
|
||||
type: 'keyword',
|
||||
},
|
||||
index: {
|
||||
type: 'keyword',
|
||||
},
|
||||
risk_score: {
|
||||
type: 'float',
|
||||
},
|
||||
timestamp: {
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
notes: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(transforms.createTransform).toHaveBeenCalledWith({
|
||||
logger,
|
||||
esClient,
|
||||
transform: {
|
||||
dest: {
|
||||
index: 'risk-score.risk-score-latest-default',
|
||||
},
|
||||
frequency: '1h',
|
||||
latest: {
|
||||
sort: '@timestamp',
|
||||
unique_key: ['host.name', 'user.name'],
|
||||
},
|
||||
source: {
|
||||
index: ['risk-score.risk-score-default'],
|
||||
},
|
||||
sync: {
|
||||
time: {
|
||||
delay: '2s',
|
||||
field: '@timestamp',
|
||||
},
|
||||
},
|
||||
transform_id: 'risk_score_latest_transform_default',
|
||||
},
|
||||
});
|
||||
|
||||
expect(transforms.startTransform).toHaveBeenCalledWith({
|
||||
esClient,
|
||||
transformId: 'risk_score_latest_transform_default',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -346,9 +524,9 @@ describe('RiskEngineDataClient', () => {
|
|||
it('should return initial status', async () => {
|
||||
const status = await riskEngineDataClient.getStatus({
|
||||
namespace: 'default',
|
||||
savedObjectsClient: mockSavedObjectClient,
|
||||
});
|
||||
expect(status).toEqual({
|
||||
isMaxAmountOfRiskEnginesReached: false,
|
||||
riskEngineStatus: 'NOT_INSTALLED',
|
||||
legacyRiskEngineStatus: 'NOT_INSTALLED',
|
||||
});
|
||||
|
@ -372,9 +550,9 @@ describe('RiskEngineDataClient', () => {
|
|||
|
||||
const status = await riskEngineDataClient.getStatus({
|
||||
namespace: 'default',
|
||||
savedObjectsClient: mockSavedObjectClient,
|
||||
});
|
||||
expect(status).toEqual({
|
||||
isMaxAmountOfRiskEnginesReached: false,
|
||||
riskEngineStatus: 'ENABLED',
|
||||
legacyRiskEngineStatus: 'NOT_INSTALLED',
|
||||
});
|
||||
|
@ -385,9 +563,9 @@ describe('RiskEngineDataClient', () => {
|
|||
|
||||
const status = await riskEngineDataClient.getStatus({
|
||||
namespace: 'default',
|
||||
savedObjectsClient: mockSavedObjectClient,
|
||||
});
|
||||
expect(status).toEqual({
|
||||
isMaxAmountOfRiskEnginesReached: false,
|
||||
riskEngineStatus: 'DISABLED',
|
||||
legacyRiskEngineStatus: 'NOT_INSTALLED',
|
||||
});
|
||||
|
@ -398,7 +576,6 @@ describe('RiskEngineDataClient', () => {
|
|||
it('should fetch transforms', async () => {
|
||||
await riskEngineDataClient.getStatus({
|
||||
namespace: 'default',
|
||||
savedObjectsClient: mockSavedObjectClient,
|
||||
});
|
||||
|
||||
expect(esClient.transform.getTransform).toHaveBeenCalledTimes(4);
|
||||
|
@ -421,10 +598,10 @@ describe('RiskEngineDataClient', () => {
|
|||
|
||||
const status = await riskEngineDataClient.getStatus({
|
||||
namespace: 'default',
|
||||
savedObjectsClient: mockSavedObjectClient,
|
||||
});
|
||||
|
||||
expect(status).toEqual({
|
||||
isMaxAmountOfRiskEnginesReached: false,
|
||||
riskEngineStatus: 'NOT_INSTALLED',
|
||||
legacyRiskEngineStatus: 'ENABLED',
|
||||
});
|
||||
|
@ -449,10 +626,7 @@ describe('RiskEngineDataClient', () => {
|
|||
|
||||
expect.assertions(1);
|
||||
try {
|
||||
await riskEngineDataClient.enableRiskEngine({
|
||||
savedObjectsClient: mockSavedObjectClient,
|
||||
user: { username: 'elastic' } as AuthenticatedUser,
|
||||
});
|
||||
await riskEngineDataClient.enableRiskEngine();
|
||||
} catch (e) {
|
||||
expect(e.message).toEqual('There no saved object configuration for risk engine');
|
||||
}
|
||||
|
@ -461,10 +635,7 @@ describe('RiskEngineDataClient', () => {
|
|||
it('should update saved object attrubute', async () => {
|
||||
mockSavedObjectClient.find.mockResolvedValueOnce(getSavedObjectConfiguration());
|
||||
|
||||
await riskEngineDataClient.enableRiskEngine({
|
||||
savedObjectsClient: mockSavedObjectClient,
|
||||
user: { username: 'elastic' } as AuthenticatedUser,
|
||||
});
|
||||
await riskEngineDataClient.enableRiskEngine();
|
||||
|
||||
expect(mockSavedObjectClient.update).toHaveBeenCalledWith(
|
||||
'risk-engine-configuration',
|
||||
|
@ -494,10 +665,7 @@ describe('RiskEngineDataClient', () => {
|
|||
|
||||
expect.assertions(1);
|
||||
try {
|
||||
await riskEngineDataClient.disableRiskEngine({
|
||||
savedObjectsClient: mockSavedObjectClient,
|
||||
user: { username: 'elastic' } as AuthenticatedUser,
|
||||
});
|
||||
await riskEngineDataClient.disableRiskEngine();
|
||||
} catch (e) {
|
||||
expect(e.message).toEqual('There no saved object configuration for risk engine');
|
||||
}
|
||||
|
@ -506,10 +674,7 @@ describe('RiskEngineDataClient', () => {
|
|||
it('should update saved object attrubute', async () => {
|
||||
mockSavedObjectClient.find.mockResolvedValueOnce(getSavedObjectConfiguration());
|
||||
|
||||
await riskEngineDataClient.disableRiskEngine({
|
||||
savedObjectsClient: mockSavedObjectClient,
|
||||
user: { username: 'elastic' } as AuthenticatedUser,
|
||||
});
|
||||
await riskEngineDataClient.disableRiskEngine();
|
||||
|
||||
expect(mockSavedObjectClient.update).toHaveBeenCalledWith(
|
||||
'risk-engine-configuration',
|
||||
|
@ -559,9 +724,7 @@ describe('RiskEngineDataClient', () => {
|
|||
|
||||
it('success', async () => {
|
||||
const initResult = await riskEngineDataClient.init({
|
||||
savedObjectsClient: mockSavedObjectClient,
|
||||
namespace: 'default',
|
||||
user: { username: 'elastic' } as AuthenticatedUser,
|
||||
});
|
||||
|
||||
expect(initResult).toEqual({
|
||||
|
@ -578,9 +741,7 @@ describe('RiskEngineDataClient', () => {
|
|||
throw new Error('Error disableLegacyRiskEngineMock');
|
||||
});
|
||||
const initResult = await riskEngineDataClient.init({
|
||||
savedObjectsClient: mockSavedObjectClient,
|
||||
namespace: 'default',
|
||||
user: { username: 'elastic' } as AuthenticatedUser,
|
||||
});
|
||||
|
||||
expect(initResult).toEqual({
|
||||
|
@ -598,9 +759,7 @@ describe('RiskEngineDataClient', () => {
|
|||
});
|
||||
|
||||
const initResult = await riskEngineDataClient.init({
|
||||
savedObjectsClient: mockSavedObjectClient,
|
||||
namespace: 'default',
|
||||
user: { username: 'elastic' } as AuthenticatedUser,
|
||||
});
|
||||
|
||||
expect(initResult).toEqual({
|
||||
|
@ -618,9 +777,7 @@ describe('RiskEngineDataClient', () => {
|
|||
});
|
||||
|
||||
const initResult = await riskEngineDataClient.init({
|
||||
savedObjectsClient: mockSavedObjectClient,
|
||||
namespace: 'default',
|
||||
user: { username: 'elastic' } as AuthenticatedUser,
|
||||
});
|
||||
|
||||
expect(initResult).toEqual({
|
||||
|
@ -638,9 +795,7 @@ describe('RiskEngineDataClient', () => {
|
|||
});
|
||||
|
||||
const initResult = await riskEngineDataClient.init({
|
||||
savedObjectsClient: mockSavedObjectClient,
|
||||
namespace: 'default',
|
||||
user: { username: 'elastic' } as AuthenticatedUser,
|
||||
});
|
||||
|
||||
expect(initResult).toEqual({
|
||||
|
@ -658,9 +813,7 @@ describe('RiskEngineDataClient', () => {
|
|||
});
|
||||
|
||||
const initResult = await riskEngineDataClient.init({
|
||||
savedObjectsClient: mockSavedObjectClient,
|
||||
namespace: 'default',
|
||||
user: { username: 'elastic' } as AuthenticatedUser,
|
||||
});
|
||||
|
||||
expect(initResult).toEqual({
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import type { Metadata } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import type { AuthenticatedUser } from '@kbn/security-plugin/common/model';
|
||||
import type { ClusterPutComponentTemplateRequest } from '@elastic/elasticsearch/lib/api/types';
|
||||
import {
|
||||
createOrUpdateComponentTemplate,
|
||||
|
@ -15,32 +14,43 @@ import {
|
|||
} from '@kbn/alerting-plugin/server';
|
||||
import { mappingFromFieldMap } from '@kbn/alerting-plugin/common';
|
||||
import { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server';
|
||||
import type { Logger, ElasticsearchClient } from '@kbn/core/server';
|
||||
import type { Logger, ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server';
|
||||
|
||||
import {
|
||||
riskScoreFieldMap,
|
||||
getIndexPattern,
|
||||
getIndexPatternDataStream,
|
||||
totalFieldsLimit,
|
||||
mappingComponentName,
|
||||
ilmPolicyName,
|
||||
ilmPolicy,
|
||||
getLatestTransformId,
|
||||
getTransformOptions,
|
||||
} from './configurations';
|
||||
import { createDataStream } from './utils/create_datastream';
|
||||
import type { RiskEngineDataWriter as Writer } from './risk_engine_data_writer';
|
||||
import { RiskEngineDataWriter } from './risk_engine_data_writer';
|
||||
import type { InitRiskEngineResult } from '../../../common/risk_engine/types';
|
||||
import { RiskEngineStatus } from '../../../common/risk_engine/types';
|
||||
import { getLegacyTransforms, removeLegacyTransforms } from './utils/risk_engine_transforms';
|
||||
import type { InitRiskEngineResult } from '../../../common/risk_engine';
|
||||
import {
|
||||
RiskEngineStatus,
|
||||
getRiskScoreLatestIndex,
|
||||
MAX_SPACES_COUNT,
|
||||
} from '../../../common/risk_engine';
|
||||
import {
|
||||
getLegacyTransforms,
|
||||
removeLegacyTransforms,
|
||||
startTransform,
|
||||
createTransform,
|
||||
} from './utils/transforms';
|
||||
import {
|
||||
updateSavedObjectAttribute,
|
||||
getConfiguration,
|
||||
initSavedObjects,
|
||||
getEnabledRiskEngineAmount,
|
||||
} from './utils/saved_object_configuration';
|
||||
import type { UpdateConfigOpts, SavedObjectsClients } from './utils/saved_object_configuration';
|
||||
import { createIndex } from './utils/create_index';
|
||||
|
||||
interface InitOpts extends SavedObjectsClients {
|
||||
interface InitOpts {
|
||||
namespace: string;
|
||||
user: AuthenticatedUser | null | undefined;
|
||||
}
|
||||
|
||||
interface InitializeRiskEngineResourcesOpts {
|
||||
|
@ -50,14 +60,16 @@ interface InitializeRiskEngineResourcesOpts {
|
|||
interface RiskEngineDataClientOpts {
|
||||
logger: Logger;
|
||||
kibanaVersion: string;
|
||||
elasticsearchClientPromise: Promise<ElasticsearchClient>;
|
||||
esClient: ElasticsearchClient;
|
||||
namespace: string;
|
||||
soClient: SavedObjectsClientContract;
|
||||
}
|
||||
|
||||
export class RiskEngineDataClient {
|
||||
private writerCache: Map<string, Writer> = new Map();
|
||||
constructor(private readonly options: RiskEngineDataClientOpts) {}
|
||||
|
||||
public async init({ namespace, savedObjectsClient, user }: InitOpts) {
|
||||
public async init({ namespace }: InitOpts) {
|
||||
const result: InitRiskEngineResult = {
|
||||
legacyRiskEngineDisabled: false,
|
||||
riskEngineResourcesInstalled: false,
|
||||
|
@ -82,7 +94,7 @@ export class RiskEngineDataClient {
|
|||
}
|
||||
|
||||
try {
|
||||
await initSavedObjects({ savedObjectsClient, user });
|
||||
await initSavedObjects({ savedObjectsClient: this.options.soClient });
|
||||
result.riskEngineConfigurationCreated = true;
|
||||
} catch (e) {
|
||||
result.errors.push(e.message);
|
||||
|
@ -90,7 +102,7 @@ export class RiskEngineDataClient {
|
|||
}
|
||||
|
||||
try {
|
||||
await this.enableRiskEngine({ savedObjectsClient, user });
|
||||
await this.enableRiskEngine();
|
||||
result.riskEngineEnabled = true;
|
||||
} catch (e) {
|
||||
result.errors.push(e.message);
|
||||
|
@ -104,14 +116,14 @@ export class RiskEngineDataClient {
|
|||
if (this.writerCache.get(namespace)) {
|
||||
return this.writerCache.get(namespace) as Writer;
|
||||
}
|
||||
|
||||
await this.initializeResources({ namespace });
|
||||
const indexPatterns = getIndexPatternDataStream(namespace);
|
||||
await this.initializeWriter(namespace, indexPatterns.alias);
|
||||
return this.writerCache.get(namespace) as Writer;
|
||||
}
|
||||
|
||||
private async initializeWriter(namespace: string, index: string): Promise<Writer> {
|
||||
const writer = new RiskEngineDataWriter({
|
||||
esClient: await this.options.elasticsearchClientPromise,
|
||||
esClient: this.options.esClient,
|
||||
namespace,
|
||||
index,
|
||||
logger: this.options.logger,
|
||||
|
@ -121,35 +133,28 @@ export class RiskEngineDataClient {
|
|||
return writer;
|
||||
}
|
||||
|
||||
public async getStatus({
|
||||
savedObjectsClient,
|
||||
namespace,
|
||||
}: SavedObjectsClients & {
|
||||
namespace: string;
|
||||
}) {
|
||||
const riskEngineStatus = await this.getCurrentStatus({ savedObjectsClient });
|
||||
public async getStatus({ namespace }: { namespace: string }) {
|
||||
const riskEngineStatus = await this.getCurrentStatus();
|
||||
const legacyRiskEngineStatus = await this.getLegacyStatus({ namespace });
|
||||
return { riskEngineStatus, legacyRiskEngineStatus };
|
||||
const isMaxAmountOfRiskEnginesReached = await this.getIsMaxAmountOfRiskEnginesReached();
|
||||
return { riskEngineStatus, legacyRiskEngineStatus, isMaxAmountOfRiskEnginesReached };
|
||||
}
|
||||
|
||||
public async enableRiskEngine({ savedObjectsClient, user }: UpdateConfigOpts) {
|
||||
public async enableRiskEngine() {
|
||||
// code to run task
|
||||
|
||||
return updateSavedObjectAttribute({
|
||||
savedObjectsClient,
|
||||
user,
|
||||
savedObjectsClient: this.options.soClient,
|
||||
attributes: {
|
||||
enabled: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public async disableRiskEngine({ savedObjectsClient, user }: UpdateConfigOpts) {
|
||||
public async disableRiskEngine() {
|
||||
// code to stop task
|
||||
|
||||
return updateSavedObjectAttribute({
|
||||
savedObjectsClient,
|
||||
user,
|
||||
savedObjectsClient: this.options.soClient,
|
||||
attributes: {
|
||||
enabled: false,
|
||||
},
|
||||
|
@ -163,9 +168,8 @@ export class RiskEngineDataClient {
|
|||
return true;
|
||||
}
|
||||
|
||||
const esClient = await this.options.elasticsearchClientPromise;
|
||||
await removeLegacyTransforms({
|
||||
esClient,
|
||||
esClient: this.options.esClient,
|
||||
namespace,
|
||||
});
|
||||
|
||||
|
@ -174,8 +178,8 @@ export class RiskEngineDataClient {
|
|||
return newlegacyRiskEngineStatus === RiskEngineStatus.NOT_INSTALLED;
|
||||
}
|
||||
|
||||
private async getCurrentStatus({ savedObjectsClient }: SavedObjectsClients) {
|
||||
const configuration = await getConfiguration({ savedObjectsClient });
|
||||
private async getCurrentStatus() {
|
||||
const configuration = await getConfiguration({ savedObjectsClient: this.options.soClient });
|
||||
|
||||
if (configuration) {
|
||||
return configuration.enabled ? RiskEngineStatus.ENABLED : RiskEngineStatus.DISABLED;
|
||||
|
@ -184,9 +188,21 @@ export class RiskEngineDataClient {
|
|||
return RiskEngineStatus.NOT_INSTALLED;
|
||||
}
|
||||
|
||||
private async getIsMaxAmountOfRiskEnginesReached() {
|
||||
try {
|
||||
const amountOfEnabledConfigurations = await getEnabledRiskEngineAmount({
|
||||
savedObjectsClient: this.options.soClient,
|
||||
});
|
||||
|
||||
return amountOfEnabledConfigurations >= MAX_SPACES_COUNT;
|
||||
} catch (e) {
|
||||
this.options.logger.error(`Error while getting amount of enabled risk engines: ${e.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async getLegacyStatus({ namespace }: { namespace: string }) {
|
||||
const esClient = await this.options.elasticsearchClientPromise;
|
||||
const transforms = await getLegacyTransforms({ namespace, esClient });
|
||||
const transforms = await getLegacyTransforms({ namespace, esClient: this.options.esClient });
|
||||
|
||||
if (transforms.length === 0) {
|
||||
return RiskEngineStatus.NOT_INSTALLED;
|
||||
|
@ -199,9 +215,9 @@ export class RiskEngineDataClient {
|
|||
namespace = DEFAULT_NAMESPACE_STRING,
|
||||
}: InitializeRiskEngineResourcesOpts) {
|
||||
try {
|
||||
const esClient = await this.options.elasticsearchClientPromise;
|
||||
const esClient = this.options.esClient;
|
||||
|
||||
const indexPatterns = getIndexPattern(namespace);
|
||||
const indexPatterns = getIndexPatternDataStream(namespace);
|
||||
|
||||
const indexMetadata: Metadata = {
|
||||
kibana: {
|
||||
|
@ -270,7 +286,29 @@ export class RiskEngineDataClient {
|
|||
indexPatterns,
|
||||
});
|
||||
|
||||
await this.initializeWriter(namespace, indexPatterns.alias);
|
||||
await createIndex({
|
||||
esClient,
|
||||
logger: this.options.logger,
|
||||
options: {
|
||||
index: getRiskScoreLatestIndex(namespace),
|
||||
mappings: mappingFromFieldMap(riskScoreFieldMap, 'strict'),
|
||||
},
|
||||
});
|
||||
|
||||
const transformId = getLatestTransformId(namespace);
|
||||
await createTransform({
|
||||
esClient,
|
||||
logger: this.options.logger,
|
||||
transform: {
|
||||
transform_id: transformId,
|
||||
...getTransformOptions({
|
||||
dest: getRiskScoreLatestIndex(namespace),
|
||||
source: [indexPatterns.alias],
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
await startTransform({ esClient, transformId });
|
||||
} catch (error) {
|
||||
this.options.logger.error(`Error initializing risk engine resources: ${error.message}`);
|
||||
throw error;
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
|
||||
import type { BulkOperationContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||
import type { Logger, ElasticsearchClient } from '@kbn/core/server';
|
||||
import type { IdentifierType } from '../../../common/risk_engine';
|
||||
import type { RiskScore } from './types';
|
||||
import type { IdentifierType, RiskScore } from '../../../common/risk_engine';
|
||||
|
||||
interface WriterBulkResponse {
|
||||
errors: string[];
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { RiskScoreService } from './risk_score_service';
|
||||
import type { RiskScore } from './types';
|
||||
import type { RiskScore } from '../../../common/risk_engine';
|
||||
|
||||
const createRiskScoreMock = (overrides: Partial<RiskScore> = {}): RiskScore => ({
|
||||
'@timestamp': '2023-02-15T00:15:19.231Z',
|
||||
|
|
|
@ -29,15 +29,10 @@ export const riskEngineDisableRoute = (
|
|||
const siemResponse = buildSiemResponse(response);
|
||||
|
||||
const securitySolution = await context.securitySolution;
|
||||
const soClient = (await context.core).savedObjects.client;
|
||||
const riskEngineClient = securitySolution.getRiskEngineDataClient();
|
||||
const user = security?.authc.getCurrentUser(request);
|
||||
|
||||
try {
|
||||
await riskEngineClient.disableRiskEngine({
|
||||
savedObjectsClient: soClient,
|
||||
user,
|
||||
});
|
||||
await riskEngineClient.disableRiskEngine();
|
||||
return response.ok({ body: { success: true } });
|
||||
} catch (e) {
|
||||
const error = transformError(e);
|
||||
|
|
|
@ -28,15 +28,10 @@ export const riskEngineEnableRoute = (
|
|||
async (context, request, response) => {
|
||||
const siemResponse = buildSiemResponse(response);
|
||||
const securitySolution = await context.securitySolution;
|
||||
const soClient = (await context.core).savedObjects.client;
|
||||
const riskEngineClient = securitySolution.getRiskEngineDataClient();
|
||||
const user = security?.authc.getCurrentUser(request);
|
||||
|
||||
try {
|
||||
await riskEngineClient.enableRiskEngine({
|
||||
savedObjectsClient: soClient,
|
||||
user,
|
||||
});
|
||||
await riskEngineClient.enableRiskEngine();
|
||||
return response.ok({ body: { success: true } });
|
||||
} catch (e) {
|
||||
const error = transformError(e);
|
||||
|
|
|
@ -29,16 +29,12 @@ export const riskEngineInitRoute = (
|
|||
async (context, request, response) => {
|
||||
const siemResponse = buildSiemResponse(response);
|
||||
const securitySolution = await context.securitySolution;
|
||||
const soClient = (await context.core).savedObjects.client;
|
||||
const riskEngineClient = securitySolution.getRiskEngineDataClient();
|
||||
const spaceId = securitySolution.getSpaceId();
|
||||
const user = security?.authc.getCurrentUser(request);
|
||||
|
||||
try {
|
||||
const initResult = await riskEngineClient.init({
|
||||
savedObjectsClient: soClient,
|
||||
namespace: spaceId,
|
||||
user,
|
||||
});
|
||||
|
||||
const initResultResponse = {
|
||||
|
|
|
@ -25,19 +25,18 @@ export const riskEngineStatusRoute = (router: SecuritySolutionPluginRouter, logg
|
|||
const siemResponse = buildSiemResponse(response);
|
||||
|
||||
const securitySolution = await context.securitySolution;
|
||||
const soClient = (await context.core).savedObjects.client;
|
||||
const riskEngineClient = securitySolution.getRiskEngineDataClient();
|
||||
const spaceId = securitySolution.getSpaceId();
|
||||
|
||||
try {
|
||||
const result = await riskEngineClient.getStatus({
|
||||
savedObjectsClient: soClient,
|
||||
namespace: spaceId,
|
||||
});
|
||||
return response.ok({
|
||||
body: {
|
||||
risk_engine_status: result.riskEngineStatus,
|
||||
legacy_risk_engine_status: result.legacyRiskEngineStatus,
|
||||
is_max_amount_of_risk_engines_reached: result.isMaxAmountOfRiskEnginesReached,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
|
|
|
@ -210,6 +210,9 @@ components:
|
|||
$ref: '#/components/schemas/RiskEngineStatus'
|
||||
risk_engine_status:
|
||||
$ref: '#/components/schemas/RiskEngineStatus'
|
||||
is_max_amount_of_risk_engines_reached:
|
||||
description: Indicates whether the maximum amount of risk engines has been reached
|
||||
type: boolean
|
||||
RiskEngineInitResponse:
|
||||
type: object
|
||||
properties:
|
||||
|
|
|
@ -5,16 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
|
||||
import type { MappingRuntimeFields, SearchResponse } from '@elastic/elasticsearch/lib/api/types';
|
||||
import type {
|
||||
AfterKey,
|
||||
AfterKeys,
|
||||
IdentifierType,
|
||||
RiskCategories,
|
||||
RiskWeights,
|
||||
RiskEngineStatus,
|
||||
RiskScore,
|
||||
} from '../../../common/risk_engine';
|
||||
import type { RiskEngineStatus } from '../../../common/risk_engine/types';
|
||||
|
||||
export interface CalculateScoresParams {
|
||||
afterKeys: AfterKeys;
|
||||
|
@ -61,6 +60,7 @@ export interface CalculateScoresResponse {
|
|||
export interface GetRiskEngineStatusResponse {
|
||||
legacy_risk_engine_status: RiskEngineStatus;
|
||||
risk_engine_status: RiskEngineStatus;
|
||||
is_max_amount_of_risk_engines_reached: boolean;
|
||||
}
|
||||
|
||||
interface InitRiskEngineResultResponse {
|
||||
|
@ -101,40 +101,6 @@ export interface DisableRiskEngineResponse {
|
|||
success: boolean;
|
||||
}
|
||||
|
||||
export interface SimpleRiskInput {
|
||||
id: string;
|
||||
index: string;
|
||||
category: RiskCategories;
|
||||
description: string;
|
||||
risk_score: string | number | undefined;
|
||||
timestamp: string | undefined;
|
||||
}
|
||||
|
||||
export type RiskInput = Ecs;
|
||||
|
||||
export interface EcsRiskScore {
|
||||
'@timestamp': string;
|
||||
host?: {
|
||||
risk: Omit<RiskScore, '@timestamp'>;
|
||||
};
|
||||
user?: {
|
||||
risk: Omit<RiskScore, '@timestamp'>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RiskScore {
|
||||
'@timestamp': string;
|
||||
id_field: string;
|
||||
id_value: string;
|
||||
calculated_level: string;
|
||||
calculated_score: number;
|
||||
calculated_score_norm: number;
|
||||
category_1_score: number;
|
||||
category_1_count: number;
|
||||
notes: string[];
|
||||
inputs: SimpleRiskInput[] | RiskInput[];
|
||||
}
|
||||
|
||||
export interface CalculateRiskScoreAggregations {
|
||||
user?: {
|
||||
after_key: AfterKey;
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 type { ElasticsearchClient, Logger } from '@kbn/core/server';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import type {
|
||||
IndicesCreateRequest,
|
||||
IndicesCreateResponse,
|
||||
} from '@elastic/elasticsearch/lib/api/types';
|
||||
|
||||
export const createIndex = async ({
|
||||
esClient,
|
||||
logger,
|
||||
options,
|
||||
}: {
|
||||
esClient: ElasticsearchClient;
|
||||
logger: Logger;
|
||||
options: IndicesCreateRequest;
|
||||
}): Promise<IndicesCreateResponse | void> => {
|
||||
try {
|
||||
const isIndexExist = await esClient.indices.exists({
|
||||
index: options.index,
|
||||
});
|
||||
if (isIndexExist) {
|
||||
logger.info('${options.index} already exist');
|
||||
return;
|
||||
}
|
||||
|
||||
return esClient.indices.create(options);
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
const fullErrorMessage = `Failed to create index: ${options.index}: ${error.message}`;
|
||||
logger.error(fullErrorMessage);
|
||||
throw new Error(fullErrorMessage);
|
||||
}
|
||||
};
|
|
@ -5,33 +5,39 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import type { SavedObject, SavedObjectsClientContract } from '@kbn/core/server';
|
||||
import type { AuthenticatedUser } from '@kbn/security-plugin/common/model';
|
||||
|
||||
import type { RiskEngineConfiguration } from '../types';
|
||||
import { riskEngineConfigurationTypeName } from '../saved_object';
|
||||
|
||||
export interface SavedObjectsClients {
|
||||
export interface SavedObjectsClientArg {
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
}
|
||||
|
||||
export interface UpdateConfigOpts extends SavedObjectsClients {
|
||||
user: AuthenticatedUser | null | undefined;
|
||||
}
|
||||
|
||||
const getConfigurationSavedObject = async ({
|
||||
savedObjectsClient,
|
||||
}: SavedObjectsClients): Promise<SavedObject<RiskEngineConfiguration> | undefined> => {
|
||||
}: SavedObjectsClientArg): Promise<SavedObject<RiskEngineConfiguration> | undefined> => {
|
||||
const savedObjectsResponse = await savedObjectsClient.find<RiskEngineConfiguration>({
|
||||
type: riskEngineConfigurationTypeName,
|
||||
});
|
||||
return savedObjectsResponse.saved_objects?.[0];
|
||||
};
|
||||
|
||||
export const getEnabledRiskEngineAmount = async ({
|
||||
savedObjectsClient,
|
||||
}: SavedObjectsClientArg): Promise<number> => {
|
||||
const savedObjectsResponse = await savedObjectsClient.find<RiskEngineConfiguration>({
|
||||
type: riskEngineConfigurationTypeName,
|
||||
namespaces: ['*'],
|
||||
});
|
||||
|
||||
return savedObjectsResponse.saved_objects?.filter((config) => config?.attributes?.enabled)
|
||||
?.length;
|
||||
};
|
||||
|
||||
export const updateSavedObjectAttribute = async ({
|
||||
savedObjectsClient,
|
||||
attributes,
|
||||
user,
|
||||
}: UpdateConfigOpts & {
|
||||
}: SavedObjectsClientArg & {
|
||||
attributes: {
|
||||
enabled: boolean;
|
||||
};
|
||||
|
@ -58,7 +64,7 @@ export const updateSavedObjectAttribute = async ({
|
|||
return result;
|
||||
};
|
||||
|
||||
export const initSavedObjects = async ({ savedObjectsClient, user }: UpdateConfigOpts) => {
|
||||
export const initSavedObjects = async ({ savedObjectsClient }: SavedObjectsClientArg) => {
|
||||
const configuration = await getConfigurationSavedObject({ savedObjectsClient });
|
||||
if (configuration) {
|
||||
return configuration;
|
||||
|
@ -71,7 +77,7 @@ export const initSavedObjects = async ({ savedObjectsClient, user }: UpdateConfi
|
|||
|
||||
export const getConfiguration = async ({
|
||||
savedObjectsClient,
|
||||
}: SavedObjectsClients): Promise<RiskEngineConfiguration | null> => {
|
||||
}: SavedObjectsClientArg): Promise<RiskEngineConfiguration | null> => {
|
||||
try {
|
||||
const savedObjectConfiguration = await getConfigurationSavedObject({
|
||||
savedObjectsClient,
|
||||
|
|
|
@ -4,11 +4,14 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ElasticsearchClient } from '@kbn/core/server';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import type { ElasticsearchClient, Logger } from '@kbn/core/server';
|
||||
import type {
|
||||
TransformGetTransformResponse,
|
||||
TransformStartTransformResponse,
|
||||
TransformPutTransformResponse,
|
||||
TransformGetTransformTransformSummary,
|
||||
TransformPutTransformRequest,
|
||||
} from '@elastic/elasticsearch/lib/api/types';
|
||||
import { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import {
|
||||
|
@ -67,3 +70,53 @@ export const removeLegacyTransforms = async ({
|
|||
|
||||
await Promise.allSettled(stopTransformRequests);
|
||||
};
|
||||
|
||||
export const createTransform = async ({
|
||||
esClient,
|
||||
transform,
|
||||
logger,
|
||||
}: {
|
||||
esClient: ElasticsearchClient;
|
||||
transform: TransformPutTransformRequest;
|
||||
logger: Logger;
|
||||
}): Promise<TransformPutTransformResponse | void> => {
|
||||
try {
|
||||
await esClient.transform.getTransform({
|
||||
transform_id: transform.transform_id,
|
||||
});
|
||||
|
||||
logger.info(`Transform ${transform.transform_id} already exists`);
|
||||
} catch (existErr) {
|
||||
const transformedError = transformError(existErr);
|
||||
if (transformedError.statusCode === 404) {
|
||||
return esClient.transform.putTransform(transform);
|
||||
} else {
|
||||
logger.error(
|
||||
`Failed to check if transform ${transform.transform_id} exists before creation: ${transformedError.message}`
|
||||
);
|
||||
throw existErr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const startTransform = async ({
|
||||
esClient,
|
||||
transformId,
|
||||
}: {
|
||||
esClient: ElasticsearchClient;
|
||||
transformId: string;
|
||||
}): Promise<TransformStartTransformResponse | void> => {
|
||||
const transformStats = await esClient.transform.getTransformStats({
|
||||
transform_id: transformId,
|
||||
});
|
||||
if (transformStats.count <= 0) {
|
||||
throw new Error(`Can't check ${transformId} status`);
|
||||
}
|
||||
if (
|
||||
transformStats.transforms[0].state === 'indexing' ||
|
||||
transformStats.transforms[0].state === 'started'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
return esClient.transform.startTransform({ transform_id: transformId });
|
||||
};
|
|
@ -62,6 +62,7 @@ export const createTransformIfNotExists = async (
|
|||
return {
|
||||
[transform.transform_id]: {
|
||||
success: false,
|
||||
isExist: true,
|
||||
error: transformError(
|
||||
new Error(
|
||||
i18n.translate('xpack.securitySolution.riskScore.transform.transformExistsTitle', {
|
||||
|
@ -78,19 +79,19 @@ export const createTransformIfNotExists = async (
|
|||
try {
|
||||
await esClient.transform.putTransform(transform);
|
||||
|
||||
return { [transform.transform_id]: { success: true, error: null } };
|
||||
return { [transform.transform_id]: { success: true, isExist: true, error: null } };
|
||||
} catch (createErr) {
|
||||
const createError = transformError(createErr);
|
||||
logger.error(
|
||||
`Failed to create transform ${transform.transform_id}: ${createError.message}`
|
||||
);
|
||||
return { [transform.transform_id]: { success: false, error: createError } };
|
||||
return { [transform.transform_id]: { success: false, isExist: false, error: createError } };
|
||||
}
|
||||
} else {
|
||||
logger.error(
|
||||
`Failed to check if transform ${transform.transform_id} exists before creation: ${existError.message}`
|
||||
);
|
||||
return { [transform.transform_id]: { success: false, error: existError } };
|
||||
return { [transform.transform_id]: { success: false, isExist: false, error: existError } };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -179,4 +180,5 @@ export const startTransformIfNotStarted = async (
|
|||
},
|
||||
};
|
||||
}
|
||||
return { [transformId]: { success: true, error: null } };
|
||||
};
|
||||
|
|
|
@ -95,7 +95,6 @@ import {
|
|||
ENDPOINT_FIELDS_SEARCH_STRATEGY,
|
||||
ENDPOINT_SEARCH_STRATEGY,
|
||||
} from '../common/endpoint/constants';
|
||||
import { RiskEngineDataClient } from './lib/risk_engine/risk_engine_data_client';
|
||||
|
||||
import { AppFeatures } from './lib/app_features';
|
||||
|
||||
|
@ -121,7 +120,6 @@ export class Plugin implements ISecuritySolutionPlugin {
|
|||
private checkMetadataTransformsTask: CheckMetadataTransformsTask | undefined;
|
||||
private telemetryUsageCounter?: UsageCounter;
|
||||
private endpointContext: EndpointAppContext;
|
||||
private riskEngineDataClient: RiskEngineDataClient | undefined;
|
||||
|
||||
constructor(context: PluginInitializerContext) {
|
||||
const serverConfig = createConfig(context);
|
||||
|
@ -163,14 +161,6 @@ export class Plugin implements ISecuritySolutionPlugin {
|
|||
|
||||
this.ruleMonitoringService.setup(core, plugins);
|
||||
|
||||
this.riskEngineDataClient = new RiskEngineDataClient({
|
||||
logger: this.logger,
|
||||
kibanaVersion: this.pluginContext.env.packageInfo.version,
|
||||
elasticsearchClientPromise: core
|
||||
.getStartServices()
|
||||
.then(([{ elasticsearch }]) => elasticsearch.client.asInternalUser),
|
||||
});
|
||||
|
||||
const requestContextFactory = new RequestContextFactory({
|
||||
config,
|
||||
logger,
|
||||
|
@ -180,7 +170,6 @@ export class Plugin implements ISecuritySolutionPlugin {
|
|||
ruleMonitoringService: this.ruleMonitoringService,
|
||||
kibanaVersion: pluginContext.env.packageInfo.version,
|
||||
kibanaBranch: pluginContext.env.packageInfo.branch,
|
||||
riskEngineDataClient: this.riskEngineDataClient,
|
||||
});
|
||||
|
||||
const router = core.http.createRouter<SecuritySolutionRequestHandlerContext>();
|
||||
|
@ -251,6 +240,7 @@ export class Plugin implements ISecuritySolutionPlugin {
|
|||
ruleExecutionLoggerFactory:
|
||||
this.ruleMonitoringService.createRuleExecutionLogClientForExecutors,
|
||||
version: pluginContext.env.packageInfo.version,
|
||||
experimentalFeatures: config.experimentalFeatures,
|
||||
};
|
||||
|
||||
const queryRuleAdditionalOptions: CreateQueryRuleAdditionalOptions = {
|
||||
|
|
|
@ -25,7 +25,7 @@ import type {
|
|||
import type { Immutable } from '../common/endpoint/types';
|
||||
import type { EndpointAuthz } from '../common/endpoint/types/authz';
|
||||
import type { EndpointAppContextService } from './endpoint/endpoint_app_context_services';
|
||||
import type { RiskEngineDataClient } from './lib/risk_engine/risk_engine_data_client';
|
||||
import { RiskEngineDataClient } from './lib/risk_engine/risk_engine_data_client';
|
||||
|
||||
export interface IRequestContextFactory {
|
||||
create(
|
||||
|
@ -43,7 +43,6 @@ interface ConstructorOptions {
|
|||
ruleMonitoringService: IRuleMonitoringService;
|
||||
kibanaVersion: string;
|
||||
kibanaBranch: string;
|
||||
riskEngineDataClient: RiskEngineDataClient;
|
||||
}
|
||||
|
||||
export class RequestContextFactory implements IRequestContextFactory {
|
||||
|
@ -58,14 +57,7 @@ export class RequestContextFactory implements IRequestContextFactory {
|
|||
request: KibanaRequest
|
||||
): Promise<SecuritySolutionApiRequestHandlerContext> {
|
||||
const { options, appClientFactory } = this;
|
||||
const {
|
||||
config,
|
||||
core,
|
||||
plugins,
|
||||
endpointAppContextService,
|
||||
ruleMonitoringService,
|
||||
riskEngineDataClient,
|
||||
} = options;
|
||||
const { config, core, plugins, endpointAppContextService, ruleMonitoringService } = options;
|
||||
|
||||
const { lists, ruleRegistry, security } = plugins;
|
||||
|
||||
|
@ -139,7 +131,16 @@ export class RequestContextFactory implements IRequestContextFactory {
|
|||
|
||||
getInternalFleetServices: memoize(() => endpointAppContextService.getInternalFleetServices()),
|
||||
|
||||
getRiskEngineDataClient: () => riskEngineDataClient,
|
||||
getRiskEngineDataClient: memoize(
|
||||
() =>
|
||||
new RiskEngineDataClient({
|
||||
logger: options.logger,
|
||||
kibanaVersion: options.kibanaVersion,
|
||||
esClient: coreContext.elasticsearch.client.asCurrentUser,
|
||||
soClient: coreContext.savedObjects.client,
|
||||
namespace: getSpaceId(),
|
||||
})
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ export const mockOptions: HostsRequestOptions = {
|
|||
pagination: { activePage: 0, cursorStart: 0, fakePossibleCount: 50, querySize: 10 },
|
||||
timerange: { interval: '12h', from: '2020-09-03T09:15:21.415Z', to: '2020-09-04T09:15:21.415Z' },
|
||||
sort: { direction: Direction.desc, field: HostsFields.lastSeen },
|
||||
isNewRiskScoreModuleAvailable: false,
|
||||
};
|
||||
|
||||
export const mockSearchStrategyResponse: IEsSearchResponse<unknown> = {
|
||||
|
|
|
@ -67,7 +67,13 @@ export const allHosts: SecuritySolutionFactory<HostsQueries.hosts> = {
|
|||
const hostNames = edges.map((edge) => getOr('', 'node.host.name[0]', edge));
|
||||
|
||||
const enhancedEdges = deps?.spaceId
|
||||
? await enhanceEdges(edges, hostNames, deps.spaceId, deps.esClient)
|
||||
? await enhanceEdges(
|
||||
edges,
|
||||
hostNames,
|
||||
deps.spaceId,
|
||||
deps.esClient,
|
||||
options.isNewRiskScoreModuleAvailable
|
||||
)
|
||||
: edges;
|
||||
|
||||
return {
|
||||
|
@ -88,9 +94,15 @@ async function enhanceEdges(
|
|||
edges: HostsEdges[],
|
||||
hostNames: string[],
|
||||
spaceId: string,
|
||||
esClient: IScopedClusterClient
|
||||
esClient: IScopedClusterClient,
|
||||
isNewRiskScoreModuleAvailable: boolean
|
||||
): Promise<HostsEdges[]> {
|
||||
const hostRiskData = await getHostRiskData(esClient, spaceId, hostNames);
|
||||
const hostRiskData = await getHostRiskData(
|
||||
esClient,
|
||||
spaceId,
|
||||
hostNames,
|
||||
isNewRiskScoreModuleAvailable
|
||||
);
|
||||
const hostsRiskByHostName: Record<string, string> | undefined = hostRiskData?.hits.hits.reduce(
|
||||
(acc, hit) => ({
|
||||
...acc,
|
||||
|
@ -113,12 +125,13 @@ async function enhanceEdges(
|
|||
export async function getHostRiskData(
|
||||
esClient: IScopedClusterClient,
|
||||
spaceId: string,
|
||||
hostNames: string[]
|
||||
hostNames: string[],
|
||||
isNewRiskScoreModuleAvailable: boolean
|
||||
) {
|
||||
try {
|
||||
const hostRiskResponse = await esClient.asCurrentUser.search<HostRiskScore>(
|
||||
buildRiskScoreQuery({
|
||||
defaultIndex: [getHostRiskIndex(spaceId)],
|
||||
defaultIndex: [getHostRiskIndex(spaceId, true, isNewRiskScoreModuleAvailable)],
|
||||
filterQuery: buildHostNamesFilter(hostNames),
|
||||
riskScoreEntity: RiskScoreEntity.host,
|
||||
})
|
||||
|
|
|
@ -21,6 +21,7 @@ export const mockOptions: UsersRelatedHostsRequestOptions = {
|
|||
factoryQueryType: RelatedEntitiesQueries.relatedHosts,
|
||||
userName: 'user1',
|
||||
from: '2020-09-02T15:17:13.678Z',
|
||||
isNewRiskScoreModuleAvailable: false,
|
||||
};
|
||||
|
||||
export const mockSearchStrategyResponse: IEsSearchResponse<unknown> = {
|
||||
|
|
|
@ -58,7 +58,12 @@ export const usersRelatedHosts: SecuritySolutionFactory<RelatedEntitiesQueries.r
|
|||
{}
|
||||
);
|
||||
const enhancedHosts = deps?.spaceId
|
||||
? await addHostRiskData(relatedHosts, deps.spaceId, deps.esClient)
|
||||
? await addHostRiskData(
|
||||
relatedHosts,
|
||||
deps.spaceId,
|
||||
deps.esClient,
|
||||
options.isNewRiskScoreModuleAvailable
|
||||
)
|
||||
: relatedHosts;
|
||||
|
||||
return {
|
||||
|
@ -73,10 +78,16 @@ export const usersRelatedHosts: SecuritySolutionFactory<RelatedEntitiesQueries.r
|
|||
async function addHostRiskData(
|
||||
relatedHosts: RelatedHost[],
|
||||
spaceId: string,
|
||||
esClient: IScopedClusterClient
|
||||
esClient: IScopedClusterClient,
|
||||
isNewRiskScoreModuleAvailable: boolean
|
||||
): Promise<RelatedHost[]> {
|
||||
const hostNames = relatedHosts.map((item) => item.host);
|
||||
const hostRiskData = await getHostRiskData(esClient, spaceId, hostNames);
|
||||
const hostRiskData = await getHostRiskData(
|
||||
esClient,
|
||||
spaceId,
|
||||
hostNames,
|
||||
isNewRiskScoreModuleAvailable
|
||||
);
|
||||
const hostsRiskByHostName: Record<string, RiskSeverity> | undefined =
|
||||
hostRiskData?.hits.hits.reduce(
|
||||
(acc, hit) => ({
|
||||
|
|
|
@ -21,6 +21,7 @@ export const mockOptions: HostsRelatedUsersRequestOptions = {
|
|||
factoryQueryType: RelatedEntitiesQueries.relatedUsers,
|
||||
hostName: 'host1',
|
||||
from: '2020-09-02T15:17:13.678Z',
|
||||
isNewRiskScoreModuleAvailable: false,
|
||||
};
|
||||
|
||||
export const mockSearchStrategyResponse: IEsSearchResponse<unknown> = {
|
||||
|
|
|
@ -57,7 +57,12 @@ export const hostsRelatedUsers: SecuritySolutionFactory<RelatedEntitiesQueries.r
|
|||
);
|
||||
|
||||
const enhancedUsers = deps?.spaceId
|
||||
? await addUserRiskData(relatedUsers, deps.spaceId, deps.esClient)
|
||||
? await addUserRiskData(
|
||||
relatedUsers,
|
||||
deps.spaceId,
|
||||
deps.esClient,
|
||||
options.isNewRiskScoreModuleAvailable
|
||||
)
|
||||
: relatedUsers;
|
||||
|
||||
return {
|
||||
|
@ -72,10 +77,16 @@ export const hostsRelatedUsers: SecuritySolutionFactory<RelatedEntitiesQueries.r
|
|||
async function addUserRiskData(
|
||||
relatedUsers: RelatedUser[],
|
||||
spaceId: string,
|
||||
esClient: IScopedClusterClient
|
||||
esClient: IScopedClusterClient,
|
||||
isNewRiskScoreModuleAvailable: boolean
|
||||
): Promise<RelatedUser[]> {
|
||||
const userNames = relatedUsers.map((item) => item.user);
|
||||
const userRiskData = await getUserRiskData(esClient, spaceId, userNames);
|
||||
const userRiskData = await getUserRiskData(
|
||||
esClient,
|
||||
spaceId,
|
||||
userNames,
|
||||
isNewRiskScoreModuleAvailable
|
||||
);
|
||||
const usersRiskByUserName: Record<string, RiskSeverity> | undefined =
|
||||
userRiskData?.hits.hits.reduce(
|
||||
(acc, hit) => ({
|
||||
|
|
|
@ -10,7 +10,11 @@ import type {
|
|||
RiskScoreRequestOptions,
|
||||
RiskScoreSortField,
|
||||
} from '../../../../../../common/search_strategy';
|
||||
import { Direction, RiskScoreFields } from '../../../../../../common/search_strategy';
|
||||
import {
|
||||
Direction,
|
||||
RiskScoreFields,
|
||||
RiskScoreEntity,
|
||||
} from '../../../../../../common/search_strategy';
|
||||
import { createQueryFilterClauses } from '../../../../../utils/build_query';
|
||||
|
||||
export const QUERY_SIZE = 10;
|
||||
|
@ -24,9 +28,10 @@ export const buildRiskScoreQuery = ({
|
|||
cursorStart: 0,
|
||||
},
|
||||
sort,
|
||||
riskScoreEntity,
|
||||
}: RiskScoreRequestOptions) => {
|
||||
const filter = createQueryFilterClauses(filterQuery);
|
||||
|
||||
const nameField = riskScoreEntity === RiskScoreEntity.host ? 'host.name' : 'user.name';
|
||||
if (timerange) {
|
||||
filter.push({
|
||||
range: {
|
||||
|
@ -38,6 +43,11 @@ export const buildRiskScoreQuery = ({
|
|||
},
|
||||
});
|
||||
}
|
||||
filter.push({
|
||||
exists: {
|
||||
field: nameField,
|
||||
},
|
||||
});
|
||||
|
||||
const dslQuery = {
|
||||
index: defaultIndex,
|
||||
|
|
|
@ -33,6 +33,7 @@ export const mockOptions: UsersRequestOptions = {
|
|||
querySize: 10,
|
||||
},
|
||||
sort: { field: UsersFields.name, direction: Direction.asc },
|
||||
isNewRiskScoreModuleAvailable: false,
|
||||
};
|
||||
|
||||
export const mockSearchStrategyResponse: IEsSearchResponse<unknown> = {
|
||||
|
|
|
@ -73,7 +73,13 @@ export const allUsers: SecuritySolutionFactory<UsersQueries.users> = {
|
|||
const edges = users.splice(cursorStart, querySize - cursorStart);
|
||||
const userNames = edges.map(({ name }) => name);
|
||||
const enhancedEdges = deps?.spaceId
|
||||
? await enhanceEdges(edges, userNames, deps.spaceId, deps.esClient)
|
||||
? await enhanceEdges(
|
||||
edges,
|
||||
userNames,
|
||||
deps.spaceId,
|
||||
deps.esClient,
|
||||
options.isNewRiskScoreModuleAvailable
|
||||
)
|
||||
: edges;
|
||||
|
||||
return {
|
||||
|
@ -94,9 +100,15 @@ async function enhanceEdges(
|
|||
edges: User[],
|
||||
userNames: string[],
|
||||
spaceId: string,
|
||||
esClient: IScopedClusterClient
|
||||
esClient: IScopedClusterClient,
|
||||
isNewRiskScoreModuleAvailable: boolean
|
||||
): Promise<User[]> {
|
||||
const userRiskData = await getUserRiskData(esClient, spaceId, userNames);
|
||||
const userRiskData = await getUserRiskData(
|
||||
esClient,
|
||||
spaceId,
|
||||
userNames,
|
||||
isNewRiskScoreModuleAvailable
|
||||
);
|
||||
const usersRiskByUserName: Record<string, RiskSeverity> | undefined =
|
||||
userRiskData?.hits.hits.reduce(
|
||||
(acc, hit) => ({
|
||||
|
@ -119,12 +131,13 @@ async function enhanceEdges(
|
|||
export async function getUserRiskData(
|
||||
esClient: IScopedClusterClient,
|
||||
spaceId: string,
|
||||
userNames: string[]
|
||||
userNames: string[],
|
||||
isNewRiskScoreModuleAvailable: boolean
|
||||
) {
|
||||
try {
|
||||
const userRiskResponse = await esClient.asCurrentUser.search<UserRiskScore>(
|
||||
buildRiskScoreQuery({
|
||||
defaultIndex: [getUserRiskIndex(spaceId)],
|
||||
defaultIndex: [getUserRiskIndex(spaceId, true, isNewRiskScoreModuleAvailable)],
|
||||
filterQuery: buildUserNamesFilter(userNames),
|
||||
riskScoreEntity: RiskScoreEntity.user,
|
||||
})
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
legacyTransformIds,
|
||||
createTransforms,
|
||||
clearLegacyTransforms,
|
||||
clearTransforms,
|
||||
} from './utils';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
|
@ -26,6 +27,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
const es = getService('es');
|
||||
const supertest = getService('supertest');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const log = getService('log');
|
||||
|
||||
describe('Risk Engine', () => {
|
||||
afterEach(async () => {
|
||||
|
@ -34,6 +36,11 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
await clearLegacyTransforms({
|
||||
es,
|
||||
log,
|
||||
});
|
||||
await clearTransforms({
|
||||
es,
|
||||
log,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -67,7 +74,9 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
const ilmPolicyName = '.risk-score-ilm-policy';
|
||||
const componentTemplateName = '.risk-score-mappings';
|
||||
const indexTemplateName = '.risk-score.risk-score-default-index-template';
|
||||
const indexName = 'risk-score.risk-score-default';
|
||||
const dataStreamName = 'risk-score.risk-score-default';
|
||||
const latestIndexName = 'risk-score.risk-score-latest-default';
|
||||
const transformId = 'risk_score_latest_transform_default';
|
||||
|
||||
await initRiskEngine();
|
||||
|
||||
|
@ -122,6 +131,9 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
calculated_score_norm: {
|
||||
type: 'float',
|
||||
},
|
||||
category_1_count: {
|
||||
type: 'long',
|
||||
},
|
||||
category_1_score: {
|
||||
type: 'float',
|
||||
},
|
||||
|
@ -178,6 +190,9 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
calculated_score_norm: {
|
||||
type: 'float',
|
||||
},
|
||||
category_1_count: {
|
||||
type: 'long',
|
||||
},
|
||||
category_1_score: {
|
||||
type: 'float',
|
||||
},
|
||||
|
@ -253,10 +268,12 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
const dsResponse = await es.indices.get({
|
||||
index: indexName,
|
||||
index: dataStreamName,
|
||||
});
|
||||
|
||||
const dataStream = Object.values(dsResponse).find((ds) => ds.data_stream === indexName);
|
||||
const dataStream = Object.values(dsResponse).find(
|
||||
(ds) => ds.data_stream === dataStreamName
|
||||
);
|
||||
|
||||
expect(dataStream?.mappings?._meta?.managed).to.eql(true);
|
||||
expect(dataStream?.mappings?._meta?.namespace).to.eql('default');
|
||||
|
@ -276,6 +293,18 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
expect(dataStream?.settings?.index?.hidden).to.eql('true');
|
||||
expect(dataStream?.settings?.index?.number_of_shards).to.eql(1);
|
||||
expect(dataStream?.settings?.index?.auto_expand_replicas).to.eql('0-1');
|
||||
|
||||
const indexExist = await es.indices.exists({
|
||||
index: latestIndexName,
|
||||
});
|
||||
|
||||
expect(indexExist).to.eql(true);
|
||||
|
||||
const transformStats = await es.transform.getTransformStats({
|
||||
transform_id: transformId,
|
||||
});
|
||||
|
||||
expect(transformStats.transforms[0].state).to.eql('started');
|
||||
});
|
||||
|
||||
it('should create configuration saved object', async () => {
|
||||
|
@ -338,6 +367,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
expect(status1.body).to.eql({
|
||||
risk_engine_status: 'NOT_INSTALLED',
|
||||
legacy_risk_engine_status: 'NOT_INSTALLED',
|
||||
is_max_amount_of_risk_engines_reached: false,
|
||||
});
|
||||
|
||||
await initRiskEngine();
|
||||
|
@ -347,6 +377,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
expect(status2.body).to.eql({
|
||||
risk_engine_status: 'ENABLED',
|
||||
legacy_risk_engine_status: 'NOT_INSTALLED',
|
||||
is_max_amount_of_risk_engines_reached: false,
|
||||
});
|
||||
|
||||
await disableRiskEngine();
|
||||
|
@ -355,6 +386,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
expect(status3.body).to.eql({
|
||||
risk_engine_status: 'DISABLED',
|
||||
legacy_risk_engine_status: 'NOT_INSTALLED',
|
||||
is_max_amount_of_risk_engines_reached: false,
|
||||
});
|
||||
|
||||
await enableRiskEngine();
|
||||
|
@ -363,6 +395,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
expect(status4.body).to.eql({
|
||||
risk_engine_status: 'ENABLED',
|
||||
legacy_risk_engine_status: 'NOT_INSTALLED',
|
||||
is_max_amount_of_risk_engines_reached: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -373,6 +406,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
expect(status1.body).to.eql({
|
||||
risk_engine_status: 'NOT_INSTALLED',
|
||||
legacy_risk_engine_status: 'ENABLED',
|
||||
is_max_amount_of_risk_engines_reached: false,
|
||||
});
|
||||
|
||||
await initRiskEngine();
|
||||
|
@ -382,6 +416,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
expect(status2.body).to.eql({
|
||||
risk_engine_status: 'ENABLED',
|
||||
legacy_risk_engine_status: 'NOT_INSTALLED',
|
||||
is_max_amount_of_risk_engines_reached: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
import { RISK_SCORE_CALCULATION_URL } from '@kbn/security-solution-plugin/common/constants';
|
||||
import type { RiskScore } from '@kbn/security-solution-plugin/server/lib/risk_engine/types';
|
||||
import type { RiskScore } from '@kbn/security-solution-plugin/common/risk_engine';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { FtrProviderContext } from '../../../common/ftr_provider_context';
|
||||
import { deleteAllAlerts, deleteAllRules } from '../../../utils';
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import expect from '@kbn/expect';
|
||||
import { ALERT_RISK_SCORE } from '@kbn/rule-data-utils';
|
||||
import { RISK_SCORE_PREVIEW_URL } from '@kbn/security-solution-plugin/common/constants';
|
||||
import type { RiskScore } from '@kbn/security-solution-plugin/server/lib/risk_engine/types';
|
||||
import type { RiskScore } from '@kbn/security-solution-plugin/common/risk_engine';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { FtrProviderContext } from '../../../common/ftr_provider_context';
|
||||
import { createSignalsIndex, deleteAllAlerts, deleteAllRules } from '../../../utils';
|
||||
|
|
|
@ -9,10 +9,7 @@ import { v4 as uuidv4 } from 'uuid';
|
|||
import type SuperTest from 'supertest';
|
||||
import type { Client } from '@elastic/elasticsearch';
|
||||
import type { ToolingLog } from '@kbn/tooling-log';
|
||||
import type {
|
||||
EcsRiskScore,
|
||||
RiskScore,
|
||||
} from '@kbn/security-solution-plugin/server/lib/risk_engine/types';
|
||||
import type { EcsRiskScore, RiskScore } from '@kbn/security-solution-plugin/common/risk_engine';
|
||||
import { riskEngineConfigurationTypeName } from '@kbn/security-solution-plugin/server/lib/risk_engine/saved_object';
|
||||
import type { KbnClient } from '@kbn/test';
|
||||
import {
|
||||
|
@ -167,16 +164,40 @@ export const legacyTransformIds = [
|
|||
'ml_userriskscore_latest_transform_default',
|
||||
];
|
||||
|
||||
export const clearLegacyTransforms = async ({ es }: { es: Client }): Promise<void> => {
|
||||
export const clearTransforms = async ({
|
||||
es,
|
||||
log,
|
||||
}: {
|
||||
es: Client;
|
||||
log: ToolingLog;
|
||||
}): Promise<void> => {
|
||||
try {
|
||||
await es.transform.deleteTransform({
|
||||
transform_id: 'risk_score_latest_transform_default',
|
||||
force: true,
|
||||
});
|
||||
} catch (e) {
|
||||
log.error(`Error deleting risk_score_latest_transform_default: ${e.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const clearLegacyTransforms = async ({
|
||||
es,
|
||||
log,
|
||||
}: {
|
||||
es: Client;
|
||||
log: ToolingLog;
|
||||
}): Promise<void> => {
|
||||
const transforms = legacyTransformIds.map((transform) =>
|
||||
es.transform.deleteTransform({
|
||||
transform_id: transform,
|
||||
force: true,
|
||||
})
|
||||
);
|
||||
try {
|
||||
await Promise.all(transforms);
|
||||
} catch (e) {
|
||||
//
|
||||
log.error(`Error deleting legacy transforms: ${e.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -590,11 +590,11 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
|
||||
describe('with host risk index', async () => {
|
||||
before(async () => {
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk');
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/entity/risks');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk');
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/entity/risks');
|
||||
});
|
||||
|
||||
it('should be enriched with host risk score', async () => {
|
||||
|
|
|
@ -248,11 +248,11 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
|
||||
describe('alerts should be be enriched', () => {
|
||||
before(async () => {
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk');
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/entity/risks');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk');
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/entity/risks');
|
||||
});
|
||||
|
||||
it('should be enriched with host risk score', async () => {
|
||||
|
|
|
@ -731,11 +731,11 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
|
||||
describe('alerts should be be enriched', () => {
|
||||
before(async () => {
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk');
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/entity/risks');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk');
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/entity/risks');
|
||||
});
|
||||
|
||||
it('should be enriched with host risk score', async () => {
|
||||
|
|
|
@ -237,51 +237,13 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
expect(previewAlerts[0]?._source?.user?.risk).to.eql(undefined);
|
||||
});
|
||||
|
||||
describe('with host risk index', () => {
|
||||
before(async () => {
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk');
|
||||
});
|
||||
|
||||
it('should host have risk score field and do not have user risk score', async () => {
|
||||
const rule: QueryRuleCreateProps = {
|
||||
...getRuleForSignalTesting(['auditbeat-*']),
|
||||
query: `_id:${ID} or _id:GBbXBmkBR346wHgn5_eR or _id:x10zJ2oE9v5HJNSHhyxi`,
|
||||
};
|
||||
const { previewId } = await previewRule({ supertest, rule });
|
||||
const previewAlerts = await getPreviewAlerts({ es, previewId });
|
||||
|
||||
const firstAlert = previewAlerts.find(
|
||||
(alert) => alert?._source?.host?.name === 'suricata-zeek-sensor-toronto'
|
||||
);
|
||||
const secondAlert = previewAlerts.find(
|
||||
(alert) => alert?._source?.host?.name === 'suricata-sensor-london'
|
||||
);
|
||||
const thirdAlert = previewAlerts.find(
|
||||
(alert) => alert?._source?.host?.name === 'IE11WIN8_1'
|
||||
);
|
||||
|
||||
expect(firstAlert?._source?.host?.risk?.calculated_level).to.eql('Critical');
|
||||
expect(firstAlert?._source?.host?.risk?.calculated_score_norm).to.eql(96);
|
||||
expect(firstAlert?._source?.user?.risk).to.eql(undefined);
|
||||
expect(secondAlert?._source?.host?.risk?.calculated_level).to.eql('Low');
|
||||
expect(secondAlert?._source?.host?.risk?.calculated_score_norm).to.eql(20);
|
||||
expect(thirdAlert?._source?.host?.risk).to.eql(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with host and user risk indices', () => {
|
||||
before(async () => {
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk');
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/entity/user_risk');
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/entity/risks');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk');
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/entity/user_risk');
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/entity/risks');
|
||||
});
|
||||
|
||||
it('should have host and user risk score fields', async () => {
|
||||
|
|
|
@ -1576,11 +1576,11 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
|
||||
describe('alerts should be enriched', () => {
|
||||
before(async () => {
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk');
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/entity/risks');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk');
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/entity/risks');
|
||||
});
|
||||
|
||||
it('should be enriched with host risk score', async () => {
|
||||
|
|
|
@ -399,11 +399,11 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
|
||||
describe('with host risk index', async () => {
|
||||
before(async () => {
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk');
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/entity/risks');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk');
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/entity/risks');
|
||||
});
|
||||
|
||||
it('should be enriched with host risk score', async () => {
|
||||
|
|
279
x-pack/test/functional/es_archives/entity/risks/data.json
Normal file
279
x-pack/test/functional/es_archives/entity/risks/data.json
Normal file
|
@ -0,0 +1,279 @@
|
|||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"index": "risk-score.risk-score-latest-default",
|
||||
"id": "1",
|
||||
"source": {
|
||||
"host": {
|
||||
"name": "suricata-zeek-sensor-toronto",
|
||||
"risk": {
|
||||
"calculated_score_norm": 96,
|
||||
"calculated_level": "Critical",
|
||||
"id_field": "host.name",
|
||||
"id_value": "suricata-zeek-sensor-toronto",
|
||||
"calculated_score": 190,
|
||||
"category_1_score": 190,
|
||||
"category_1_count": 1,
|
||||
"notes": [],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "2e17f189-d77d-4537-8d84-592e29334493",
|
||||
"index": ".internal.alerts-security.alerts-default-000001",
|
||||
"description": "Alert from Rule: Rule 2",
|
||||
"category": "category_1",
|
||||
"risk_score": 70,
|
||||
"timestamp": "2023-08-14T09:08:18.664Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"@timestamp": "2022-08-12T14:45:36.171Z"
|
||||
},
|
||||
"type": "_doc"
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"id": "2",
|
||||
"index": "risk-score.risk-score-latest-default",
|
||||
"source": {
|
||||
"host": {
|
||||
"name": "suricata-sensor-london",
|
||||
"risk": {
|
||||
"calculated_score_norm": 20,
|
||||
"calculated_level": "Low",
|
||||
"id_field": "host.name",
|
||||
"id_value": "suricata-sensor-london",
|
||||
"calculated_score": 70,
|
||||
"category_1_score": 70,
|
||||
"category_1_count": 1,
|
||||
"notes": [],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "2e17f189-d77d-4537-8d84-592e29334493",
|
||||
"index": ".internal.alerts-security.alerts-default-000001",
|
||||
"description": "Alert from Rule: Rule 2",
|
||||
"category": "category_1",
|
||||
"risk_score": 70,
|
||||
"timestamp": "2023-08-14T09:08:18.664Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"@timestamp": "2022-08-12T14:45:36.171Z"
|
||||
},
|
||||
"type": "_doc"
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"id": "3",
|
||||
"index": "risk-score.risk-score-latest-default",
|
||||
"source": {
|
||||
"host": {
|
||||
"name": "zeek-newyork-sha-aa8df15",
|
||||
"risk": {
|
||||
"calculated_score_norm": 23,
|
||||
"calculated_level": "Low",
|
||||
"id_field": "host.name",
|
||||
"id_value": "zeek-newyork-sha-aa8df15",
|
||||
"calculated_score": 70,
|
||||
"category_1_score": 70,
|
||||
"category_1_count": 1,
|
||||
"notes": [],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "2e17f189-d77d-4537-8d84-592e29334493",
|
||||
"index": ".internal.alerts-security.alerts-default-000001",
|
||||
"description": "Alert from Rule: Rule 2",
|
||||
"category": "category_1",
|
||||
"risk_score": 70,
|
||||
"timestamp": "2023-08-14T09:08:18.664Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"@timestamp": "2022-08-12T14:45:36.171Z"
|
||||
},
|
||||
"type": "_doc"
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"id": "4",
|
||||
"index": "risk-score.risk-score-latest-default",
|
||||
"source": {
|
||||
"host": {
|
||||
"name": "zeek-sensor-amsterdam",
|
||||
"risk": {
|
||||
"calculated_score_norm": 70,
|
||||
"calculated_level": "Critical",
|
||||
"id_field": "host.name",
|
||||
"id_value": "zeek-newyork-sha-aa8df15",
|
||||
"calculated_score": 190,
|
||||
"category_1_score": 190,
|
||||
"category_1_count": 1,
|
||||
"notes": [],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "2e17f189-d77d-4537-8d84-592e29334493",
|
||||
"index": ".internal.alerts-security.alerts-default-000001",
|
||||
"description": "Alert from Rule: Rule 2",
|
||||
"category": "category_1",
|
||||
"risk_score": 70,
|
||||
"timestamp": "2023-08-14T09:08:18.664Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"@timestamp": "2022-08-12T14:45:36.171Z"
|
||||
},
|
||||
"type": "_doc"
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"id": "5",
|
||||
"index": "risk-score.risk-score-latest-default",
|
||||
"source": {
|
||||
"host": {
|
||||
"name": "mothra",
|
||||
"risk": {
|
||||
"calculated_score_norm": 1,
|
||||
"calculated_level": "Low",
|
||||
"id_field": "host.name",
|
||||
"id_value": "mothra",
|
||||
"calculated_score": 20,
|
||||
"category_1_score": 20,
|
||||
"category_1_count": 1,
|
||||
"notes": [],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "2e17f189-d77d-4537-8d84-592e29334493",
|
||||
"index": ".internal.alerts-security.alerts-default-000001",
|
||||
"description": "Alert from Rule: Rule 2",
|
||||
"category": "category_1",
|
||||
"risk_score": 20,
|
||||
"timestamp": "2023-08-14T09:08:18.664Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"@timestamp": "2022-08-12T14:45:36.171Z"
|
||||
},
|
||||
"type": "_doc"
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"id": "6",
|
||||
"index": "risk-score.risk-score-latest-default",
|
||||
"source": {
|
||||
"host": {
|
||||
"name": "host-0",
|
||||
"risk": {
|
||||
"calculated_score_norm": 1,
|
||||
"calculated_level": "Low",
|
||||
"id_field": "host.name",
|
||||
"id_value": "host-0",
|
||||
"calculated_score": 20,
|
||||
"category_1_score": 20,
|
||||
"category_1_count": 1,
|
||||
"notes": [],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "2e17f189-d77d-4537-8d84-592e29334493",
|
||||
"index": ".internal.alerts-security.alerts-default-000001",
|
||||
"description": "Alert from Rule: Rule 2",
|
||||
"category": "category_1",
|
||||
"risk_score": 20,
|
||||
"timestamp": "2023-08-14T09:08:18.664Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"@timestamp": "2022-08-12T14:45:36.171Z"
|
||||
},
|
||||
"type": "_doc"
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"index": "risk-score.risk-score-latest-default",
|
||||
"id": "7",
|
||||
"source": {
|
||||
"user": {
|
||||
"name": "root",
|
||||
"risk": {
|
||||
"calculated_score_norm": 11,
|
||||
"calculated_level": "Low",
|
||||
"id_field": "user.name",
|
||||
"id_value": "root",
|
||||
"calculated_score": 30,
|
||||
"category_1_score": 30,
|
||||
"category_1_count": 1,
|
||||
"notes": [],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "2e17f189-d77d-4537-8d84-592e29334493",
|
||||
"index": ".internal.alerts-security.alerts-default-000001",
|
||||
"description": "Alert from Rule: Rule 2",
|
||||
"category": "category_1",
|
||||
"risk_score": 30,
|
||||
"timestamp": "2023-08-14T09:08:18.664Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"@timestamp": "2022-08-12T14:45:36.171Z"
|
||||
},
|
||||
"type": "_doc"
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"id": "8",
|
||||
"index": "risk-score.risk-score-latest-default",
|
||||
"source": {
|
||||
"host": {
|
||||
"name": "User name 1",
|
||||
"risk": {
|
||||
"calculated_score_norm": 20,
|
||||
"calculated_level": "Low",
|
||||
"id_field": "user.name",
|
||||
"id_value": "User name 1",
|
||||
"calculated_score": 50,
|
||||
"category_1_score": 50,
|
||||
"category_1_count": 1,
|
||||
"notes": [],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "2e17f189-d77d-4537-8d84-592e29334493",
|
||||
"index": ".internal.alerts-security.alerts-default-000001",
|
||||
"description": "Alert from Rule: Rule 2",
|
||||
"category": "category_1",
|
||||
"risk_score": 50,
|
||||
"timestamp": "2023-08-14T09:08:18.664Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"@timestamp": "2022-08-12T14:45:36.171Z"
|
||||
},
|
||||
"type": "_doc"
|
||||
}
|
||||
}
|
136
x-pack/test/functional/es_archives/entity/risks/mappings.json
Normal file
136
x-pack/test/functional/es_archives/entity/risks/mappings.json
Normal file
|
@ -0,0 +1,136 @@
|
|||
{
|
||||
|
||||
"type": "index",
|
||||
"value": {
|
||||
"index": "risk-score.risk-score-latest-default",
|
||||
"mappings": {
|
||||
"dynamic": "strict",
|
||||
"properties": {
|
||||
"@timestamp": {
|
||||
"type": "date"
|
||||
},
|
||||
"host": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"risk": {
|
||||
"properties": {
|
||||
"calculated_level": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"calculated_score": {
|
||||
"type": "float"
|
||||
},
|
||||
"calculated_score_norm": {
|
||||
"type": "float"
|
||||
},
|
||||
"category_1_count": {
|
||||
"type": "long"
|
||||
},
|
||||
"category_1_score": {
|
||||
"type": "float"
|
||||
},
|
||||
"id_field": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"id_value": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"inputs": {
|
||||
"properties": {
|
||||
"category": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"description": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"id": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"index": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"risk_score": {
|
||||
"type": "float"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "date"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notes": {
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"risk": {
|
||||
"properties": {
|
||||
"calculated_level": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"calculated_score": {
|
||||
"type": "float"
|
||||
},
|
||||
"calculated_score_norm": {
|
||||
"type": "float"
|
||||
},
|
||||
"category_1_count": {
|
||||
"type": "long"
|
||||
},
|
||||
"category_1_score": {
|
||||
"type": "float"
|
||||
},
|
||||
"id_field": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"id_value": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"inputs": {
|
||||
"properties": {
|
||||
"category": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"description": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"id": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"index": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"risk_score": {
|
||||
"type": "float"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "date"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notes": {
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"index": {
|
||||
"auto_expand_replicas": "0-1",
|
||||
"number_of_replicas": "0",
|
||||
"number_of_shards": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue